diff --git a/README.md b/README.md index 0cd99b792..6f1ccfcaf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- +

diff --git a/config/custom-ue.yaml b/config/custom-ue.yaml index 2600b37ae..4545e6a24 100644 --- a/config/custom-ue.yaml +++ b/config/custom-ue.yaml @@ -29,7 +29,6 @@ sessions: slice: sst: 1 sd: 1 - emergency: false # Configured NSSAI for this UE by HPLMN configured-nssai: diff --git a/config/free5gc-ue.yaml b/config/free5gc-ue.yaml index 9f2e7c8f1..28b0baa9c 100644 --- a/config/free5gc-ue.yaml +++ b/config/free5gc-ue.yaml @@ -29,7 +29,6 @@ sessions: slice: sst: 0x01 sd: 0x010203 - emergency: false # Configured NSSAI for this UE by HPLMN configured-nssai: diff --git a/config/open5gs-ue.yaml b/config/open5gs-ue.yaml index d6779ed65..49f60824b 100644 --- a/config/open5gs-ue.yaml +++ b/config/open5gs-ue.yaml @@ -28,8 +28,6 @@ sessions: apn: 'internet' slice: sst: 1 - sd: 1 - emergency: false # Configured NSSAI for this UE by HPLMN configured-nssai: diff --git a/src/gnb/app/cmd_handler.cpp b/src/gnb/app/cmd_handler.cpp index a3b94ccde..5d40d4c69 100644 --- a/src/gnb/app/cmd_handler.cpp +++ b/src/gnb/app/cmd_handler.cpp @@ -66,7 +66,7 @@ bool GnbCmdHandler::isAllPaused() return true; } -void GnbCmdHandler::handleCmd(NwGnbCliCommand &msg) +void GnbCmdHandler::handleCmd(NmGnbCliCommand &msg) { pauseTasks(); @@ -97,7 +97,7 @@ void GnbCmdHandler::handleCmd(NwGnbCliCommand &msg) unpauseTasks(); } -void GnbCmdHandler::handleCmdImpl(NwGnbCliCommand &msg) +void GnbCmdHandler::handleCmdImpl(NmGnbCliCommand &msg) { switch (msg.cmd->present) { diff --git a/src/gnb/app/cmd_handler.hpp b/src/gnb/app/cmd_handler.hpp index 4341dbff7..354583d31 100644 --- a/src/gnb/app/cmd_handler.hpp +++ b/src/gnb/app/cmd_handler.hpp @@ -24,7 +24,7 @@ class GnbCmdHandler { } - void handleCmd(NwGnbCliCommand &msg); + void handleCmd(NmGnbCliCommand &msg); private: void pauseTasks(); @@ -32,7 +32,7 @@ class GnbCmdHandler bool isAllPaused(); private: - void handleCmdImpl(NwGnbCliCommand &msg); + void handleCmdImpl(NmGnbCliCommand &msg); private: void sendResult(const InetAddress &address, const std::string &output); diff --git a/src/gnb/app/task.cpp b/src/gnb/app/task.cpp index 5ba5bc46f..aa6b39d07 100644 --- a/src/gnb/app/task.cpp +++ b/src/gnb/app/task.cpp @@ -32,17 +32,17 @@ void GnbAppTask::onLoop() switch (msg->msgType) { case NtsMessageType::GNB_STATUS_UPDATE: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->what) { - case NwGnbStatusUpdate::NGAP_IS_UP: + case NmGnbStatusUpdate::NGAP_IS_UP: m_statusInfo.isNgapUp = w->isNgapUp; break; } break; } case NtsMessageType::GNB_CLI_COMMAND: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); GnbCmdHandler handler{m_base}; handler.handleCmd(*w); break; diff --git a/src/gnb/gnb.cpp b/src/gnb/gnb.cpp index 2a0561763..284982312 100644 --- a/src/gnb/gnb.cpp +++ b/src/gnb/gnb.cpp @@ -70,7 +70,7 @@ void GNodeB::start() void GNodeB::pushCommand(std::unique_ptr cmd, const InetAddress &address) { - taskBase->appTask->push(new NwGnbCliCommand(std::move(cmd), address)); + taskBase->appTask->push(new NmGnbCliCommand(std::move(cmd), address)); } } // namespace nr::gnb diff --git a/src/gnb/gtp/task.cpp b/src/gnb/gtp/task.cpp index d4cb91d12..6622fd961 100644 --- a/src/gnb/gtp/task.cpp +++ b/src/gnb/gtp/task.cpp @@ -55,22 +55,22 @@ void GtpTask::onLoop() switch (msg->msgType) { case NtsMessageType::GNB_NGAP_TO_GTP: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbNgapToGtp::UE_CONTEXT_UPDATE: { + case NmGnbNgapToGtp::UE_CONTEXT_UPDATE: { handleUeContextUpdate(*w->update); break; } - case NwGnbNgapToGtp::UE_CONTEXT_RELEASE: { + case NmGnbNgapToGtp::UE_CONTEXT_RELEASE: { handleUeContextDelete(w->ueId); break; } - case NwGnbNgapToGtp::SESSION_CREATE: { + case NmGnbNgapToGtp::SESSION_CREATE: { handleSessionCreate(w->resource); break; } - case NwGnbNgapToGtp::SESSION_RELEASE: { + case NmGnbNgapToGtp::SESSION_RELEASE: { handleSessionRelease(w->ueId, w->psi); break; } @@ -78,10 +78,10 @@ void GtpTask::onLoop() break; } case NtsMessageType::GNB_RLS_TO_GTP: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbRlsToGtp::DATA_PDU_DELIVERY: { + case NmGnbRlsToGtp::DATA_PDU_DELIVERY: { handleUplinkData(w->ueId, w->psi, std::move(w->pdu)); break; } @@ -240,7 +240,7 @@ void GtpTask::handleUdpReceive(const udp::NwUdpServerReceive &msg) if (m_rateLimiter->allowDownlinkPacket(sessionInd, gtp->payload.length())) { - auto *w = new NwGnbGtpToRls(NwGnbGtpToRls::DATA_PDU_DELIVERY); + auto *w = new NmGnbGtpToRls(NmGnbGtpToRls::DATA_PDU_DELIVERY); w->ueId = GetUeId(sessionInd); w->psi = GetPsi(sessionInd); w->pdu = std::move(gtp->payload); diff --git a/src/gnb/ngap/context.cpp b/src/gnb/ngap/context.cpp index 330985c8e..32ea91bd8 100644 --- a/src/gnb/ngap/context.cpp +++ b/src/gnb/ngap/context.cpp @@ -53,7 +53,7 @@ void NgapTask::receiveInitialContextSetup(int amfId, ASN_NGAP_InitialContextSetu if (ie) deliverDownlinkNas(ue->ctxId, asn::GetOctetString(ie->NAS_PDU)); - auto *w = new NwGnbNgapToGtp(NwGnbNgapToGtp::UE_CONTEXT_UPDATE); + auto *w = new NmGnbNgapToGtp(NmGnbNgapToGtp::UE_CONTEXT_UPDATE); w->update = std::make_unique(true, ue->ctxId, ue->ueAmbr); m_base->gtpTask->push(w); } @@ -67,12 +67,12 @@ void NgapTask::receiveContextRelease(int amfId, ASN_NGAP_UEContextReleaseCommand return; // Notify RRC task - auto *w1 = new NwGnbNgapToRrc(NwGnbNgapToRrc::AN_RELEASE); + auto *w1 = new NmGnbNgapToRrc(NmGnbNgapToRrc::AN_RELEASE); w1->ueId = ue->ctxId; m_base->rrcTask->push(w1); // Notify GTP task - auto *w2 = new NwGnbNgapToGtp(NwGnbNgapToGtp::UE_CONTEXT_RELEASE); + auto *w2 = new NmGnbNgapToGtp(NmGnbNgapToGtp::UE_CONTEXT_RELEASE); w2->ueId = ue->ctxId; m_base->gtpTask->push(w2); @@ -108,7 +108,7 @@ void NgapTask::receiveContextModification(int amfId, ASN_NGAP_UEContextModificat auto *response = asn::ngap::NewMessagePdu({}); sendNgapUeAssociated(ue->ctxId, response); - auto *w = new NwGnbNgapToGtp(NwGnbNgapToGtp::UE_CONTEXT_UPDATE); + auto *w = new NmGnbNgapToGtp(NmGnbNgapToGtp::UE_CONTEXT_UPDATE); w->update = std::make_unique(false, ue->ctxId, ue->ueAmbr); m_base->gtpTask->push(w); } diff --git a/src/gnb/ngap/interface.cpp b/src/gnb/ngap/interface.cpp index 87af38579..d6126486a 100644 --- a/src/gnb/ngap/interface.cpp +++ b/src/gnb/ngap/interface.cpp @@ -99,7 +99,7 @@ void NgapTask::handleAssociationShutdown(int amfId) amf->state = EAmfState::NOT_CONNECTED; - auto *w = new NwGnbSctp(NwGnbSctp::CONNECTION_CLOSE); + auto *w = new NmGnbSctp(NmGnbSctp::CONNECTION_CLOSE); w->clientId = amfId; m_base->sctpTask->push(w); @@ -192,11 +192,11 @@ void NgapTask::receiveNgSetupResponse(int amfId, ASN_NGAP_NGSetupResponse *msg) { m_isInitialized = true; - auto *update = new NwGnbStatusUpdate(NwGnbStatusUpdate::NGAP_IS_UP); + auto *update = new NmGnbStatusUpdate(NmGnbStatusUpdate::NGAP_IS_UP); update->isNgapUp = true; m_base->appTask->push(update); - m_base->rrcTask->push(new NwGnbNgapToRrc(NwGnbNgapToRrc::RADIO_POWER_ON)); + m_base->rrcTask->push(new NmGnbNgapToRrc(NmGnbNgapToRrc::RADIO_POWER_ON)); } } diff --git a/src/gnb/ngap/nas.cpp b/src/gnb/ngap/nas.cpp index 9bbacec3a..307d09649 100644 --- a/src/gnb/ngap/nas.cpp +++ b/src/gnb/ngap/nas.cpp @@ -78,7 +78,7 @@ void NgapTask::handleInitialNasTransport(int ueId, const OctetString &nasPdu, lo void NgapTask::deliverDownlinkNas(int ueId, OctetString &&nasPdu) { - auto *w = new NwGnbNgapToRrc(NwGnbNgapToRrc::NAS_DELIVERY); + auto *w = new NmGnbNgapToRrc(NmGnbNgapToRrc::NAS_DELIVERY); w->ueId = ueId; w->pdu = std::move(nasPdu); m_base->rrcTask->push(w); diff --git a/src/gnb/ngap/radio.cpp b/src/gnb/ngap/radio.cpp index 52fecfd3c..01fa5bc9d 100644 --- a/src/gnb/ngap/radio.cpp +++ b/src/gnb/ngap/radio.cpp @@ -22,7 +22,7 @@ namespace nr::gnb void NgapTask::handleRadioLinkFailure(int ueId) { // Notify GTP task - auto *w2 = new NwGnbNgapToGtp(NwGnbNgapToGtp::UE_CONTEXT_RELEASE); + auto *w2 = new NmGnbNgapToGtp(NmGnbNgapToGtp::UE_CONTEXT_RELEASE); w2->ueId = ueId; m_base->gtpTask->push(w2); @@ -48,7 +48,7 @@ void NgapTask::receivePaging(int amfId, ASN_NGAP_Paging *msg) return; } - auto *w = new NwGnbNgapToRrc(NwGnbNgapToRrc::PAGING); + auto *w = new NmGnbNgapToRrc(NmGnbNgapToRrc::PAGING); w->uePagingTmsi = asn::UniqueCopy(*ieUePagingIdentity->UEPagingIdentity.choice.fiveG_S_TMSI, asn_DEF_ASN_NGAP_FiveG_S_TMSI); w->taiListForPaging = asn::UniqueCopy(ieTaiListForPaging->TAIListForPaging, asn_DEF_ASN_NGAP_TAIListForPaging); diff --git a/src/gnb/ngap/session.cpp b/src/gnb/ngap/session.cpp index 0511da1f1..eb9bcff50 100644 --- a/src/gnb/ngap/session.cpp +++ b/src/gnb/ngap/session.cpp @@ -236,7 +236,7 @@ std::optional NgapTask::setupPduSessionResource(PduSessionResource *r resource->downTunnel.address = utils::IpToOctetString(m_base->config->gtpIp); resource->downTunnel.teid = ++m_downlinkTeidCounter; - auto *w = new NwGnbNgapToGtp(NwGnbNgapToGtp::SESSION_CREATE); + auto *w = new NmGnbNgapToGtp(NmGnbNgapToGtp::SESSION_CREATE); w->resource = resource; m_base->gtpTask->push(w); @@ -277,7 +277,7 @@ void NgapTask::receiveSessionResourceReleaseCommand(int amfId, ASN_NGAP_PDUSessi // Perform release for (auto &psi : psIds) { - auto *w = new NwGnbNgapToGtp(NwGnbNgapToGtp::SESSION_RELEASE); + auto *w = new NmGnbNgapToGtp(NmGnbNgapToGtp::SESSION_RELEASE); w->ueId = ue->ctxId; w->psi = psi; m_base->gtpTask->push(w); diff --git a/src/gnb/ngap/task.cpp b/src/gnb/ngap/task.cpp index 34610ecb9..3623f4334 100644 --- a/src/gnb/ngap/task.cpp +++ b/src/gnb/ngap/task.cpp @@ -30,7 +30,7 @@ void NgapTask::onStart() for (auto &amfCtx : m_amfCtx) { - auto *msg = new NwGnbSctp(NwGnbSctp::CONNECTION_REQUEST); + auto *msg = new NmGnbSctp(NmGnbSctp::CONNECTION_REQUEST); msg->clientId = amfCtx.second->ctxId; msg->localAddress = m_base->config->ngapIp; msg->localPort = 0; @@ -51,18 +51,18 @@ void NgapTask::onLoop() switch (msg->msgType) { case NtsMessageType::GNB_RRC_TO_NGAP: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbRrcToNgap::INITIAL_NAS_DELIVERY: { + case NmGnbRrcToNgap::INITIAL_NAS_DELIVERY: { handleInitialNasTransport(w->ueId, w->pdu, w->rrcEstablishmentCause); break; } - case NwGnbRrcToNgap::UPLINK_NAS_DELIVERY: { + case NmGnbRrcToNgap::UPLINK_NAS_DELIVERY: { handleUplinkNasTransport(w->ueId, w->pdu); break; } - case NwGnbRrcToNgap::RADIO_LINK_FAILURE: { + case NmGnbRrcToNgap::RADIO_LINK_FAILURE: { handleRadioLinkFailure(w->ueId); break; } @@ -70,16 +70,16 @@ void NgapTask::onLoop() break; } case NtsMessageType::GNB_SCTP: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbSctp::ASSOCIATION_SETUP: + case NmGnbSctp::ASSOCIATION_SETUP: handleAssociationSetup(w->clientId, w->associationId, w->inStreams, w->outStreams); break; - case NwGnbSctp::RECEIVE_MESSAGE: + case NmGnbSctp::RECEIVE_MESSAGE: handleSctpMessage(w->clientId, w->stream, w->buffer); break; - case NwGnbSctp::ASSOCIATION_SHUTDOWN: + case NmGnbSctp::ASSOCIATION_SHUTDOWN: handleAssociationShutdown(w->clientId); break; default: diff --git a/src/gnb/ngap/transport.cpp b/src/gnb/ngap/transport.cpp index 29f4d8405..b6c0e2494 100644 --- a/src/gnb/ngap/transport.cpp +++ b/src/gnb/ngap/transport.cpp @@ -109,7 +109,7 @@ void NgapTask::sendNgapNonUe(int associatedAmf, ASN_NGAP_NGAP_PDU *pdu) m_logger->err("NGAP APER encoding failed"); else { - auto *msg = new NwGnbSctp(NwGnbSctp::SEND_MESSAGE); + auto *msg = new NmGnbSctp(NmGnbSctp::SEND_MESSAGE); msg->clientId = amf->ctxId; msg->stream = 0; msg->buffer = UniqueBuffer{buffer, static_cast(encoded)}; @@ -200,7 +200,7 @@ void NgapTask::sendNgapUeAssociated(int ueId, ASN_NGAP_NGAP_PDU *pdu) m_logger->err("NGAP APER encoding failed"); else { - auto *msg = new NwGnbSctp(NwGnbSctp::SEND_MESSAGE); + auto *msg = new NmGnbSctp(NmGnbSctp::SEND_MESSAGE); msg->clientId = amf->ctxId; msg->stream = ue->uplinkStream; msg->buffer = UniqueBuffer{buffer, static_cast(encoded)}; diff --git a/src/gnb/nts.hpp b/src/gnb/nts.hpp index cb3176bce..3edde81ac 100644 --- a/src/gnb/nts.hpp +++ b/src/gnb/nts.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -31,28 +32,28 @@ extern "C" namespace nr::gnb { -struct NwGnbRlsToRrc : NtsMessage +struct NmGnbRlsToRrc : NtsMessage { enum PR { - RRC_PDU_DELIVERY, - SIGNAL_LOST + SIGNAL_DETECTED, + UPLINK_RRC, } present; - // RRC_PDU_DELIVERY - // SIGNAL_LOST + // SIGNAL_DETECTED + // UPLINK_RRC int ueId{}; - // RRC_PDU_DELIVERY - rrc::RrcChannel channel{}; - OctetString pdu{}; + // UPLINK_RRC + OctetString data; + rrc::RrcChannel rrcChannel{}; - explicit NwGnbRlsToRrc(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_RRC), present(present) + explicit NmGnbRlsToRrc(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_RRC), present(present) { } }; -struct NwGnbRlsToGtp : NtsMessage +struct NmGnbRlsToGtp : NtsMessage { enum PR { @@ -62,14 +63,14 @@ struct NwGnbRlsToGtp : NtsMessage // DATA_PDU_DELIVERY int ueId{}; int psi{}; - OctetString pdu{}; + OctetString pdu; - explicit NwGnbRlsToGtp(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_GTP), present(present) + explicit NmGnbRlsToGtp(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_GTP), present(present) { } }; -struct NwGnbGtpToRls : NtsMessage +struct NmGnbGtpToRls : NtsMessage { enum PR { @@ -81,16 +82,69 @@ struct NwGnbGtpToRls : NtsMessage int psi{}; OctetString pdu{}; - explicit NwGnbGtpToRls(PR present) : NtsMessage(NtsMessageType::GNB_GTP_TO_RLS), present(present) + explicit NmGnbGtpToRls(PR present) : NtsMessage(NtsMessageType::GNB_GTP_TO_RLS), present(present) { } }; -struct NwGnbRrcToRls : NtsMessage +struct NmGnbRlsToRls : NtsMessage +{ + enum PR + { + SIGNAL_DETECTED, + SIGNAL_LOST, + RECEIVE_RLS_MESSAGE, + DOWNLINK_RRC, + DOWNLINK_DATA, + UPLINK_RRC, + UPLINK_DATA, + RADIO_LINK_FAILURE, + TRANSMISSION_FAILURE, + } present; + + // SIGNAL_DETECTED + // SIGNAL_LOST + // DOWNLINK_RRC + // DOWNLINK_DATA + // UPLINK_DATA + // UPLINK_RRC + int ueId{}; + + // RECEIVE_RLS_MESSAGE + std::unique_ptr msg{}; + + // DOWNLINK_DATA + // UPLINK_DATA + int psi{}; + + // DOWNLINK_DATA + // DOWNLINK_RRC + // UPLINK_DATA + // UPLINK_RRC + OctetString data; + + // DOWNLINK_RRC + uint32_t pduId{}; + + // DOWNLINK_RRC + // UPLINK_RRC + rrc::RrcChannel rrcChannel{}; + + // RADIO_LINK_FAILURE + rls::ERlfCause rlfCause{}; + + // TRANSMISSION_FAILURE + std::vector pduList; + + explicit NmGnbRlsToRls(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_RLS), present(present) + { + } +}; + +struct NmGnbRrcToRls : NtsMessage { enum PR { - RADIO_POWER_ON, RRC_PDU_DELIVERY, } present; @@ -99,12 +153,12 @@ struct NwGnbRrcToRls : NtsMessage rrc::RrcChannel channel{}; OctetString pdu{}; - explicit NwGnbRrcToRls(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_RLS), present(present) + explicit NmGnbRrcToRls(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_RLS), present(present) { } }; -struct NwGnbNgapToRrc : NtsMessage +struct NmGnbNgapToRrc : NtsMessage { enum PR { @@ -125,12 +179,12 @@ struct NwGnbNgapToRrc : NtsMessage asn::Unique uePagingTmsi{}; asn::Unique taiListForPaging{}; - explicit NwGnbNgapToRrc(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_RRC), present(present) + explicit NmGnbNgapToRrc(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_RRC), present(present) { } }; -struct NwGnbRrcToNgap : NtsMessage +struct NmGnbRrcToNgap : NtsMessage { enum PR { @@ -151,12 +205,12 @@ struct NwGnbRrcToNgap : NtsMessage // INITIAL_NAS_DELIVERY long rrcEstablishmentCause{}; - explicit NwGnbRrcToNgap(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_NGAP), present(present) + explicit NmGnbRrcToNgap(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_NGAP), present(present) { } }; -struct NwGnbNgapToGtp : NtsMessage +struct NmGnbNgapToGtp : NtsMessage { enum PR { @@ -179,12 +233,12 @@ struct NwGnbNgapToGtp : NtsMessage // SESSION_RELEASE int psi{}; - explicit NwGnbNgapToGtp(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_GTP), present(present) + explicit NmGnbNgapToGtp(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_GTP), present(present) { } }; -struct NwGnbSctp : NtsMessage +struct NmGnbSctp : NtsMessage { enum PR { @@ -224,12 +278,12 @@ struct NwGnbSctp : NtsMessage UniqueBuffer buffer{}; uint16_t stream{}; - explicit NwGnbSctp(PR present) : NtsMessage(NtsMessageType::GNB_SCTP), present(present) + explicit NmGnbSctp(PR present) : NtsMessage(NtsMessageType::GNB_SCTP), present(present) { } }; -struct NwGnbStatusUpdate : NtsMessage +struct NmGnbStatusUpdate : NtsMessage { static constexpr const int NGAP_IS_UP = 1; @@ -238,17 +292,17 @@ struct NwGnbStatusUpdate : NtsMessage // NGAP_IS_UP bool isNgapUp{}; - explicit NwGnbStatusUpdate(const int what) : NtsMessage(NtsMessageType::GNB_STATUS_UPDATE), what(what) + explicit NmGnbStatusUpdate(const int what) : NtsMessage(NtsMessageType::GNB_STATUS_UPDATE), what(what) { } }; -struct NwGnbCliCommand : NtsMessage +struct NmGnbCliCommand : NtsMessage { std::unique_ptr cmd; InetAddress address; - NwGnbCliCommand(std::unique_ptr cmd, InetAddress address) + NmGnbCliCommand(std::unique_ptr cmd, InetAddress address) : NtsMessage(NtsMessageType::GNB_CLI_COMMAND), cmd(std::move(cmd)), address(address) { } diff --git a/src/gnb/rls/ctl_task.cpp b/src/gnb/rls/ctl_task.cpp new file mode 100644 index 000000000..a2f72ea04 --- /dev/null +++ b/src/gnb/rls/ctl_task.cpp @@ -0,0 +1,260 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "ctl_task.hpp" + +#include +#include + +static constexpr const size_t MAX_PDU_COUNT = 4096; +static constexpr const int MAX_PDU_TTL = 3000; + +static constexpr const int TIMER_ID_ACK_CONTROL = 1; +static constexpr const int TIMER_ID_ACK_SEND = 2; + +static constexpr const int TIMER_PERIOD_ACK_CONTROL = 1500; +static constexpr const int TIMER_PERIOD_ACK_SEND = 2250; + +namespace nr::gnb +{ + +RlsControlTask::RlsControlTask(TaskBase *base, uint64_t sti) + : m_sti{sti}, m_mainTask{}, m_udpTask{}, m_pduMap{}, m_pendingAck{} +{ + m_logger = base->logBase->makeUniqueLogger("rls-ctl"); +} + +void RlsControlTask::initialize(NtsTask *mainTask, RlsUdpTask *udpTask) +{ + m_mainTask = mainTask; + m_udpTask = udpTask; +} + +void RlsControlTask::onStart() +{ + setTimer(TIMER_ID_ACK_CONTROL, TIMER_PERIOD_ACK_CONTROL); + setTimer(TIMER_ID_ACK_SEND, TIMER_PERIOD_ACK_SEND); +} + +void RlsControlTask::onLoop() +{ + NtsMessage *msg = take(); + if (!msg) + return; + + switch (msg->msgType) + { + case NtsMessageType::GNB_RLS_TO_RLS: { + auto *w = dynamic_cast(msg); + switch (w->present) + { + case NmGnbRlsToRls::SIGNAL_DETECTED: + handleSignalDetected(w->ueId); + break; + case NmGnbRlsToRls::SIGNAL_LOST: + handleSignalLost(w->ueId); + break; + case NmGnbRlsToRls::RECEIVE_RLS_MESSAGE: + handleRlsMessage(w->ueId, *w->msg); + break; + case NmGnbRlsToRls::DOWNLINK_DATA: + handleDownlinkDataDelivery(w->ueId, w->psi, std::move(w->data)); + break; + case NmGnbRlsToRls::DOWNLINK_RRC: + handleDownlinkRrcDelivery(w->ueId, w->pduId, w->rrcChannel, std::move(w->data)); + break; + default: + m_logger->unhandledNts(msg); + break; + } + break; + } + case NtsMessageType::TIMER_EXPIRED: { + auto *w = dynamic_cast(msg); + if (w->timerId == TIMER_ID_ACK_CONTROL) + { + setTimer(TIMER_ID_ACK_CONTROL, TIMER_PERIOD_ACK_CONTROL); + onAckControlTimerExpired(); + } + else if (w->timerId == TIMER_ID_ACK_SEND) + { + setTimer(TIMER_ID_ACK_SEND, TIMER_PERIOD_ACK_SEND); + onAckSendTimerExpired(); + } + break; + } + default: + m_logger->unhandledNts(msg); + break; + } + + delete msg; +} + +void RlsControlTask::onQuit() +{ +} + +void RlsControlTask::handleSignalDetected(int ueId) +{ + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::SIGNAL_DETECTED); + w->ueId = ueId; + m_mainTask->push(w); +} + +void RlsControlTask::handleSignalLost(int ueId) +{ + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::SIGNAL_LOST); + w->ueId = ueId; + m_mainTask->push(w); +} + +void RlsControlTask::handleRlsMessage(int ueId, rls::RlsMessage &msg) +{ + if (msg.msgType == rls::EMessageType::PDU_TRANSMISSION_ACK) + { + auto &m = (rls::RlsPduTransmissionAck &)msg; + for (auto pduId : m.pduIds) + m_pduMap.erase(pduId); + } + else if (msg.msgType == rls::EMessageType::PDU_TRANSMISSION) + { + auto &m = (rls::RlsPduTransmission &)msg; + if (m.pduId != 0) + m_pendingAck[ueId].push_back(m.pduId); + + if (m.pduType == rls::EPduType::DATA) + { + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::UPLINK_DATA); + w->ueId = ueId; + w->psi = static_cast(m.payload); + w->data = std::move(m.pdu); + m_mainTask->push(w); + } + else if (m.pduType == rls::EPduType::RRC) + { + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::UPLINK_RRC); + w->ueId = ueId; + w->rrcChannel = static_cast(m.payload); + w->data = std::move(m.pdu); + m_mainTask->push(w); + } + else + { + m_logger->err("Unhandled RLS PDU type"); + } + } + else + { + m_logger->err("Unhandled RLS message type"); + } +} + +void RlsControlTask::handleDownlinkRrcDelivery(int ueId, uint32_t pduId, rrc::RrcChannel channel, OctetString &&data) +{ + if (ueId == 0 && pduId != 0) + { + // PDU ID must be not set in case of broadcast + throw std::runtime_error(""); + } + + if (pduId != 0) + { + if (m_pduMap.count(pduId)) + { + m_pduMap.clear(); + + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::RADIO_LINK_FAILURE); + w->rlfCause = rls::ERlfCause::PDU_ID_EXISTS; + m_mainTask->push(w); + return; + } + + if (m_pduMap.size() > MAX_PDU_COUNT) + { + m_pduMap.clear(); + + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::RADIO_LINK_FAILURE); + w->rlfCause = rls::ERlfCause::PDU_ID_FULL; + m_mainTask->push(w); + return; + } + + m_pduMap[pduId].endPointId = ueId; + m_pduMap[pduId].id = pduId; + m_pduMap[pduId].pdu = data.copy(); + m_pduMap[pduId].rrcChannel = channel; + m_pduMap[pduId].sentTime = utils::CurrentTimeMillis(); + } + + rls::RlsPduTransmission msg{m_sti}; + msg.pduType = rls::EPduType::RRC; + msg.pdu = std::move(data); + msg.payload = static_cast(channel); + msg.pduId = pduId; + + m_udpTask->send(ueId, msg); +} + +void RlsControlTask::handleDownlinkDataDelivery(int ueId, int psi, OctetString &&data) +{ + rls::RlsPduTransmission msg{m_sti}; + msg.pduType = rls::EPduType::DATA; + msg.pdu = std::move(data); + msg.payload = static_cast(psi); + msg.pduId = 0; + + m_udpTask->send(ueId, msg); +} + +void RlsControlTask::onAckControlTimerExpired() +{ + int64_t current = utils::CurrentTimeMillis(); + + std::vector transmissionFailureIds; + std::vector transmissionFailures; + + for (auto &pdu : m_pduMap) + { + auto delta = current - pdu.second.sentTime; + if (delta > MAX_PDU_TTL) + { + transmissionFailureIds.push_back(pdu.first); + transmissionFailures.push_back(std::move(pdu.second)); + } + } + + for (auto id : transmissionFailureIds) + m_pduMap.erase(id); + + if (!transmissionFailures.empty()) + { + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::TRANSMISSION_FAILURE); + w->pduList = std::move(transmissionFailures); + m_mainTask->push(w); + } +} + +void RlsControlTask::onAckSendTimerExpired() +{ + auto copy = m_pendingAck; + m_pendingAck.clear(); + + for (auto &item : copy) + { + if (!item.second.empty()) + continue; + + rls::RlsPduTransmissionAck msg{m_sti}; + msg.pduIds = std::move(item.second); + + m_udpTask->send(item.first, msg); + } +} + +} // namespace nr::gnb diff --git a/src/gnb/rls/ctl_task.hpp b/src/gnb/rls/ctl_task.hpp new file mode 100644 index 000000000..1f8a01096 --- /dev/null +++ b/src/gnb/rls/ctl_task.hpp @@ -0,0 +1,52 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include "udp_task.hpp" + +#include +#include +#include + +namespace nr::gnb +{ + +class RlsControlTask : public NtsTask +{ + private: + std::unique_ptr m_logger; + uint64_t m_sti; + NtsTask *m_mainTask; + RlsUdpTask *m_udpTask; + std::unordered_map m_pduMap; + std::unordered_map> m_pendingAck; + + public: + explicit RlsControlTask(TaskBase *base, uint64_t sti); + ~RlsControlTask() override = default; + + protected: + void onStart() override; + void onLoop() override; + void onQuit() override; + + public: + void initialize(NtsTask *mainTask, RlsUdpTask *udpTask); + + private: + void handleSignalDetected(int ueId); + void handleSignalLost(int ueId); + void handleRlsMessage(int ueId, rls::RlsMessage &msg); + void handleDownlinkRrcDelivery(int ueId, uint32_t pduId, rrc::RrcChannel channel, OctetString &&data); + void handleDownlinkDataDelivery(int ueId, int psi, OctetString &&data); + void onAckControlTimerExpired(); + void onAckSendTimerExpired(); +}; + +} // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/rls/handler.cpp b/src/gnb/rls/handler.cpp deleted file mode 100644 index 527d2be9a..000000000 --- a/src/gnb/rls/handler.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// -// This file is a part of UERANSIM open source project. -// Copyright (c) 2021 ALİ GÜNGÖR. -// -// The software and all associated files are licensed under GPL-3.0 -// and subject to the terms and conditions defined in LICENSE file. -// - -#include "task.hpp" - -#include - -#include -#include - -static int MIN_ALLOWED_DBM = -120; - -static int EstimateSimulatedDbm(const Vector3 &myPos, const Vector3 &uePos) -{ - int deltaX = myPos.x - uePos.x; - int deltaY = myPos.y - uePos.y; - int deltaZ = myPos.z - uePos.z; - - int distance = static_cast(std::sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ)); - if (distance == 0) - return -1; // 0 may be confusing for people - return -distance; -} - -namespace nr::gnb -{ - -void GnbRlsTask::handleCellInfoRequest(int ueId, const rls::RlsCellInfoRequest &msg) -{ - int dbm = EstimateSimulatedDbm(m_base->config->phyLocation, msg.simPos); - if (dbm < MIN_ALLOWED_DBM) - { - // if the simulated signal strength is such low, then do not send a response to this message - return; - } - - rls::RlsCellInfoResponse resp{m_sti}; - resp.cellId.nci = m_base->config->nci; - resp.cellId.plmn = m_base->config->plmn; - resp.tac = m_base->config->tac; - resp.dbm = dbm; - resp.gnbName = m_base->config->name; - resp.linkIp = m_base->config->portalIp; - - sendRlsMessage(ueId, resp); -} - -void GnbRlsTask::handleUplinkPduDelivery(int ueId, rls::RlsPduDelivery &msg) -{ - if (msg.pduType == rls::EPduType::RRC) - { - auto *nw = new NwGnbRlsToRrc(NwGnbRlsToRrc::RRC_PDU_DELIVERY); - nw->ueId = ueId; - nw->channel = static_cast(msg.payload.get4I(0)); - nw->pdu = std::move(msg.pdu); - m_base->rrcTask->push(nw); - } - else if (msg.pduType == rls::EPduType::DATA) - { - auto *nw = new NwGnbRlsToGtp(NwGnbRlsToGtp::DATA_PDU_DELIVERY); - nw->ueId = ueId; - nw->psi = msg.payload.get4I(0); - nw->pdu = std::move(msg.pdu); - m_base->gtpTask->push(nw); - } -} - -void GnbRlsTask::handleDownlinkDelivery(int ueId, rls::EPduType pduType, OctetString &&pdu, OctetString &&payload) -{ - rls::RlsPduDelivery resp{m_sti}; - resp.pduType = pduType; - resp.pdu = std::move(pdu); - resp.payload = std::move(payload); - - if (ueId != 0) - { - sendRlsMessage(ueId, resp); - } - else - { - for (auto &ue : m_ueCtx) - sendRlsMessage(ue.first, resp); - } -} - -} // namespace nr::gnb diff --git a/src/gnb/rls/management.cpp b/src/gnb/rls/management.cpp deleted file mode 100644 index fd64fa772..000000000 --- a/src/gnb/rls/management.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// -// This file is a part of UERANSIM open source project. -// Copyright (c) 2021 ALİ GÜNGÖR. -// -// The software and all associated files are licensed under GPL-3.0 -// and subject to the terms and conditions defined in LICENSE file. -// - -#include "task.hpp" - -#include - -#include -#include - -static const int64_t LAST_SEEN_THRESHOLD = 3000; - -namespace nr::gnb -{ - -int GnbRlsTask::updateUeInfo(const InetAddress &addr, uint64_t sti) -{ - if (m_stiToUeId.count(sti)) - { - int ueId = m_stiToUeId[sti]; - auto &ctx = m_ueCtx[ueId]; - ctx->addr = addr; - ctx->lastSeen = utils::CurrentTimeMillis(); - return ueId; - } - else - { - int ueId = ++m_ueIdCounter; - m_stiToUeId[sti] = ueId; - auto ctx = std::make_unique(ueId); - ctx->sti = sti; - ctx->addr = addr; - ctx->lastSeen = utils::CurrentTimeMillis(); - m_ueCtx[ueId] = std::move(ctx); - - m_logger->debug("New UE signal detected, total [%d] UEs in coverage", static_cast(m_stiToUeId.size())); - return ueId; - } -} - -void GnbRlsTask::onPeriodicLostControl() -{ - int64_t current = utils::CurrentTimeMillis(); - - std::set lostUeId{}; - std::set lostSti{}; - - for (auto &item : m_ueCtx) - { - if (current - item.second->lastSeen > LAST_SEEN_THRESHOLD) - { - lostUeId.insert(item.second->ueId); - lostSti.insert(item.second->sti); - } - } - - for (uint64_t sti : lostSti) - m_stiToUeId.erase(sti); - for (int ueId : lostUeId) - { - m_ueCtx.erase(ueId); - m_logger->debug("Signal lost detected for UE[%d]", ueId); - - auto *w = new NwGnbRlsToRrc(NwGnbRlsToRrc::SIGNAL_LOST); - w->ueId = ueId; - m_base->rrcTask->push(w); - } -} - -} // namespace nr::gnb diff --git a/src/gnb/rls/task.cpp b/src/gnb/rls/task.cpp index dcb1e6eef..6f699b81a 100644 --- a/src/gnb/rls/task.cpp +++ b/src/gnb/rls/task.cpp @@ -9,40 +9,28 @@ #include "task.hpp" #include -#include #include #include -#include -#include - -static const int TIMER_ID_LOST_CONTROL = 1; -static const int TIMER_PERIOD_LOST_CONTROL = 2000; namespace nr::gnb { -GnbRlsTask::GnbRlsTask(TaskBase *base) - : m_base{base}, m_udpTask{}, m_powerOn{}, m_ueCtx{}, m_stiToUeId{}, m_ueIdCounter{} +GnbRlsTask::GnbRlsTask(TaskBase *base) : m_base{base} { m_logger = m_base->logBase->makeUniqueLogger("rls"); m_sti = utils::Random64(); + + m_udpTask = new RlsUdpTask(base, m_sti, base->config->phyLocation); + m_ctlTask = new RlsControlTask(base, m_sti); + + m_udpTask->initialize(m_ctlTask); + m_ctlTask->initialize(this, m_udpTask); } void GnbRlsTask::onStart() { - try - { - m_udpTask = new udp::UdpServerTask(m_base->config->portalIp, cons::PortalPort, this); - m_udpTask->start(); - } - catch (const LibError &e) - { - m_logger->err("RLS failure [%s]", e.what()); - quit(); - return; - } - - setTimer(TIMER_ID_LOST_CONTROL, TIMER_PERIOD_LOST_CONTROL); + m_udpTask->start(); + m_ctlTask->start(); } void GnbRlsTask::onLoop() @@ -53,51 +41,79 @@ void GnbRlsTask::onLoop() switch (msg->msgType) { - case NtsMessageType::GNB_RRC_TO_RLS: { - auto *w = dynamic_cast(msg); + case NtsMessageType::GNB_RLS_TO_RLS: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbRrcToRls::RRC_PDU_DELIVERY: { - handleDownlinkDelivery(w->ueId, rls::EPduType::RRC, std::move(w->pdu), - OctetString::FromOctet4(static_cast(w->channel))); + case NmGnbRlsToRls::SIGNAL_DETECTED: { + auto *m = new NmGnbRlsToRrc(NmGnbRlsToRrc::SIGNAL_DETECTED); + m->ueId = w->ueId; + m_base->rrcTask->push(m); break; } - case NwGnbRrcToRls::RADIO_POWER_ON: { - m_powerOn = true; + case NmGnbRlsToRls::SIGNAL_LOST: { + m_logger->debug("UE[%d] signal lost", w->ueId); + break; + } + case NmGnbRlsToRls::UPLINK_DATA: { + auto *m = new NmGnbRlsToGtp(NmGnbRlsToGtp::DATA_PDU_DELIVERY); + m->ueId = w->ueId; + m->psi = w->psi; + m->pdu = std::move(w->data); + m_base->gtpTask->push(m); + break; + } + case NmGnbRlsToRls::UPLINK_RRC: { + auto *m = new NmGnbRlsToRrc(NmGnbRlsToRrc::UPLINK_RRC); + m->ueId = w->ueId; + m->rrcChannel = w->rrcChannel; + m->data = std::move(w->data); + m_base->rrcTask->push(m); + break; + } + case NmGnbRlsToRls::RADIO_LINK_FAILURE: { + m_logger->debug("radio link failure [%d]", (int)w->rlfCause); + break; + } + case NmGnbRlsToRls::TRANSMISSION_FAILURE: { + m_logger->debug("transmission failure [%s]", ""); + break; + } + default: { + m_logger->unhandledNts(msg); break; } } break; } - case NtsMessageType::GNB_GTP_TO_RLS: { - auto *w = dynamic_cast(msg); + case NtsMessageType::GNB_RRC_TO_RLS: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbGtpToRls::DATA_PDU_DELIVERY: { - handleDownlinkDelivery(w->ueId, rls::EPduType::DATA, std::move(w->pdu), - OctetString::FromOctet4(static_cast(w->psi))); + case NmGnbRrcToRls::RRC_PDU_DELIVERY: { + auto *m = new NmGnbRlsToRls(NmGnbRlsToRls::DOWNLINK_RRC); + m->ueId = w->ueId; + m->rrcChannel = w->channel; + m->pduId = 0; + m->data = std::move(w->pdu); + m_ctlTask->push(m); break; } } break; } - case NtsMessageType::UDP_SERVER_RECEIVE: { - auto *w = dynamic_cast(msg); - auto rlsMsg = rls::DecodeRlsMessage(OctetView{w->packet}); - if (rlsMsg == nullptr) + case NtsMessageType::GNB_GTP_TO_RLS: { + auto *w = dynamic_cast(msg); + switch (w->present) { - m_logger->err("Unable to decode RLS message"); + case NmGnbGtpToRls::DATA_PDU_DELIVERY: { + auto *m = new NmGnbRlsToRls(NmGnbRlsToRls::DOWNLINK_DATA); + m->ueId = w->ueId; + m->psi = w->psi; + m->data = std::move(w->pdu); + m_ctlTask->push(m); break; } - receiveRlsMessage(w->fromAddress, *rlsMsg); - break; - } - case NtsMessageType::TIMER_EXPIRED: { - auto *w = dynamic_cast(msg); - if (w->timerId == TIMER_ID_LOST_CONTROL) - { - setTimer(TIMER_ID_LOST_CONTROL, TIMER_PERIOD_LOST_CONTROL); - onPeriodicLostControl(); } break; } @@ -111,9 +127,10 @@ void GnbRlsTask::onLoop() void GnbRlsTask::onQuit() { - if (m_udpTask != nullptr) - m_udpTask->quit(); + m_udpTask->quit(); + m_ctlTask->quit(); delete m_udpTask; + delete m_ctlTask; } } // namespace nr::gnb diff --git a/src/gnb/rls/task.hpp b/src/gnb/rls/task.hpp index 8ddaa14ce..969c89338 100644 --- a/src/gnb/rls/task.hpp +++ b/src/gnb/rls/task.hpp @@ -8,6 +8,9 @@ #pragma once +#include "ctl_task.hpp" +#include "udp_task.hpp" + #include #include #include @@ -28,13 +31,11 @@ class GnbRlsTask : public NtsTask private: TaskBase *m_base; std::unique_ptr m_logger; - udp::UdpServerTask *m_udpTask; - bool m_powerOn; + RlsUdpTask *m_udpTask; + RlsControlTask *m_ctlTask; + uint64_t m_sti; - std::unordered_map> m_ueCtx; - std::unordered_map m_stiToUeId; - int m_ueIdCounter; friend class GnbCmdHandler; @@ -46,19 +47,6 @@ class GnbRlsTask : public NtsTask void onStart() override; void onLoop() override; void onQuit() override; - - private: /* Transport */ - void receiveRlsMessage(const InetAddress &addr, rls::RlsMessage &msg); - void sendRlsMessage(int ueId, const rls::RlsMessage &msg); - - private: /* Handler */ - void handleCellInfoRequest(int ueId, const rls::RlsCellInfoRequest &msg); - void handleUplinkPduDelivery(int ueId, rls::RlsPduDelivery &msg); - void handleDownlinkDelivery(int ueId, rls::EPduType pduType, OctetString &&pdu, OctetString &&payload); - - private: /* UE Management */ - int updateUeInfo(const InetAddress &addr, uint64_t sti); - void onPeriodicLostControl(); }; -} // namespace nr::gnb \ No newline at end of file +} // namespace nr::gnb diff --git a/src/gnb/rls/transport.cpp b/src/gnb/rls/transport.cpp deleted file mode 100644 index 43eeaaa97..000000000 --- a/src/gnb/rls/transport.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// This file is a part of UERANSIM open source project. -// Copyright (c) 2021 ALİ GÜNGÖR. -// -// The software and all associated files are licensed under GPL-3.0 -// and subject to the terms and conditions defined in LICENSE file. -// - -#include "task.hpp" - -namespace nr::gnb -{ - -void GnbRlsTask::receiveRlsMessage(const InetAddress &addr, rls::RlsMessage &msg) -{ - if (!m_powerOn) - { - // ignore received RLS message - return; - } - - int ueId = updateUeInfo(addr, msg.sti); - - switch (msg.msgType) - { - case rls::EMessageType::CELL_INFO_REQUEST: { - handleCellInfoRequest(ueId, (const rls::RlsCellInfoRequest &)msg); - break; - } - case rls::EMessageType::PDU_DELIVERY: { - handleUplinkPduDelivery(ueId, (rls::RlsPduDelivery &)msg); - break; - } - default: - m_logger->err("Unhandled RLS message received with type[%d]", static_cast(msg.msgType)); - break; - } -} - -void GnbRlsTask::sendRlsMessage(int ueId, const rls::RlsMessage &msg) -{ - if (!m_ueCtx.count(ueId)) - { - m_logger->err("RLS message sending failure, UE[%d] not exists", ueId); - return; - } - - OctetString stream{}; - rls::EncodeRlsMessage(msg, stream); - m_udpTask->send(m_ueCtx[ueId]->addr, stream); -} - -} // namespace nr::gnb diff --git a/src/gnb/rls/udp_task.cpp b/src/gnb/rls/udp_task.cpp new file mode 100644 index 000000000..fbd83933f --- /dev/null +++ b/src/gnb/rls/udp_task.cpp @@ -0,0 +1,201 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "udp_task.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +static constexpr const int BUFFER_SIZE = 16384; + +static constexpr const int LOOP_PERIOD = 1000; +static constexpr const int RECEIVE_TIMEOUT = 200; +static constexpr const int HEARTBEAT_THRESHOLD = 2000; // (LOOP_PERIOD + RECEIVE_TIMEOUT)'dan büyük olmalı + +static constexpr const int MIN_ALLOWED_DBM = -120; + +static int EstimateSimulatedDbm(const Vector3 &myPos, const Vector3 &uePos) +{ + int deltaX = myPos.x - uePos.x; + int deltaY = myPos.y - uePos.y; + int deltaZ = myPos.z - uePos.z; + + int distance = static_cast(std::sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ)); + if (distance == 0) + return -1; // 0 may be confusing for people + return -distance; +} + +namespace nr::gnb +{ + +RlsUdpTask::RlsUdpTask(TaskBase *base, uint64_t sti, Vector3 phyLocation) + : m_server{}, m_ctlTask{}, m_sti{sti}, m_phyLocation{phyLocation}, m_lastLoop{}, m_stiToUe{}, m_ueMap{}, m_newIdCounter{} +{ + m_logger = base->logBase->makeUniqueLogger("rls-udp"); + + try + { + m_server = new udp::UdpServer(base->config->portalIp, cons::PortalPort); + } + catch (const LibError &e) + { + m_logger->err("RLS failure [%s]", e.what()); + quit(); + return; + } +} + +void RlsUdpTask::onStart() +{ +} + +void RlsUdpTask::onLoop() +{ + auto current = utils::CurrentTimeMillis(); + if (current - m_lastLoop > LOOP_PERIOD) + { + m_lastLoop = current; + heartbeatCycle(current); + } + + uint8_t buffer[BUFFER_SIZE]; + InetAddress peerAddress; + + int size = m_server->Receive(buffer, BUFFER_SIZE, RECEIVE_TIMEOUT, peerAddress); + if (size > 0) + { + auto rlsMsg = rls::DecodeRlsMessage(OctetView{buffer, static_cast(size)}); + if (rlsMsg == nullptr) + m_logger->err("Unable to decode RLS message"); + else + receiveRlsPdu(peerAddress, std::move(rlsMsg)); + } +} + +void RlsUdpTask::onQuit() +{ + delete m_server; +} + +void RlsUdpTask::receiveRlsPdu(const InetAddress &addr, std::unique_ptr &&msg) +{ + if (msg->msgType == rls::EMessageType::HEARTBEAT) + { + int dbm = EstimateSimulatedDbm(m_phyLocation, ((const rls::RlsHeartBeat &)*msg).simPos); + if (dbm < MIN_ALLOWED_DBM) + { + // if the simulated signal strength is such low, then ignore this message + return; + } + + if (m_stiToUe.count(msg->sti)) + { + int ueId = m_stiToUe[msg->sti]; + m_ueMap[ueId].address = addr; + m_ueMap[ueId].lastSeen = utils::CurrentTimeMillis(); + } + else + { + int ueId = ++m_newIdCounter; + + m_stiToUe[msg->sti] = ueId; + m_ueMap[ueId].address = addr; + m_ueMap[ueId].lastSeen = utils::CurrentTimeMillis(); + + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::SIGNAL_DETECTED); + w->ueId = ueId; + m_ctlTask->push(w); + } + + rls::RlsHeartBeatAck ack{m_sti}; + ack.dbm = dbm; + + sendRlsPdu(addr, ack); + return; + } + + if (!m_stiToUe.count(msg->sti)) + { + // if no HB received yet, and the message is not HB, then ignore the message + return; + } + + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::RECEIVE_RLS_MESSAGE); + w->ueId = m_stiToUe[msg->sti]; + w->msg = std::move(msg); + m_ctlTask->push(w); +} + +void RlsUdpTask::sendRlsPdu(const InetAddress &addr, const rls::RlsMessage &msg) +{ + OctetString stream; + rls::EncodeRlsMessage(msg, stream); + + m_server->Send(addr, stream.data(), static_cast(stream.length())); +} + +void RlsUdpTask::heartbeatCycle(int64_t time) +{ + std::set lostUeId{}; + std::set lostSti{}; + + for (auto &item : m_ueMap) + { + if (time - item.second.lastSeen > HEARTBEAT_THRESHOLD) + { + lostUeId.insert(item.first); + lostSti.insert(item.second.sti); + } + } + + for (uint64_t sti : lostSti) + m_stiToUe.erase(sti); + + for (int ueId : lostUeId) + m_ueMap.erase(ueId); + + for (int ueId : lostUeId) + { + auto *w = new NmGnbRlsToRls(NmGnbRlsToRls::SIGNAL_LOST); + w->ueId = ueId; + m_ctlTask->push(w); + } +} + +void RlsUdpTask::initialize(NtsTask *ctlTask) +{ + m_ctlTask = ctlTask; +} + +void RlsUdpTask::send(int ueId, const rls::RlsMessage &msg) +{ + if (ueId == 0) + { + for (auto &ue : m_ueMap) + send(ue.first, msg); + return; + } + + if (!m_ueMap.count(ueId)) + { + // ignore the message + return; + } + + sendRlsPdu(m_ueMap[ueId].address, msg); +} + +} // namespace nr::gnb diff --git a/src/gnb/rls/udp_task.hpp b/src/gnb/rls/udp_task.hpp new file mode 100644 index 000000000..5bcd41795 --- /dev/null +++ b/src/gnb/rls/udp_task.hpp @@ -0,0 +1,63 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace nr::gnb +{ + +class RlsUdpTask : public NtsTask +{ + private: + struct UeInfo + { + uint64_t sti{}; + InetAddress address; + int64_t lastSeen{}; + }; + + private: + std::unique_ptr m_logger; + udp::UdpServer *m_server; + NtsTask *m_ctlTask; + uint64_t m_sti; + Vector3 m_phyLocation; + int64_t m_lastLoop; + std::unordered_map m_stiToUe; + std::unordered_map m_ueMap; + int m_newIdCounter; + + public: + explicit RlsUdpTask(TaskBase *base, uint64_t sti, Vector3 phyLocation); + ~RlsUdpTask() override = default; + + protected: + void onStart() override; + void onLoop() override; + void onQuit() override; + + private: + void receiveRlsPdu(const InetAddress &addr, std::unique_ptr &&msg); + void sendRlsPdu(const InetAddress &addr, const rls::RlsMessage &msg); + void heartbeatCycle(int64_t time); + + public: + void initialize(NtsTask *ctlTask); + void send(int ueId, const rls::RlsMessage &msg); +}; + +} // namespace nr::gnb diff --git a/src/gnb/rrc/broadcast.cpp b/src/gnb/rrc/broadcast.cpp new file mode 100644 index 000000000..3a24d6804 --- /dev/null +++ b/src/gnb/rrc/broadcast.cpp @@ -0,0 +1,122 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nr::gnb +{ + +static ASN_RRC_BCCH_BCH_Message *ConstructMibMessage(bool barred, bool intraFreqReselectAllowed) +{ + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_BCCH_BCH_MessageType_PR_mib; + pdu->message.choice.mib = asn::New(); + + auto &mib = *pdu->message.choice.mib; + + asn::SetBitStringInt<6>(0, mib.systemFrameNumber); + mib.subCarrierSpacingCommon = ASN_RRC_MIB__subCarrierSpacingCommon_scs15or60; + mib.ssb_SubcarrierOffset = 0; + mib.dmrs_TypeA_Position = ASN_RRC_MIB__dmrs_TypeA_Position_pos2; + mib.cellBarred = barred ? ASN_RRC_MIB__cellBarred_barred : ASN_RRC_MIB__cellBarred_notBarred; + mib.intraFreqReselection = intraFreqReselectAllowed ? ASN_RRC_MIB__intraFreqReselection_allowed + : ASN_RRC_MIB__intraFreqReselection_notAllowed; + asn::SetBitStringInt<1>(0, mib.spare); + mib.pdcch_ConfigSIB1.controlResourceSetZero = 0; + mib.pdcch_ConfigSIB1.searchSpaceZero = 0; + return pdu; +} + +static ASN_RRC_BCCH_DL_SCH_Message *ConstructSib1Message(bool cellReserved, int tac, int64_t nci, const Plmn &plmn, + const UacAiBarringSet &aiBarringSet) +{ + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_BCCH_DL_SCH_MessageType_PR_c1; + pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); + pdu->message.choice.c1->present = ASN_RRC_BCCH_DL_SCH_MessageType__c1_PR_systemInformationBlockType1; + pdu->message.choice.c1->choice.systemInformationBlockType1 = asn::New(); + + auto &sib1 = *pdu->message.choice.c1->choice.systemInformationBlockType1; + + if (cellReserved) + { + asn::MakeNew(sib1.cellAccessRelatedInfo.cellReservedForOtherUse); + *sib1.cellAccessRelatedInfo.cellReservedForOtherUse = + ASN_RRC_CellAccessRelatedInfo__cellReservedForOtherUse_true; + } + + auto *plmnInfo = asn::New(); + plmnInfo->cellReservedForOperatorUse = cellReserved + ? ASN_RRC_PLMN_IdentityInfo__cellReservedForOperatorUse_reserved + : ASN_RRC_PLMN_IdentityInfo__cellReservedForOperatorUse_notReserved; + asn::MakeNew(plmnInfo->trackingAreaCode); + asn::SetBitStringInt<24>(tac, *plmnInfo->trackingAreaCode); + asn::SetBitStringLong<36>(nci, plmnInfo->cellIdentity); + asn::SequenceAdd(plmnInfo->plmn_IdentityList, asn::rrc::NewPlmnId(plmn)); + asn::SequenceAdd(sib1.cellAccessRelatedInfo.plmn_IdentityList, plmnInfo); + + asn::MakeNew(sib1.uac_BarringInfo); + + auto *info = asn::New(); + info->uac_BarringFactor = ASN_RRC_UAC_BarringInfoSet__uac_BarringFactor_p50; + info->uac_BarringTime = ASN_RRC_UAC_BarringInfoSet__uac_BarringTime_s4; + + asn::SetBitStringInt<7>(bits::Consequential8(false, aiBarringSet.ai1, aiBarringSet.ai2, aiBarringSet.ai11, + aiBarringSet.ai12, aiBarringSet.ai13, aiBarringSet.ai14, + aiBarringSet.ai15), + info->uac_BarringForAccessIdentity); + + asn::SequenceAdd(sib1.uac_BarringInfo->uac_BarringInfoSetList, info); + + asn::MakeNew(sib1.uac_BarringInfo->uac_BarringForCommon); + + for (size_t i = 0; i < 63; i++) + { + auto *item = asn::New(); + item->accessCategory = static_cast(i + 1); + item->uac_barringInfoSetIndex = 1; + + asn::SequenceAdd(*sib1.uac_BarringInfo->uac_BarringForCommon, item); + } + + return pdu; +} + +void GnbRrcTask::onBroadcastTimerExpired() +{ + triggerSysInfoBroadcast(); +} + +void GnbRrcTask::triggerSysInfoBroadcast() +{ + auto *mib = ConstructMibMessage(m_isBarred, m_intraFreqReselectAllowed); + auto *sib1 = ConstructSib1Message(m_cellReserved, m_config->tac, m_config->nci, m_config->plmn, m_aiBarringSet); + + sendRrcMessage(mib); + sendRrcMessage(sib1); + + asn::Free(asn_DEF_ASN_RRC_BCCH_BCH_Message, mib); + asn::Free(asn_DEF_ASN_RRC_BCCH_DL_SCH_Message, sib1); +} + +} // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/rrc/channel.cpp b/src/gnb/rrc/channel.cpp index cf6c0635c..fc89756dc 100644 --- a/src/gnb/rrc/channel.cpp +++ b/src/gnb/rrc/channel.cpp @@ -65,7 +65,7 @@ void GnbRrcTask::handleUplinkRrc(int ueId, rrc::RrcChannel channel, const OctetS } } -void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg) +void GnbRrcTask::sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg) { OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_BCCH_BCH_Message, msg); if (pdu.length() == 0) @@ -74,14 +74,14 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg) return; } - auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); - w->ueId = ueId; + auto *w = new NmGnbRrcToRls(NmGnbRrcToRls::RRC_PDU_DELIVERY); + w->ueId = 0; w->channel = rrc::RrcChannel::BCCH_BCH; w->pdu = std::move(pdu); m_base->rlsTask->push(w); } -void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_BCCH_DL_SCH_Message *msg) +void GnbRrcTask::sendRrcMessage(ASN_RRC_BCCH_DL_SCH_Message *msg) { OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_BCCH_DL_SCH_Message, msg); if (pdu.length() == 0) @@ -90,8 +90,8 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_BCCH_DL_SCH_Message *msg) return; } - auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); - w->ueId = ueId; + auto *w = new NmGnbRrcToRls(NmGnbRrcToRls::RRC_PDU_DELIVERY); + w->ueId = 0; w->channel = rrc::RrcChannel::BCCH_DL_SCH; w->pdu = std::move(pdu); m_base->rlsTask->push(w); @@ -106,7 +106,7 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg) return; } - auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); + auto *w = new NmGnbRrcToRls(NmGnbRrcToRls::RRC_PDU_DELIVERY); w->ueId = ueId; w->channel = rrc::RrcChannel::DL_CCCH; w->pdu = std::move(pdu); @@ -122,7 +122,7 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg) return; } - auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); + auto *w = new NmGnbRrcToRls(NmGnbRrcToRls::RRC_PDU_DELIVERY); w->ueId = ueId; w->channel = rrc::RrcChannel::DL_DCCH; w->pdu = std::move(pdu); @@ -138,7 +138,7 @@ void GnbRrcTask::sendRrcMessage(ASN_RRC_PCCH_Message *msg) return; } - auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); + auto *w = new NmGnbRrcToRls(NmGnbRrcToRls::RRC_PDU_DELIVERY); w->ueId = 0; w->channel = rrc::RrcChannel::PCCH; w->pdu = std::move(pdu); diff --git a/src/gnb/rrc/connection.cpp b/src/gnb/rrc/connection.cpp new file mode 100644 index 000000000..e95c956c4 --- /dev/null +++ b/src/gnb/rrc/connection.cpp @@ -0,0 +1,110 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nr::gnb +{ + +void GnbRrcTask::receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg) +{ + auto *ue = tryFindUe(ueId); + if (ue) + { + // TODO: handle this more properly + m_logger->warn("Discarding RRC Setup Request, UE context already exists"); + return; + } + + if (msg.rrcSetupRequest.ue_Identity.present == ASN_RRC_InitialUE_Identity_PR_ng_5G_S_TMSI_Part1) + { + m_logger->err("RRC Setup Request with TMSI not implemented yet"); + return; + } + + if (msg.rrcSetupRequest.ue_Identity.present != ASN_RRC_InitialUE_Identity_PR_randomValue) + { + m_logger->err("Bad constructed RRC message ignored"); + return; + } + + int64_t initialRandomId = asn::GetBitStringLong<39>(msg.rrcSetupRequest.ue_Identity.choice.randomValue); + if (tryFindByInitialRandomId(initialRandomId) != nullptr) + { + m_logger->err("Initial random ID conflict [%ld], discarding RRC Setup Request", initialRandomId); + return; + } + + ue = createUe(ueId); + ue->initialRandomId = initialRandomId; + ue->establishmentCause = msg.rrcSetupRequest.establishmentCause; + + // Prepare RRC Setup + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_DL_CCCH_MessageType_PR_c1; + pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); + pdu->message.choice.c1->present = ASN_RRC_DL_CCCH_MessageType__c1_PR_rrcSetup; + auto &rrcSetup = pdu->message.choice.c1->choice.rrcSetup = asn::New(); + rrcSetup->rrc_TransactionIdentifier = getNextTid(); + rrcSetup->criticalExtensions.present = ASN_RRC_RRCSetup__criticalExtensions_PR_rrcSetup; + auto &rrcSetupIEs = rrcSetup->criticalExtensions.choice.rrcSetup = asn::New(); + + ASN_RRC_CellGroupConfig masterCellGroup{}; + masterCellGroup.cellGroupId = 0; + + asn::SetOctetString(rrcSetupIEs->masterCellGroup, + rrc::encode::EncodeS(asn_DEF_ASN_RRC_CellGroupConfig, &masterCellGroup)); + + m_logger->info("RRC Setup for UE[%d]", ueId); + sendRrcMessage(ueId, pdu); +} + +void GnbRrcTask::receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplete &msg) +{ + auto *ue = findUe(ueId); + if (!ue) + return; + + auto setupComplete = msg.criticalExtensions.choice.rrcSetupComplete; + + auto *w = new NmGnbRrcToNgap(NmGnbRrcToNgap::INITIAL_NAS_DELIVERY); + w->ueId = ueId; + w->pdu = asn::GetOctetString(setupComplete->dedicatedNAS_Message); + w->rrcEstablishmentCause = ue->establishmentCause; + m_base->ngapTask->push(w); +} + +} // namespace nr::gnb diff --git a/src/gnb/rrc/handler.cpp b/src/gnb/rrc/handler.cpp index 300e7b15c..04d42e6de 100644 --- a/src/gnb/rrc/handler.cpp +++ b/src/gnb/rrc/handler.cpp @@ -59,7 +59,7 @@ void GnbRrcTask::handleDownlinkNasDelivery(int ueId, const OctetString &nasPdu) void GnbRrcTask::deliverUplinkNas(int ueId, OctetString &&nasPdu) { - auto *w = new NwGnbRrcToNgap(NwGnbRrcToNgap::UPLINK_NAS_DELIVERY); + auto *w = new NmGnbRrcToNgap(NmGnbRrcToNgap::UPLINK_NAS_DELIVERY); w->ueId = ueId; w->pdu = std::move(nasPdu); m_base->ngapTask->push(w); @@ -72,76 +72,6 @@ void GnbRrcTask::receiveUplinkInformationTransfer(int ueId, const ASN_RRC_ULInfo ueId, asn::GetOctetString(*msg.criticalExtensions.choice.ulInformationTransfer->dedicatedNAS_Message)); } -void GnbRrcTask::receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg) -{ - auto *ue = tryFindUe(ueId); - if (ue) - { - m_logger->warn("Discarding RRC Setup Request, UE context already exists"); - return; - } - - if (msg.rrcSetupRequest.ue_Identity.present == ASN_RRC_InitialUE_Identity_PR_ng_5G_S_TMSI_Part1) - { - m_logger->err("RRC Setup Request with TMSI not implemented yet"); - return; - } - - if (msg.rrcSetupRequest.ue_Identity.present != ASN_RRC_InitialUE_Identity_PR_randomValue) - { - m_logger->err("Bad message"); - return; - } - - int64_t initialRandomId = asn::GetBitStringLong<39>(msg.rrcSetupRequest.ue_Identity.choice.randomValue); - if (tryFindByInitialRandomId(initialRandomId) != nullptr) - { - m_logger->err("Initial random ID conflict [%ld], discarding RRC Setup Request", initialRandomId); - return; - } - - ue = createUe(ueId); - ue->initialRandomId = initialRandomId; - ue->establishmentCause = msg.rrcSetupRequest.establishmentCause; - - // Prepare RRC Setup - auto *pdu = asn::New(); - pdu->message.present = ASN_RRC_DL_CCCH_MessageType_PR_c1; - pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); - pdu->message.choice.c1->present = ASN_RRC_DL_CCCH_MessageType__c1_PR_rrcSetup; - auto &rrcSetup = pdu->message.choice.c1->choice.rrcSetup = asn::New(); - rrcSetup->rrc_TransactionIdentifier = getNextTid(); - rrcSetup->criticalExtensions.present = ASN_RRC_RRCSetup__criticalExtensions_PR_rrcSetup; - auto &rrcSetupIEs = rrcSetup->criticalExtensions.choice.rrcSetup = asn::New(); - - ASN_RRC_CellGroupConfig masterCellGroup{}; - masterCellGroup.cellGroupId = 0; - - asn::SetOctetString(rrcSetupIEs->masterCellGroup, - rrc::encode::EncodeS(asn_DEF_ASN_RRC_CellGroupConfig, &masterCellGroup)); - - m_logger->info("RRC Setup for UE[%d]", ueId); - sendRrcMessage(ueId, pdu); -} - -void GnbRrcTask::receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplete &msg) -{ - if (msg.criticalExtensions.present != ASN_RRC_RRCSetupComplete__criticalExtensions_PR_rrcSetupComplete) - return; - - auto *ue = findUe(ueId); - if (!ue) - return; - - auto setupComplete = msg.criticalExtensions.choice.rrcSetupComplete; - - auto *w = new NwGnbRrcToNgap(NwGnbRrcToNgap::INITIAL_NAS_DELIVERY); - w->ueId = ueId; - w->pdu = asn::GetOctetString(setupComplete->dedicatedNAS_Message); - w->rrcEstablishmentCause = ue->establishmentCause; - m_base->ngapTask->push(w); -} - void GnbRrcTask::releaseConnection(int ueId) { m_logger->info("Releasing RRC connection for UE[%d]", ueId); @@ -165,7 +95,7 @@ void GnbRrcTask::releaseConnection(int ueId) void GnbRrcTask::handleRadioLinkFailure(int ueId) { // Notify NGAP task - auto *w = new NwGnbRrcToNgap(NwGnbRrcToNgap::RADIO_LINK_FAILURE); + auto *w = new NmGnbRrcToNgap(NmGnbRrcToNgap::RADIO_LINK_FAILURE); w->ueId = ueId; m_base->ngapTask->push(w); diff --git a/src/gnb/rrc/management.cpp b/src/gnb/rrc/management.cpp index 7740317e1..d6765725c 100644 --- a/src/gnb/rrc/management.cpp +++ b/src/gnb/rrc/management.cpp @@ -11,31 +11,6 @@ namespace nr::gnb { -RrcUeContext *GnbRrcTask::tryFindUe(int id) -{ - if (m_ueCtx.count(id)) - return m_ueCtx[id]; - return nullptr; -} - -RrcUeContext *GnbRrcTask::findUe(int id) -{ - auto *ue = tryFindUe(id); - if (ue == nullptr) - { - m_logger->err("UE context with ID[%d] not found", id); - return ue; - } - return ue; -} - -RrcUeContext *GnbRrcTask::createUe(int id) -{ - auto *ctx = new RrcUeContext(id); - m_ueCtx[id] = ctx; - return ctx; -} - RrcUeContext *GnbRrcTask::tryFindByInitialRandomId(int64_t id) { if (id == -1) diff --git a/src/gnb/rrc/sap.cpp b/src/gnb/rrc/sap.cpp new file mode 100644 index 000000000..c21b96802 --- /dev/null +++ b/src/gnb/rrc/sap.cpp @@ -0,0 +1,33 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include + +namespace nr::gnb +{ + +void GnbRrcTask::handleRlsSapMessage(NmGnbRlsToRrc &msg) +{ + switch (msg.present) + { + case NmGnbRlsToRrc::SIGNAL_DETECTED: { + m_logger->debug("UE[%d] new signal detected", msg.ueId); + triggerSysInfoBroadcast(); + break; + } + case NmGnbRlsToRrc::UPLINK_RRC: { + handleUplinkRrc(msg.ueId, msg.rrcChannel, msg.data); + break; + } + } +} + +} // namespace nr::gnb diff --git a/src/gnb/rrc/task.cpp b/src/gnb/rrc/task.cpp index 1ef21ba3f..e10837c12 100644 --- a/src/gnb/rrc/task.cpp +++ b/src/gnb/rrc/task.cpp @@ -15,16 +15,21 @@ #include #include +static constexpr const int TIMER_ID_SI_BROADCAST = 1; +static constexpr const int TIMER_PERIOD_SI_BROADCAST = 10'000; + namespace nr::gnb { GnbRrcTask::GnbRrcTask(TaskBase *base) : m_base{base}, m_ueCtx{}, m_tidCounter{} { m_logger = base->logBase->makeUniqueLogger("rrc"); + m_config = m_base->config; } void GnbRrcTask::onStart() { + setTimer(TIMER_ID_SI_BROADCAST, TIMER_PERIOD_SI_BROADCAST); } void GnbRrcTask::onQuit() @@ -41,42 +46,41 @@ void GnbRrcTask::onLoop() switch (msg->msgType) { case NtsMessageType::GNB_RLS_TO_RRC: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwGnbRlsToRrc::RRC_PDU_DELIVERY: { - handleUplinkRrc(w->ueId, w->channel, w->pdu); - break; - } - case NwGnbRlsToRrc::SIGNAL_LOST: { - handleRadioLinkFailure(w->ueId); - break; - } - } + handleRlsSapMessage(*dynamic_cast(msg)); break; } case NtsMessageType::GNB_NGAP_TO_RRC: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbNgapToRrc::RADIO_POWER_ON: { - m_base->rlsTask->push(new NwGnbRrcToRls(NwGnbRrcToRls::RADIO_POWER_ON)); + case NmGnbNgapToRrc::RADIO_POWER_ON: { + m_isBarred = false; + triggerSysInfoBroadcast(); break; } - case NwGnbNgapToRrc::NAS_DELIVERY: { + case NmGnbNgapToRrc::NAS_DELIVERY: { handleDownlinkNasDelivery(w->ueId, w->pdu); break; } - case NwGnbNgapToRrc::AN_RELEASE: { + case NmGnbNgapToRrc::AN_RELEASE: { releaseConnection(w->ueId); break; } - case NwGnbNgapToRrc::PAGING: + case NmGnbNgapToRrc::PAGING: handlePaging(w->uePagingTmsi, w->taiListForPaging); break; } break; } + case NtsMessageType::TIMER_EXPIRED: { + auto *w = dynamic_cast(msg); + if (w->timerId == TIMER_ID_SI_BROADCAST) + { + setTimer(TIMER_ID_SI_BROADCAST, TIMER_PERIOD_SI_BROADCAST); + onBroadcastTimerExpired(); + } + break; + } default: m_logger->unhandledNts(msg); break; diff --git a/src/gnb/rrc/task.hpp b/src/gnb/rrc/task.hpp index acc3aaae3..e33ca4c11 100644 --- a/src/gnb/rrc/task.hpp +++ b/src/gnb/rrc/task.hpp @@ -42,10 +42,17 @@ class GnbRrcTask : public NtsTask { private: TaskBase *m_base; + GnbConfig *m_config; std::unique_ptr m_logger; + std::unordered_map m_ueCtx; int m_tidCounter; + bool m_isBarred = true; + bool m_cellReserved = false; + UacAiBarringSet m_aiBarringSet = {}; + bool m_intraFreqReselectAllowed = true; + friend class GnbCmdHandler; public: @@ -59,9 +66,6 @@ class GnbRrcTask : public NtsTask private: /* Management */ - RrcUeContext *tryFindUe(int id); - RrcUeContext *findUe(int id); - RrcUeContext *createUe(int id); RrcUeContext *tryFindByInitialRandomId(int64_t id); int getNextTid(); @@ -75,12 +79,10 @@ class GnbRrcTask : public NtsTask const asn::Unique &taiList); void receiveUplinkInformationTransfer(int ueId, const ASN_RRC_ULInformationTransfer &msg); - void receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg); - void receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplete &msg); /* RRC channel send message */ - void sendRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg); - void sendRrcMessage(int ueId, ASN_RRC_BCCH_DL_SCH_Message *msg); + void sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg); + void sendRrcMessage(ASN_RRC_BCCH_DL_SCH_Message *msg); void sendRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg); void sendRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg); void sendRrcMessage(ASN_RRC_PCCH_Message *msg); @@ -90,6 +92,22 @@ class GnbRrcTask : public NtsTask void receiveRrcMessage(int ueId, ASN_RRC_UL_CCCH_Message *msg); void receiveRrcMessage(int ueId, ASN_RRC_UL_CCCH1_Message *msg); void receiveRrcMessage(int ueId, ASN_RRC_UL_DCCH_Message *msg); + + /* System Information Broadcast related */ + void onBroadcastTimerExpired(); + void triggerSysInfoBroadcast(); + + /* Service Access Point */ + void handleRlsSapMessage(NmGnbRlsToRrc &msg); + + /* UE Management */ + RrcUeContext *createUe(int id); + RrcUeContext *tryFindUe(int id); + RrcUeContext *findUe(int id); + + /* Connection Control */ + void receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg); + void receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplete &msg); }; } // namespace nr::gnb diff --git a/src/gnb/rrc/ues.cpp b/src/gnb/rrc/ues.cpp new file mode 100644 index 000000000..18a530c53 --- /dev/null +++ b/src/gnb/rrc/ues.cpp @@ -0,0 +1,42 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include + +namespace nr::gnb +{ + +RrcUeContext *GnbRrcTask::createUe(int id) +{ + auto *ctx = new RrcUeContext(id); + m_ueCtx[id] = ctx; + return ctx; +} + +RrcUeContext *GnbRrcTask::tryFindUe(int id) +{ + if (m_ueCtx.count(id)) + return m_ueCtx[id]; + return nullptr; +} + +RrcUeContext *GnbRrcTask::findUe(int id) +{ + auto *ue = tryFindUe(id); + if (ue == nullptr) + { + m_logger->err("UE context with ID[%d] not found", id); + return ue; + } + return ue; +} + +} // namespace nr::gnb diff --git a/src/gnb/sctp/task.cpp b/src/gnb/sctp/task.cpp index 5cffa5e4e..18b71526b 100644 --- a/src/gnb/sctp/task.cpp +++ b/src/gnb/sctp/task.cpp @@ -55,7 +55,7 @@ class SctpHandler : public sctp::ISctpHandler private: void onAssociationSetup(int associationId, int inStreams, int outStreams) override { - auto *w = new NwGnbSctp(NwGnbSctp::ASSOCIATION_SETUP); + auto *w = new NmGnbSctp(NmGnbSctp::ASSOCIATION_SETUP); w->clientId = clientId; w->associationId = associationId; w->inStreams = inStreams; @@ -65,7 +65,7 @@ class SctpHandler : public sctp::ISctpHandler void onAssociationShutdown() override { - auto *w = new NwGnbSctp(NwGnbSctp::ASSOCIATION_SHUTDOWN); + auto *w = new NmGnbSctp(NmGnbSctp::ASSOCIATION_SHUTDOWN); w->clientId = clientId; sctpTask->push(w); } @@ -75,7 +75,7 @@ class SctpHandler : public sctp::ISctpHandler auto *data = new uint8_t[length]; std::memcpy(data, buffer, length); - auto *w = new NwGnbSctp(NwGnbSctp::RECEIVE_MESSAGE); + auto *w = new NmGnbSctp(NmGnbSctp::RECEIVE_MESSAGE); w->clientId = clientId; w->buffer = UniqueBuffer{data, length}; w->stream = stream; @@ -84,7 +84,7 @@ class SctpHandler : public sctp::ISctpHandler void onUnhandledNotification() override { - auto *w = new NwGnbSctp(NwGnbSctp::UNHANDLED_NOTIFICATION); + auto *w = new NmGnbSctp(NmGnbSctp::UNHANDLED_NOTIFICATION); w->clientId = clientId; sctpTask->push(w); } @@ -119,35 +119,35 @@ void SctpTask::onLoop() switch (msg->msgType) { case NtsMessageType::GNB_SCTP: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbSctp::CONNECTION_REQUEST: { + case NmGnbSctp::CONNECTION_REQUEST: { receiveSctpConnectionSetupRequest(w->clientId, w->localAddress, w->localPort, w->remoteAddress, w->remotePort, w->ppid, w->associatedTask); break; } - case NwGnbSctp::CONNECTION_CLOSE: { + case NmGnbSctp::CONNECTION_CLOSE: { receiveConnectionClose(w->clientId); break; } - case NwGnbSctp::ASSOCIATION_SETUP: { + case NmGnbSctp::ASSOCIATION_SETUP: { receiveAssociationSetup(w->clientId, w->associationId, w->inStreams, w->outStreams); break; } - case NwGnbSctp::ASSOCIATION_SHUTDOWN: { + case NmGnbSctp::ASSOCIATION_SHUTDOWN: { receiveAssociationShutdown(w->clientId); break; } - case NwGnbSctp::RECEIVE_MESSAGE: { + case NmGnbSctp::RECEIVE_MESSAGE: { receiveClientReceive(w->clientId, w->stream, std::move(w->buffer)); break; } - case NwGnbSctp::SEND_MESSAGE: { + case NmGnbSctp::SEND_MESSAGE: { receiveSendMessage(w->clientId, w->stream, std::move(w->buffer)); break; } - case NwGnbSctp::UNHANDLED_NOTIFICATION: { + case NmGnbSctp::UNHANDLED_NOTIFICATION: { receiveUnhandledNotification(w->clientId); break; } @@ -242,7 +242,7 @@ void SctpTask::receiveAssociationSetup(int clientId, int associationId, int inSt } // Notify the relevant task - auto *msg = new NwGnbSctp(NwGnbSctp::ASSOCIATION_SETUP); + auto *msg = new NmGnbSctp(NmGnbSctp::ASSOCIATION_SETUP); msg->clientId = clientId; msg->associationId = associationId; msg->inStreams = inStreams; @@ -262,7 +262,7 @@ void SctpTask::receiveAssociationShutdown(int clientId) } // Notify the relevant task - auto *msg = new NwGnbSctp(NwGnbSctp::ASSOCIATION_SHUTDOWN); + auto *msg = new NmGnbSctp(NmGnbSctp::ASSOCIATION_SHUTDOWN); msg->clientId = clientId; entry->associatedTask->push(msg); } @@ -277,7 +277,7 @@ void SctpTask::receiveClientReceive(int clientId, uint16_t stream, UniqueBuffer } // Notify the relevant task - auto *msg = new NwGnbSctp(NwGnbSctp::RECEIVE_MESSAGE); + auto *msg = new NmGnbSctp(NmGnbSctp::RECEIVE_MESSAGE); msg->clientId = clientId; msg->stream = stream; msg->buffer = std::move(buffer); diff --git a/src/lib/app/cli_cmd.cpp b/src/lib/app/cli_cmd.cpp index af3fa28e4..5a0b847a3 100644 --- a/src/lib/app/cli_cmd.cpp +++ b/src/lib/app/cli_cmd.cpp @@ -158,13 +158,15 @@ static OrderedMap g_ueCmdEntries = { {"info", {"Show some information about the UE", "", DefaultDesc, false}}, {"status", {"Show some status information about the UE", "", DefaultDesc, false}}, {"timers", {"Dump current status of the timers in the UE", "", DefaultDesc, false}}, + {"rls-state", {"Show status information about RLS", "", DefaultDesc, false}}, + {"coverage", {"Dump available cells and PLMNs in the coverage", "", DefaultDesc, false}}, {"ps-establish", {"Trigger a PDU session establishment procedure", " [options]", DescForPsEstablish, true}}, + {"ps-list", {"List all PDU sessions", "", DefaultDesc, false}}, {"ps-release", {"Trigger a PDU session release procedure", "...", DefaultDesc, true}}, {"ps-release-all", {"Trigger PDU session release procedures for all active sessions", "", DefaultDesc, false}}, {"deregister", {"Perform a de-registration by the UE", "", DefaultDesc, true}}, - {"coverage", {"Show gNodeB cell coverage information", "", DefaultDesc, false}}, }; static std::unique_ptr GnbCliParseImpl(const std::string &subCmd, const opt::OptionsResult &options, @@ -236,19 +238,20 @@ static std::unique_ptr UeCliParseImpl(const std::string &subCmd, c else if (subCmd == "deregister") { auto cmd = std::make_unique(UeCliCommand::DE_REGISTER); - cmd->deregCause = EDeregCause::UNSPECIFIED; if (options.positionalCount() == 0) CMD_ERR("De-registration type is expected") if (options.positionalCount() > 1) CMD_ERR("Only one de-registration type is expected") auto type = options.getPositional(0); - if (type == "switch-off") + if (type == "normal") + cmd->deregCause = EDeregCause::NORMAL; + else if (type == "switch-off") cmd->deregCause = EDeregCause::SWITCH_OFF; else if (type == "disable-5g") cmd->deregCause = EDeregCause::DISABLE_5G; else if (type == "remove-sim") cmd->deregCause = EDeregCause::USIM_REMOVAL; - else if (type != "normal") + else CMD_ERR("Invalid de-registration type, possible values are: \"normal\", \"disable-5g\", \"switch-off\", " "\"remove-sim\"") return cmd; @@ -316,6 +319,14 @@ static std::unique_ptr UeCliParseImpl(const std::string &subCmd, c } return cmd; } + else if (subCmd == "ps-list") + { + return std::make_unique(UeCliCommand::PS_LIST); + } + else if (subCmd == "rls-state") + { + return std::make_unique(UeCliCommand::RLS_STATE); + } else if (subCmd == "coverage") { return std::make_unique(UeCliCommand::COVERAGE); diff --git a/src/lib/app/cli_cmd.hpp b/src/lib/app/cli_cmd.hpp index 94dbb6fdd..49b32d891 100644 --- a/src/lib/app/cli_cmd.hpp +++ b/src/lib/app/cli_cmd.hpp @@ -52,7 +52,9 @@ struct UeCliCommand PS_ESTABLISH, PS_RELEASE, PS_RELEASE_ALL, + PS_LIST, DE_REGISTER, + RLS_STATE, COVERAGE, } present; diff --git a/src/lib/app/monitor.hpp b/src/lib/app/monitor.hpp index d8d599df9..848fefd4c 100644 --- a/src/lib/app/monitor.hpp +++ b/src/lib/app/monitor.hpp @@ -36,6 +36,7 @@ enum class StateType RM, CM, U5, + RRC }; class INodeListener diff --git a/src/lib/asn/rrc.cpp b/src/lib/asn/rrc.cpp new file mode 100644 index 000000000..ec5ce653d --- /dev/null +++ b/src/lib/asn/rrc.cpp @@ -0,0 +1,77 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "rrc.hpp" + +#include + +#include + +#include +#include +#include + +namespace asn::rrc +{ + +static int NthDigit(int number, int n) +{ + for (int i = 0; i < n; i++) + number /= 10; + return number % 10; +} + +void SetPlmnId(const Plmn &source, ASN_RRC_PLMN_Identity &target) +{ + int mncDigits = source.isLongMnc ? 3 : 2; + for (int i = 0; i < mncDigits; i++) + asn::SequenceAdd(target.mnc, NewMccMncDigit(NthDigit(source.mnc, mncDigits - i - 1))); + + target.mcc = asn::NewFor(target.mcc); + + for (int i = 0; i < 3; i++) + asn::SequenceAdd(*target.mcc, NewMccMncDigit(NthDigit(source.mcc, 2 - i))); +} + +ASN_RRC_PLMN_Identity *NewPlmnId(const Plmn &plmn) +{ + auto *value = asn::New(); + SetPlmnId(plmn, *value); + return value; +} + +ASN_RRC_MCC_MNC_Digit_t *NewMccMncDigit(int digit) +{ + auto *value = asn::New(); + *value = digit; + return value; +} + +Plmn GetPlmnId(const ASN_RRC_PLMN_Identity &value) +{ + if (value.mcc == nullptr) + throw std::runtime_error(""); + + Plmn plmn; + + asn::ForeachItem(*value.mcc, [&plmn](auto &i) { + plmn.mcc *= 10; + plmn.mcc += static_cast(i); + }); + + asn::ForeachItem(value.mnc, [&plmn](auto &i) { + plmn.mnc *= 10; + plmn.mnc += static_cast(i); + }); + + plmn.isLongMnc = value.mnc.list.count == 3; + + return plmn; +} + +} // namespace asn::rrc diff --git a/src/lib/asn/rrc.hpp b/src/lib/asn/rrc.hpp new file mode 100644 index 000000000..a402030fb --- /dev/null +++ b/src/lib/asn/rrc.hpp @@ -0,0 +1,24 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include + +#include + +namespace asn::rrc +{ + +void SetPlmnId(const Plmn &source, ASN_RRC_PLMN_Identity &target); + +ASN_RRC_PLMN_Identity *NewPlmnId(const Plmn &plmn); + +ASN_RRC_MCC_MNC_Digit_t *NewMccMncDigit(int digit); + +Plmn GetPlmnId(const ASN_RRC_PLMN_Identity& value); + +} // namespace asn::rrc diff --git a/src/lib/asn/utils.hpp b/src/lib/asn/utils.hpp index 1e4a2fef5..459458d48 100644 --- a/src/lib/asn/utils.hpp +++ b/src/lib/asn/utils.hpp @@ -43,6 +43,12 @@ inline T *NewFor(T *p) return New::type>(); } +template +inline void MakeNew(T *&p) +{ + p = NewFor(p); +} + template inline void Free(asn_TYPE_descriptor_t &desc, T *ptr) { diff --git a/src/lib/nas/base.cpp b/src/lib/nas/base.cpp index 07dce6c7e..8d288561e 100644 --- a/src/lib/nas/base.cpp +++ b/src/lib/nas/base.cpp @@ -36,7 +36,7 @@ void nas::EncodeBcdString(OctetString &stream, const std::string &bcd, size_t oc if (skipFirst) { - for (int i = bcd.length(); i >= 1; i--) + for (size_t i = bcd.length(); i >= 1; i--) halfOctets[i] = halfOctets[i - 1]; halfOctets[0] = skippedHalfOctet & 0xF; } diff --git a/src/lib/nas/enums.cpp b/src/lib/nas/enums.cpp index 93ebff1f3..eadd0bd7a 100644 --- a/src/lib/nas/enums.cpp +++ b/src/lib/nas/enums.cpp @@ -7,3 +7,27 @@ // #include "enums.hpp" + +namespace nas +{ + +Json ToJson(const EPduSessionType &v) +{ + switch (v) + { + case EPduSessionType::IPV4: + return "IPv4"; + case EPduSessionType::IPV6: + return "IPv6"; + case EPduSessionType::IPV4V6: + return "IPv4v6"; + case EPduSessionType::UNSTRUCTURED: + return "unstructured"; + case EPduSessionType::ETHERNET: + return "ethernet"; + default: + return "?"; + } +} + +} // namespace nas diff --git a/src/lib/nas/enums.hpp b/src/lib/nas/enums.hpp index d810aa58a..4c6767ebc 100644 --- a/src/lib/nas/enums.hpp +++ b/src/lib/nas/enums.hpp @@ -8,6 +8,8 @@ #pragma once +#include + namespace nas { @@ -750,4 +752,6 @@ enum class EQoSOperationCode MODIFY_EXISTING = 0b011, }; +Json ToJson(const EPduSessionType &v); + } // namespace nas \ No newline at end of file diff --git a/src/lib/nas/ie3.cpp b/src/lib/nas/ie3.cpp index 75897087e..0d7360a07 100644 --- a/src/lib/nas/ie3.cpp +++ b/src/lib/nas/ie3.cpp @@ -32,6 +32,11 @@ IE5gsTrackingAreaIdentity::IE5gsTrackingAreaIdentity(int mcc, int mnc, bool isLo { } +IE5gsTrackingAreaIdentity::IE5gsTrackingAreaIdentity(const Tai &tai) + : mcc(tai.plmn.mcc), mnc(tai.plmn.mnc), isLongMnc(tai.plmn.isLongMnc), trackingAreaCode(tai.tac) +{ +} + IE5gsTrackingAreaIdentity IE5gsTrackingAreaIdentity::Decode(const OctetView &stream) { auto plmn = VPlmn::Decode(stream); diff --git a/src/lib/nas/ie3.hpp b/src/lib/nas/ie3.hpp index 19885d2a6..7aa9982ad 100644 --- a/src/lib/nas/ie3.hpp +++ b/src/lib/nas/ie3.hpp @@ -34,6 +34,7 @@ struct IE5gsTrackingAreaIdentity : InformationElement3 IE5gsTrackingAreaIdentity() = default; IE5gsTrackingAreaIdentity(int mcc, int mnc, bool isLongMnc, const octet3 &trackingAreaCode); + explicit IE5gsTrackingAreaIdentity(const Tai &tai); static IE5gsTrackingAreaIdentity Decode(const OctetView &stream); static void Encode(const IE5gsTrackingAreaIdentity &ie, OctetString &stream); diff --git a/src/lib/nas/ie4.cpp b/src/lib/nas/ie4.cpp index 5a6e5ea14..b5df744bf 100644 --- a/src/lib/nas/ie4.cpp +++ b/src/lib/nas/ie4.cpp @@ -8,6 +8,8 @@ #include "ie4.hpp" +#include + namespace nas { @@ -1075,4 +1077,43 @@ void IE5gsTrackingAreaIdentityList::Encode(const IE5gsTrackingAreaIdentityList & VPartialTrackingAreaIdentityList::Encode(x, stream); } +Json ToJson(const IEPduAddress &v) +{ + switch (v.sessionType) + { + case EPduSessionType::IPV4: + case EPduSessionType::IPV6: + case EPduSessionType::IPV4V6: + return utils::OctetStringToIp(v.pduAddressInformation); + case EPduSessionType::UNSTRUCTURED: + case EPduSessionType::ETHERNET: + return v.pduAddressInformation.toHexString(); + default: + return "?"; + } +} + +Json ToJson(const IESessionAmbr &v) +{ + int downlinkValue = static_cast(v.sessionAmbrForDownlink); + int uplinkValue = static_cast(v.sessionAmbrForUplink); + + int downlinkUnit = static_cast(v.unitForSessionAmbrForDownlink); + int uplinkUnit = static_cast(v.unitForSessionAmbrForUplink); + + int downlinkFactor = 1 << (2 * ((downlinkUnit - 1) % 5)); + int uplinkFactor = 1 << (2 * ((uplinkUnit - 1) % 5)); + + int downlinkMagnitude = (downlinkUnit - 1) / 5; + int uplinkMagnitude = (uplinkUnit - 1) / 5; + + int downlink = downlinkValue * downlinkFactor; + int uplink = uplinkValue * uplinkFactor; + + static const std::string units[5] = {"Kb/s", "Mb/s", "Gb/s", "Tb/s", "Pb/s"}; + + return "up[" + std::to_string(uplink) + units[uplinkMagnitude] + "] down[" + std::to_string(downlink) + + units[downlinkMagnitude] + "]"; +} + } // namespace nas \ No newline at end of file diff --git a/src/lib/nas/ie4.hpp b/src/lib/nas/ie4.hpp index b191f3ed9..1406054fe 100644 --- a/src/lib/nas/ie4.hpp +++ b/src/lib/nas/ie4.hpp @@ -14,6 +14,8 @@ #include #include +#include + namespace nas { @@ -570,4 +572,7 @@ struct IE5gsTrackingAreaIdentityList : InformationElement4 static void Encode(const IE5gsTrackingAreaIdentityList &ie, OctetString &stream); }; +Json ToJson(const IEPduAddress &v); +Json ToJson(const IESessionAmbr &v); + } // namespace nas diff --git a/src/lib/nas/ie6.cpp b/src/lib/nas/ie6.cpp index 1102496b0..54cd8b03a 100644 --- a/src/lib/nas/ie6.cpp +++ b/src/lib/nas/ie6.cpp @@ -448,7 +448,7 @@ Json ToJson(const IE5gsMobileIdentity &v) switch (v.type) { case EIdentityType::NO_IDENTITY: - return "no-identitiy"; + return "no-identity"; case EIdentityType::SUCI: { switch (v.supiFormat) { diff --git a/src/lib/nas/storage.cpp b/src/lib/nas/storage.cpp new file mode 100644 index 000000000..6712838d6 --- /dev/null +++ b/src/lib/nas/storage.cpp @@ -0,0 +1,14 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "storage.hpp" + +namespace nas +{ + +} \ No newline at end of file diff --git a/src/lib/nas/storage.hpp b/src/lib/nas/storage.hpp new file mode 100644 index 000000000..3c0f3a82d --- /dev/null +++ b/src/lib/nas/storage.hpp @@ -0,0 +1,281 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include +#include +#include +#include +#include + +#include +#include + +namespace nas +{ + +// TODO: Periodun sonuna geldiğinde erişilmezse, (autoClearIfNecessary çağrılmazsa) delete yapılmaz backup çalışmaz + +/* + * - Items are unique, if already exists, deletes the previous one + * - List have fixed size, if capacity is full, oldest item is deleted + * - Automatically cleared after specified period + * - The list is NOT thread safe + */ +template +class NasList +{ + public: + using backup_functor_type = std::function &buffer, size_t count)>; + + private: + const size_t m_sizeLimit; + const int64_t m_autoClearingPeriod; + const std::optional m_backupFunctor; + + std::vector m_data; + size_t m_size; + int64_t m_lastAutoCleared; + + public: + NasList(size_t sizeLimit, int64_t autoClearingPeriod, std::optional backupFunctor) + : m_sizeLimit{sizeLimit}, m_autoClearingPeriod{autoClearingPeriod}, + m_backupFunctor{backupFunctor}, m_data{sizeLimit}, m_size{}, m_lastAutoCleared{::utils::CurrentTimeMillis()} + { + } + + NasList(const NasList &) = delete; + NasList(NasList &&) = delete; + + public: + void add(const T &item) + { + autoClearIfNecessary(); + remove(item); + makeSlotForNewItem(); + + m_data[m_size] = item; + m_size++; + + touch(); + } + + void add(T &&item) + { + autoClearIfNecessary(); + remove(item); + makeSlotForNewItem(); + + m_data[m_size] = std::move(item); + m_size++; + + touch(); + } + + void remove(const T &item) + { + autoClearIfNecessary(); + + size_t index = ~0u; + for (size_t i = 0; i < m_size; i++) + { + if (m_data[i] == item) + { + index = i; + break; + } + } + + if (index != ~0u) + removeAt(index); + } + + bool contains(const T &item) + { + autoClearIfNecessary(); + + for (size_t i = 0; i < m_size; i++) + if (m_data[i] == item) + return true; + return false; + } + + template + void forEach(Functor &&fun) + { + autoClearIfNecessary(); + + for (size_t i = 0; i < m_size; i++) + fun((const T &)m_data[i]); + } + + template + void mutateForEach(Functor &&fun) + { + autoClearIfNecessary(); + + for (size_t i = 0; i < m_size; i++) + fun(m_data[i]); + + touch(); + } + + void clear() + { + int64_t currentTime = ::utils::CurrentTimeMillis(); + if (currentTime - m_lastAutoCleared >= m_autoClearingPeriod) + m_lastAutoCleared = currentTime; + + m_data.clear(); + m_size = 0; + + touch(); + } + + [[nodiscard]] size_t size() const + { + autoClearIfNecessary(); + + return m_data.size(); + } + + private: + void autoClearIfNecessary() + { + if (m_autoClearingPeriod <= 0) + return; + + int64_t currentTime = ::utils::CurrentTimeMillis(); + if (currentTime - m_lastAutoCleared >= m_autoClearingPeriod) + { + m_lastAutoCleared = currentTime; + clear(); + } + } + + void makeSlotForNewItem() + { + if (m_size >= m_sizeLimit) + removeAt(0); + } + + void removeAt(size_t index) + { + for (size_t i = index; i < m_size; ++i) + m_data[i] = i + 1 < m_sizeLimit ? m_data[i + 1] : T{}; + m_size--; + + touch(); + } + + void touch() + { + if (m_backupFunctor) + (*m_backupFunctor)(m_data, m_size); + } +}; + +template +class NasSlot +{ + public: + using backup_functor_type = std::function; + + private: + const int64_t m_autoClearingPeriod; + const std::optional m_backupFunctor; + + T m_value; + int64_t m_lastAutoCleared; + + static_assert(!std::is_reference::value); + + public: + NasSlot(int64_t autoClearingPeriod, std::optional backupFunctor) + : m_autoClearingPeriod{autoClearingPeriod}, m_backupFunctor{backupFunctor}, m_value{}, + m_lastAutoCleared{::utils::CurrentTimeMillis()} + { + } + + const T &get() + { + autoClearIfNecessary(); + + return m_value; + } + + const T &getPure() const + { + return m_value; + } + + void clear() + { + set(T{}); + } + + void set(const T &value) + { + autoClearIfNecessary(); + + m_value = value; + touch(); + } + + void set(T &&value) + { + autoClearIfNecessary(); + + m_value = std::move(value); + touch(); + } + + template + void access(Functor fun) + { + autoClearIfNecessary(); + + fun((const T &)m_value); + } + + template + void mutate(Functor fun) + { + autoClearIfNecessary(); + + fun((T &)m_value); + touch(); + } + + private: + void autoClearIfNecessary() + { + if (m_autoClearingPeriod <= 0) + return; + + int64_t currentTime = ::utils::CurrentTimeMillis(); + if (currentTime - m_lastAutoCleared >= m_autoClearingPeriod) + { + m_lastAutoCleared = currentTime; + m_value = {}; + } + } + + void touch() + { + if (m_backupFunctor) + (*m_backupFunctor)(m_value); + } +}; + +} // namespace nas + +template +inline Json ToJson(const nas::NasSlot &v) +{ + return ToJson(v.getPure()); +} diff --git a/src/lib/nas/utils.cpp b/src/lib/nas/utils.cpp index d69eca31b..cdd36c687 100644 --- a/src/lib/nas/utils.cpp +++ b/src/lib/nas/utils.cpp @@ -11,6 +11,8 @@ #include #include +#include + namespace nas::utils { @@ -434,4 +436,118 @@ void RemoveFromTaiList(IE5gsTrackingAreaIdentityList &list, const VTrackingAreaI list.list.end()); } +int TaiListSize(const IE5gsTrackingAreaIdentityList &list) +{ + int size = 0; + for (auto &item : list.list) + size += TaiListSize(item); + return size; +} + +int TaiListSize(const VPartialTrackingAreaIdentityList &list) +{ + size_t size = 0; + if (list.list00.has_value()) + size += list.list00->tacs.size(); + if (list.list01.has_value()) + size++; + if (list.list10.has_value()) + size += list.list10->tais.size(); + return static_cast(size); +} + +bool ServiceAreaListAllowsPlmn(const IEServiceAreaList &list, const VPlmn &plmn) +{ + return std::any_of(list.list.begin(), list.list.end(), + [&plmn](auto &item) { return ServiceAreaListAllowsPlmn(item, plmn); }); +} + +bool ServiceAreaListAllowsTai(const IEServiceAreaList &list, const VTrackingAreaIdentity &tai) +{ + return std::any_of(list.list.begin(), list.list.end(), + [&tai](auto &item) { return ServiceAreaListAllowsTai(item, tai); }); +} + +bool ServiceAreaListAllowsPlmn(const VPartialServiceAreaList &list, const VPlmn &plmn) +{ + if (list.present == 3) + { + if (list.list11->allowedType == EAllowedType::IN_THE_ALLOWED_AREA && DeepEqualsV(list.list11->plmn, plmn)) + return true; + } + return false; +} + +bool ServiceAreaListAllowsTai(const VPartialServiceAreaList &list, const VTrackingAreaIdentity &tai) +{ + if (list.present == 0) + { + if (list.list00->allowedType == EAllowedType::IN_THE_ALLOWED_AREA && DeepEqualsV(list.list00->plmn, tai.plmn) && + std::any_of(list.list00->tacs.begin(), list.list00->tacs.end(), + [&tai](auto &i) { return (int)i == (int)tai.tac; })) + return true; + } + else if (list.present == 1) + { + if (list.list01->allowedType == EAllowedType::IN_THE_ALLOWED_AREA && DeepEqualsV(list.list01->plmn, tai.plmn) && + (int)list.list01->tac == (int)tai.tac) + return true; + } + else if (list.present == 2) + { + if (list.list10->allowedType == EAllowedType::IN_THE_ALLOWED_AREA && + std::any_of(list.list10->tais.begin(), list.list10->tais.end(), + [tai](auto &i) { return DeepEqualsV(i, tai); })) + return true; + } + return false; +} + +Plmn PlmnFrom(const VPlmn &plmn) +{ + Plmn res; + res.mcc = plmn.mcc; + res.mnc = plmn.mnc; + res.isLongMnc = plmn.isLongMnc; + return res; +} + +void RemoveFromServiceAreaList(IEServiceAreaList &list, const VTrackingAreaIdentity &tai) +{ + std::vector deletedSubLists; + int index = 0; + + for (auto &sublist : list.list) + { + if (sublist.present == 0) + { + if (nas::utils::DeepEqualsV(sublist.list00->plmn, tai.plmn)) + ::utils::EraseWhere(sublist.list00->tacs, [&tai](auto &i) { return (int)i == (int)tai.tac; }); + if (sublist.list00->tacs.empty()) + deletedSubLists.push_back(index); + } + else if (sublist.present == 1) + { + if (nas::utils::DeepEqualsV(sublist.list01->plmn, tai.plmn) && (int)tai.tac == (int)sublist.list01->tac) + deletedSubLists.push_back(index); + } + else if (sublist.present == 2) + { + ::utils::EraseWhere(sublist.list10->tais, [&tai](auto &i) { return nas::utils::DeepEqualsV(i, tai); }); + if (sublist.list10->tais.empty()) + deletedSubLists.push_back(index); + } + index++; + } + + int deletedSoFar = 0; + + for (int i : deletedSubLists) + { + int indexToDelete = i - deletedSoFar; + list.list.erase(list.list.begin() + indexToDelete); + deletedSoFar++; + } +} + } // namespace nas::utils diff --git a/src/lib/nas/utils.hpp b/src/lib/nas/utils.hpp index 4262ce560..cd4cd38cc 100644 --- a/src/lib/nas/utils.hpp +++ b/src/lib/nas/utils.hpp @@ -17,6 +17,7 @@ IESNssai SNssaiFrom(const SingleSlice &v); IENssai NssaiFrom(const NetworkSlice &v); IEDnn DnnFromApn(const std::string &apn); VPlmn PlmnFrom(const Plmn &plmn); +Plmn PlmnFrom(const VPlmn &plmn); NetworkSlice NssaiTo(const IENssai &v); SingleSlice SNssaiTo(const IESNssai &v); @@ -31,10 +32,17 @@ bool PlmnListContains(const IEPlmnList &list, VPlmn item); bool PlmnListContains(const IEPlmnList &list, Plmn item); bool TaiListContains(const nas::IE5gsTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai); bool TaiListContains(const nas::VPartialTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai); +int TaiListSize(const nas::VPartialTrackingAreaIdentityList &list); +int TaiListSize(const nas::IE5gsTrackingAreaIdentityList &list); bool ServiceAreaListForbidsPlmn(const nas::IEServiceAreaList &list, const VPlmn &plmn); bool ServiceAreaListForbidsTai(const nas::IEServiceAreaList &list, const VTrackingAreaIdentity &tai); bool ServiceAreaListForbidsPlmn(const nas::VPartialServiceAreaList &list, const VPlmn &plmn); bool ServiceAreaListForbidsTai(const nas::VPartialServiceAreaList &list, const VTrackingAreaIdentity &tai); +bool ServiceAreaListAllowsPlmn(const nas::IEServiceAreaList &list, const VPlmn &plmn); +bool ServiceAreaListAllowsTai(const nas::IEServiceAreaList &list, const VTrackingAreaIdentity &tai); +bool ServiceAreaListAllowsPlmn(const nas::VPartialServiceAreaList &list, const VPlmn &plmn); +bool ServiceAreaListAllowsTai(const nas::VPartialServiceAreaList &list, const VTrackingAreaIdentity &tai); +void RemoveFromServiceAreaList(nas::IEServiceAreaList &list, const VTrackingAreaIdentity &tai); const char *EnumToString(ERegistrationType v); const char *EnumToString(EMmCause v); diff --git a/src/lib/nas/values.cpp b/src/lib/nas/values.cpp index 88a7806ea..56508dd00 100644 --- a/src/lib/nas/values.cpp +++ b/src/lib/nas/values.cpp @@ -149,6 +149,11 @@ VTrackingAreaIdentity::VTrackingAreaIdentity(const VPlmn &plmn, const octet3 &ta { } +VTrackingAreaIdentity::VTrackingAreaIdentity(const Tai &tai) + : plmn(tai.plmn.mcc, tai.plmn.mnc, tai.plmn.isLongMnc), tac(tai.tac) +{ +} + void VTime::Encode(const VTime &value, OctetString &stream) { stream.appendOctet(value.year); diff --git a/src/lib/nas/values.hpp b/src/lib/nas/values.hpp index a07bf71a7..089ba0771 100644 --- a/src/lib/nas/values.hpp +++ b/src/lib/nas/values.hpp @@ -15,6 +15,7 @@ #include #include +#include namespace nas { @@ -73,6 +74,7 @@ struct VTrackingAreaIdentity octet3 tac; VTrackingAreaIdentity(const VPlmn &plmn, const octet3 &tac); + explicit VTrackingAreaIdentity(const Tai &tai); static void Encode(const VTrackingAreaIdentity &value, OctetString &stream); static VTrackingAreaIdentity Decode(const OctetView &stream); diff --git a/src/lib/rls/rls_base.cpp b/src/lib/rls/rls_base.cpp new file mode 100644 index 000000000..f2a5ce76d --- /dev/null +++ b/src/lib/rls/rls_base.cpp @@ -0,0 +1,15 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "rls_base.hpp" + +namespace rls +{ + +} + diff --git a/src/lib/rls/rls_base.hpp b/src/lib/rls/rls_base.hpp new file mode 100644 index 000000000..bb8cbb5e0 --- /dev/null +++ b/src/lib/rls/rls_base.hpp @@ -0,0 +1,32 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "rls_pdu.hpp" + +#include + +namespace rls +{ + +struct PduInfo +{ + uint32_t id{}; + OctetString pdu; + rrc::RrcChannel rrcChannel{}; + int64_t sentTime{}; + int endPointId{}; +}; + +enum class ERlfCause +{ + PDU_ID_EXISTS, + PDU_ID_FULL, + SIGNAL_LOST_TO_CONNECTED_CELL +}; + +} // namespace rls \ No newline at end of file diff --git a/src/lib/rls/rls_pdu.cpp b/src/lib/rls/rls_pdu.cpp index 2940b4465..cb4354467 100644 --- a/src/lib/rls/rls_pdu.cpp +++ b/src/lib/rls/rls_pdu.cpp @@ -13,36 +13,6 @@ namespace rls { -static void AppendPlmn(const Plmn &plmn, OctetString &stream) -{ - stream.appendOctet2(plmn.mcc); - stream.appendOctet2(plmn.mnc); - stream.appendOctet(plmn.isLongMnc ? 1 : 0); -} - -static void AppendGlobalNci(const GlobalNci &nci, OctetString &stream) -{ - AppendPlmn(nci.plmn, stream); - stream.appendOctet8(nci.nci); -} - -static Plmn DecodePlmn(const OctetView &stream) -{ - Plmn res{}; - res.mcc = stream.read2I(); - res.mnc = stream.read2I(); - res.isLongMnc = stream.readI() != 0; - return res; -} - -static GlobalNci DecodeGlobalNci(const OctetView &stream) -{ - GlobalNci res{}; - res.plmn = DecodePlmn(stream); - res.nci = stream.read8L(); - return res; -} - void EncodeRlsMessage(const RlsMessage &msg, OctetString &stream) { stream.appendOctet(0x03); // (Just for old RLS compatibility) @@ -52,32 +22,33 @@ void EncodeRlsMessage(const RlsMessage &msg, OctetString &stream) stream.appendOctet(cons::Patch); stream.appendOctet(static_cast(msg.msgType)); stream.appendOctet8(msg.sti); - if (msg.msgType == EMessageType::CELL_INFO_REQUEST) + if (msg.msgType == EMessageType::HEARTBEAT) { - auto &m = (const RlsCellInfoRequest &)msg; + auto &m = (const RlsHeartBeat &)msg; stream.appendOctet4(m.simPos.x); stream.appendOctet4(m.simPos.y); stream.appendOctet4(m.simPos.z); } - else if (msg.msgType == EMessageType::CELL_INFO_RESPONSE) + else if (msg.msgType == EMessageType::HEARTBEAT_ACK) { - auto &m = (const RlsCellInfoResponse &)msg; - AppendGlobalNci(m.cellId, stream); - stream.appendOctet4(m.tac); + auto &m = (const RlsHeartBeatAck &)msg; stream.appendOctet4(m.dbm); - stream.appendOctet4(static_cast(m.gnbName.size())); - stream.appendUtf8(m.gnbName); - stream.appendOctet4(static_cast(m.linkIp.size())); - stream.appendUtf8(m.linkIp); } - else if (msg.msgType == EMessageType::PDU_DELIVERY) + else if (msg.msgType == EMessageType::PDU_TRANSMISSION) { - auto &m = (const RlsPduDelivery &)msg; + auto &m = (const RlsPduTransmission &)msg; stream.appendOctet(static_cast(m.pduType)); + stream.appendOctet4(m.pduId); + stream.appendOctet4(m.payload); stream.appendOctet4(m.pdu.length()); stream.append(m.pdu); - stream.appendOctet4(m.payload.length()); - stream.append(m.payload); + } + else if (msg.msgType == EMessageType::PDU_TRANSMISSION_ACK) + { + auto &m = (const RlsPduTransmissionAck &)msg; + stream.appendOctet4(static_cast(m.pduIds.size())); + for (auto pduId : m.pduIds) + stream.appendOctet4(pduId); } } @@ -97,30 +68,36 @@ std::unique_ptr DecodeRlsMessage(const OctetView &stream) auto msgType = static_cast(stream.readI()); uint64_t sti = stream.read8UL(); - if (msgType == EMessageType::CELL_INFO_REQUEST) + if (msgType == EMessageType::HEARTBEAT) { - auto res = std::make_unique(sti); + auto res = std::make_unique(sti); res->simPos.x = stream.read4I(); res->simPos.y = stream.read4I(); res->simPos.z = stream.read4I(); return res; } - else if (msgType == EMessageType::CELL_INFO_RESPONSE) + else if (msgType == EMessageType::HEARTBEAT_ACK) { - auto res = std::make_unique(sti); - res->cellId = DecodeGlobalNci(stream); - res->tac = stream.read4I(); + auto res = std::make_unique(sti); res->dbm = stream.read4I(); - res->gnbName = stream.readUtf8String(stream.read4I()); - res->linkIp = stream.readUtf8String(stream.read4I()); return res; } - else if (msgType == EMessageType::PDU_DELIVERY) + else if (msgType == EMessageType::PDU_TRANSMISSION) { - auto res = std::make_unique(sti); - res->pduType = static_cast(stream.readI()); + auto res = std::make_unique(sti); + res->pduType = static_cast((uint8_t)stream.read()); + res->pduId = stream.read4UI(); + res->payload = stream.read4UI(); res->pdu = stream.readOctetString(stream.read4I()); - res->payload = stream.readOctetString(stream.read4I()); + return res; + } + else if (msgType == EMessageType::PDU_TRANSMISSION_ACK) + { + auto res = std::make_unique(sti); + auto count = stream.read4UI(); + res->pduIds.reserve(count); + for (uint32_t i = 0; i < count; i++) + res->pduIds.push_back(stream.read4UI()); return res; } diff --git a/src/lib/rls/rls_pdu.hpp b/src/lib/rls/rls_pdu.hpp index f00064c88..c214976d5 100644 --- a/src/lib/rls/rls_pdu.hpp +++ b/src/lib/rls/rls_pdu.hpp @@ -21,9 +21,15 @@ namespace rls enum class EMessageType : uint8_t { RESERVED = 0, - CELL_INFO_REQUEST, - CELL_INFO_RESPONSE, - PDU_DELIVERY + + DEPRECATED1 = 1, + DEPRECATED2 = 2, + DEPRECATED3 = 3, + + HEARTBEAT = 4, + HEARTBEAT_ACK = 5, + PDU_TRANSMISSION = 6, + PDU_TRANSMISSION_ACK = 7, }; enum class EPduType : uint8_t @@ -43,35 +49,41 @@ struct RlsMessage } }; -struct RlsCellInfoRequest : RlsMessage +struct RlsHeartBeat : RlsMessage { - Vector3 simPos{}; + Vector3 simPos; - explicit RlsCellInfoRequest(uint64_t sti) : RlsMessage(EMessageType::CELL_INFO_REQUEST, sti) + explicit RlsHeartBeat(uint64_t sti) : RlsMessage(EMessageType::HEARTBEAT, sti) { } }; -struct RlsCellInfoResponse : RlsMessage +struct RlsHeartBeatAck : RlsMessage { - GlobalNci cellId{}; - int tac{}; int dbm{}; - std::string gnbName{}; - std::string linkIp{}; - explicit RlsCellInfoResponse(uint64_t sti) : RlsMessage(EMessageType::CELL_INFO_RESPONSE, sti) + explicit RlsHeartBeatAck(uint64_t sti) : RlsMessage(EMessageType::HEARTBEAT_ACK, sti) { } }; -struct RlsPduDelivery : RlsMessage +struct RlsPduTransmission : RlsMessage { EPduType pduType{}; + uint32_t pduId{}; + uint32_t payload{}; OctetString pdu{}; - OctetString payload{}; - explicit RlsPduDelivery(uint64_t sti) : RlsMessage(EMessageType::PDU_DELIVERY, sti) + explicit RlsPduTransmission(uint64_t sti) : RlsMessage(EMessageType::PDU_TRANSMISSION, sti) + { + } +}; + +struct RlsPduTransmissionAck : RlsMessage +{ + std::vector pduIds; + + explicit RlsPduTransmissionAck(uint64_t sti) : RlsMessage(EMessageType::PDU_TRANSMISSION_ACK, sti) { } }; diff --git a/src/ue.cpp b/src/ue.cpp index a3f4241d6..a4a8e6345 100644 --- a/src/ue.cpp +++ b/src/ue.cpp @@ -118,7 +118,7 @@ static nr::ue::UeConfig *ReadConfigYaml() s.sst = yaml::GetInt32(sNssai, "sst", 0, 0xFF); if (yaml::HasField(sNssai, "sd")) s.sd = octet3{yaml::GetInt32(sNssai, "sd", 0, 0xFFFFFF)}; - result->initials.defaultConfiguredNssai.slices.push_back(s); + result->defaultConfiguredNssai.slices.push_back(s); } } @@ -130,7 +130,7 @@ static nr::ue::UeConfig *ReadConfigYaml() s.sst = yaml::GetInt32(sNssai, "sst", 0, 0xFF); if (yaml::HasField(sNssai, "sd")) s.sd = octet3{yaml::GetInt32(sNssai, "sd", 0, 0xFFFFFF)}; - result->initials.configuredNssai.slices.push_back(s); + result->configuredNssai.slices.push_back(s); } } @@ -199,9 +199,9 @@ static nr::ue::UeConfig *ReadConfigYaml() else throw std::runtime_error("Invalid PDU session type: " + type); - s.isEmergency = yaml::GetBool(sess, "emergency"); + s.isEmergency = false; - result->initSessions.push_back(s); + result->defaultSessions.push_back(s); } } @@ -314,10 +314,11 @@ static nr::ue::UeConfig *GetConfigByUe(int ueIndex) c->imeiSv = g_refConfig->imeiSv; c->supi = g_refConfig->supi; c->hplmn = g_refConfig->hplmn; - c->initials = g_refConfig->initials; + c->configuredNssai = g_refConfig->configuredNssai; + c->defaultConfiguredNssai = g_refConfig->defaultConfiguredNssai; c->supportedAlgs = g_refConfig->supportedAlgs; c->gnbSearchList = g_refConfig->gnbSearchList; - c->initSessions = g_refConfig->initSessions; + c->defaultSessions = g_refConfig->defaultSessions; c->configureRouting = g_refConfig->configureRouting; c->prefixLogger = g_refConfig->prefixLogger; c->integrityMaxRate = g_refConfig->integrityMaxRate; diff --git a/src/ue/app/cmd_handler.cpp b/src/ue/app/cmd_handler.cpp index 5863d4fdd..8110a9539 100644 --- a/src/ue/app/cmd_handler.cpp +++ b/src/ue/app/cmd_handler.cpp @@ -10,8 +10,8 @@ #include #include -#include #include +#include #include #include #include @@ -19,6 +19,7 @@ #define PAUSE_CONFIRM_TIMEOUT 3000 #define PAUSE_POLLING 10 +// todo add coverage again to cli static std::string SignalDescription(int dbm) { if (dbm > -90) @@ -68,7 +69,7 @@ bool UeCmdHandler::isAllPaused() return true; } -void UeCmdHandler::handleCmd(NwUeCliCommand &msg) +void UeCmdHandler::handleCmd(NmUeCliCommand &msg) { pauseTasks(); @@ -99,34 +100,51 @@ void UeCmdHandler::handleCmd(NwUeCliCommand &msg) unpauseTasks(); } -void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg) +void UeCmdHandler::handleCmdImpl(NmUeCliCommand &msg) { switch (msg.cmd->present) { case app::UeCliCommand::STATUS: { - std::vector pduSessions{}; - for (auto &pduSession : m_base->appTask->m_pduSessions) - if (pduSession.has_value()) - pduSessions.push_back(ToJson(*pduSession)); + std::optional currentCellId = std::nullopt; + std::optional currentPlmn = std::nullopt; + std::optional currentTac = std::nullopt; + + auto currentCell = m_base->shCtx.currentCell.get(); + if (currentCell.hasValue()) + { + currentCellId = currentCell.cellId; + currentPlmn = currentCell.plmn; + currentTac = currentCell.tac; + } Json json = Json::Obj({ {"cm-state", ToJson(m_base->nasTask->mm->m_cmState)}, {"rm-state", ToJson(m_base->nasTask->mm->m_rmState)}, {"mm-state", ToJson(m_base->nasTask->mm->m_mmSubState)}, - {"5u-state", ToJson(m_base->nasTask->mm->m_usim->m_uState)}, - {"camped-cell", - ::ToJson(m_base->rlsTask->m_servingCell.has_value() ? m_base->rlsTask->m_servingCell->gnbName : "")}, + {"5u-state", ToJson(m_base->nasTask->mm->m_storage->uState->get())}, {"sim-inserted", m_base->nasTask->mm->m_usim->isValid()}, - {"stored-suci", ToJson(m_base->nasTask->mm->m_usim->m_storedSuci)}, - {"stored-guti", ToJson(m_base->nasTask->mm->m_usim->m_storedGuti)}, + {"selected-plmn", ::ToJson(m_base->shCtx.selectedPlmn.get())}, + {"current-cell", ::ToJson(currentCellId)}, + {"current-plmn", ::ToJson(currentPlmn)}, + {"current-tac", ::ToJson(currentTac)}, + {"last-tai", ToJson(m_base->nasTask->mm->m_storage->lastVisitedRegisteredTai)}, + {"stored-suci", ToJson(m_base->nasTask->mm->m_storage->storedSuci->get())}, + {"stored-guti", ToJson(m_base->nasTask->mm->m_storage->storedGuti->get())}, {"has-emergency", ::ToJson(m_base->nasTask->mm->hasEmergency())}, - {"pdu-sessions", Json::Arr(std::move(pduSessions))}, }); sendResult(msg.address, json.dumpYaml()); break; } case app::UeCliCommand::INFO: { - sendResult(msg.address, ToJson(*m_base->config).dumpYaml()); + auto json = Json::Obj({ + {"supi", ToJson(m_base->config->supi)}, + {"hplmn", ToJson(m_base->config->hplmn)}, + {"imei", ::ToJson(m_base->config->imei)}, + {"imeisv", ::ToJson(m_base->config->imeiSv)}, + {"ecall-only", ::ToJson(m_base->nasTask->usim->m_isECallOnly)}, + }); + + sendResult(msg.address, json.dumpYaml()); break; } case app::UeCliCommand::TIMERS: { @@ -134,7 +152,7 @@ void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg) break; } case app::UeCliCommand::DE_REGISTER: { - m_base->nasTask->mm->sendDeregistration(msg.cmd->deregCause); + m_base->nasTask->mm->deregistrationRequired(msg.cmd->deregCause); if (msg.cmd->deregCause != EDeregCause::SWITCH_OFF) sendResult(msg.address, "De-registration procedure triggered"); @@ -154,7 +172,7 @@ void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg) break; } case app::UeCliCommand::PS_ESTABLISH: { - SessionConfig config{}; + SessionConfig config; config.type = nas::EPduSessionType::IPV4; config.isEmergency = msg.cmd->isEmergency; config.apn = msg.cmd->apn; @@ -163,28 +181,77 @@ void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg) sendResult(msg.address, "PDU session establishment procedure triggered"); break; } - case app::UeCliCommand::COVERAGE: { - auto &map = m_base->rlsTask->m_activeMeasurements; - if (map.empty()) + case app::UeCliCommand::PS_LIST: { + Json json = Json::Obj({}); + for (auto *pduSession : m_base->nasTask->sm->m_pduSessions) { - sendResult(msg.address, "No cell exists in the range"); - break; + if (pduSession->psi == 0 || pduSession->psState == EPsState::INACTIVE) + continue; + + auto obj = Json::Obj({ + {"state", ToJson(pduSession->psState)}, + {"session-type", ToJson(pduSession->sessionType)}, + {"apn", ::ToJson(pduSession->apn)}, + {"s-nssai", ToJson(pduSession->sNssai)}, + {"emergency", pduSession->isEmergency}, + {"address", ::ToJson(pduSession->pduAddress)}, + {"ambr", ::ToJson(pduSession->sessionAmbr)}, + {"data-pending", pduSession->uplinkPending}, + }); + + json.put("PDU Session" + std::to_string(pduSession->psi), obj); } + sendResult(msg.address, json.dumpYaml()); + break; + } + case app::UeCliCommand::RLS_STATE: { + Json json = Json::Obj({ + {"sti", OctetString::FromOctet8(m_base->rlsTask->m_shCtx->sti).toHexString()}, + {"gnb-search-space", ::ToJson(m_base->config->gnbSearchList)}, + }); + sendResult(msg.address, json.dumpYaml()); + break; + } + case app::UeCliCommand::COVERAGE: { + Json json = Json::Obj({}); - std::vector cellInfo{}; - for (auto &entry : map) + const auto &cells = m_base->rrcTask->m_cellDesc; + for (auto &item : cells) { - auto &measurement = entry.second; - cellInfo.push_back(Json::Obj({ - {"gnb", measurement.gnbName}, - {"plmn", ToJson(measurement.cellId.plmn)}, - {"nci", measurement.cellId.nci}, - {"tac", measurement.tac}, - {"signal", std::to_string(measurement.dbm) + "dBm [" + SignalDescription(measurement.dbm) + "]"}, - })); + auto &cell = item.second; + + auto mib = Json{}; + auto sib1 = Json{}; + + if (cell.mib.hasMib) + { + mib = Json::Obj({ + {"barred", cell.mib.isBarred}, + {"intra-freq-reselection", + std::string{cell.mib.isIntraFreqReselectAllowed ? "allowed" : "not-allowed"}}, + }); + } + if (cell.sib1.hasSib1) + { + sib1 = Json::Obj({ + {"nr-cell-id", utils::IntToHex(cell.sib1.nci)}, + {"plmn", ToJson(cell.sib1.plmn)}, + {"tac", cell.sib1.tac}, + {"operator-reserved", cell.sib1.isReserved}, + }); + } + + auto obj = Json::Obj({{"signal", std::to_string(cell.dbm) + " dBm (" + SignalDescription(cell.dbm) + ")"}, + {"mib", mib}, + {"sib1", sib1}}); + + json.put("[" + std::to_string(item.first) + "]", obj); } - sendResult(msg.address, Json::Arr(cellInfo).dumpYaml()); + if (cells.empty()) + json = "No cell available"; + + sendResult(msg.address, json.dumpYaml()); break; } } diff --git a/src/ue/app/cmd_handler.hpp b/src/ue/app/cmd_handler.hpp index 9a107f5a8..ab5c9ba67 100644 --- a/src/ue/app/cmd_handler.hpp +++ b/src/ue/app/cmd_handler.hpp @@ -24,7 +24,7 @@ class UeCmdHandler { } - void handleCmd(NwUeCliCommand &msg); + void handleCmd(NmUeCliCommand &msg); private: void pauseTasks(); @@ -32,7 +32,7 @@ class UeCmdHandler bool isAllPaused(); private: - void handleCmdImpl(NwUeCliCommand &msg); + void handleCmdImpl(NmUeCliCommand &msg); private: void sendResult(const InetAddress &address, const std::string &output); diff --git a/src/ue/app/task.cpp b/src/ue/app/task.cpp index fc4c8bcfb..d1376b0b3 100644 --- a/src/ue/app/task.cpp +++ b/src/ue/app/task.cpp @@ -51,33 +51,18 @@ void UeAppTask::onLoop() switch (msg->msgType) { - case NtsMessageType::UE_RLS_TO_APP: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwUeRlsToApp::DATA_PDU_DELIVERY: { - auto *tunTask = m_tunTasks[w->psi]; - if (tunTask) - { - auto *nw = new NwAppToTun(NwAppToTun::DATA_PDU_DELIVERY); - nw->psi = w->psi; - nw->data = std::move(w->pdu); - tunTask->push(nw); - } - break; - } - } - break; - } case NtsMessageType::UE_TUN_TO_APP: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeTunToApp::DATA_PDU_DELIVERY: { - handleUplinkDataRequest(w->psi, std::move(w->data)); + case NmUeTunToApp::DATA_PDU_DELIVERY: { + auto *m = new NmUeAppToNas(NmUeAppToNas::UPLINK_DATA_DELIVERY); + m->psi = w->psi; + m->data = std::move(w->data); + m_base->nasTask->push(m); break; } - case NwUeTunToApp::TUN_ERROR: { + case NmUeTunToApp::TUN_ERROR: { m_logger->err("TUN failure [%s]", w->error.c_str()); break; } @@ -85,28 +70,39 @@ void UeAppTask::onLoop() break; } case NtsMessageType::UE_NAS_TO_APP: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeNasToApp::PERFORM_SWITCH_OFF: { + case NmUeNasToApp::PERFORM_SWITCH_OFF: { setTimer(SWITCH_OFF_TIMER_ID, SWITCH_OFF_DELAY); break; } + case NmUeNasToApp::DOWNLINK_DATA_DELIVERY: { + auto *tunTask = m_tunTasks[w->psi]; + if (tunTask) + { + auto *m = new NmAppToTun(NmAppToTun::DATA_PDU_DELIVERY); + m->psi = w->psi; + m->data = std::move(w->data); + tunTask->push(m); + } + break; + } } break; } case NtsMessageType::UE_STATUS_UPDATE: { - receiveStatusUpdate(*dynamic_cast(msg)); + receiveStatusUpdate(*dynamic_cast(msg)); break; } case NtsMessageType::UE_CLI_COMMAND: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); UeCmdHandler handler{m_base}; handler.handleCmd(*w); break; } case NtsMessageType::TIMER_EXPIRED: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); if (w->timerId == SWITCH_OFF_TIMER_ID) { m_logger->info("UE device is switching off"); @@ -121,30 +117,17 @@ void UeAppTask::onLoop() delete msg; } -void UeAppTask::receiveStatusUpdate(NwUeStatusUpdate &msg) +void UeAppTask::receiveStatusUpdate(NmUeStatusUpdate &msg) { - if (msg.what == NwUeStatusUpdate::SESSION_ESTABLISHMENT) + if (msg.what == NmUeStatusUpdate::SESSION_ESTABLISHMENT) { auto *session = msg.pduSession; - UePduSessionInfo sessionInfo{}; - sessionInfo.psi = session->psi; - - sessionInfo.apn = session->apn; - sessionInfo.sNssai = session->sNssai; - sessionInfo.type = nas::utils::EnumToString(session->sessionType); - if (session->pduAddress.has_value()) - sessionInfo.address = utils::OctetStringToIp(session->pduAddress->pduAddressInformation); - sessionInfo.isEmergency = session->isEmergency; - sessionInfo.uplinkPending = false; - - m_pduSessions[session->psi] = std::move(sessionInfo); - setupTunInterface(session); return; } - if (msg.what == NwUeStatusUpdate::SESSION_RELEASE) + if (msg.what == NmUeStatusUpdate::SESSION_RELEASE) { if (m_tunTasks[msg.psi] != nullptr) { @@ -153,15 +136,10 @@ void UeAppTask::receiveStatusUpdate(NwUeStatusUpdate &msg) m_tunTasks[msg.psi] = nullptr; } - if (m_pduSessions[msg.psi].has_value()) - { - m_logger->info("PDU session[%d] released", msg.psi); - m_pduSessions[msg.psi] = {}; - } return; } - if (msg.what == NwUeStatusUpdate::CM_STATE) + if (msg.what == NmUeStatusUpdate::CM_STATE) { m_cmState = msg.cmState; return; @@ -227,40 +205,4 @@ void UeAppTask::setupTunInterface(const PduSession *pduSession) allocatedName.c_str(), ipAddress.c_str()); } -void UeAppTask::handleUplinkDataRequest(int psi, OctetString &&data) -{ - if (!m_pduSessions[psi].has_value()) - return; - - if (m_cmState == ECmState::CM_CONNECTED) - { - if (m_pduSessions[psi]->uplinkPending) - { - m_pduSessions[psi]->uplinkPending = false; - - auto *w = new NwUeAppToNas(NwUeAppToNas::UPLINK_STATUS_CHANGE); - w->psi = psi; - w->isPending = false; - m_base->nasTask->push(w); - } - - auto *nw = new NwUeAppToRls(NwUeAppToRls::DATA_PDU_DELIVERY); - nw->psi = psi; - nw->pdu = std::move(data); - m_base->rlsTask->push(nw); - } - else - { - if (!m_pduSessions[psi]->uplinkPending) - { - m_pduSessions[psi]->uplinkPending = true; - - auto *w = new NwUeAppToNas(NwUeAppToNas::UPLINK_STATUS_CHANGE); - w->psi = psi; - w->isPending = true; - m_base->nasTask->push(w); - } - } -} - } // namespace nr::ue diff --git a/src/ue/app/task.hpp b/src/ue/app/task.hpp index ceab73bf8..128758970 100644 --- a/src/ue/app/task.hpp +++ b/src/ue/app/task.hpp @@ -27,7 +27,6 @@ class UeAppTask : public NtsTask TaskBase *m_base; std::unique_ptr m_logger; - std::array, 16> m_pduSessions{}; std::array m_tunTasks{}; ECmState m_cmState{}; @@ -43,9 +42,8 @@ class UeAppTask : public NtsTask void onQuit() override; private: - void receiveStatusUpdate(NwUeStatusUpdate &msg); + void receiveStatusUpdate(NmUeStatusUpdate &msg); void setupTunInterface(const PduSession *pduSession); - void handleUplinkDataRequest(int psi, OctetString &&data); }; } // namespace nr::ue diff --git a/src/ue/nas/mm/access.cpp b/src/ue/nas/mm/access.cpp index cb313331a..4c655303d 100644 --- a/src/ue/nas/mm/access.cpp +++ b/src/ue/nas/mm/access.cpp @@ -18,17 +18,28 @@ bool NasMm::hasEmergency() { // Indicates emergency services are required (even if registered for normal initial registration) // This happens if it 'has' or 'need' some emergency PDU Session, as well. - if (m_rmState == ERmState::RM_REGISTERED && m_registeredForEmergency) return true; + if (m_mmState == EMmState::MM_REGISTERED_INITIATED && m_lastRegistrationRequest && m_lastRegistrationRequest->registrationType.registrationType == nas::ERegistrationType::EMERGENCY_REGISTRATION) return true; - // TODO: Other case which is an emergency PDU session need to be established (and wanted to be - // established soon) + if (m_procCtl.initialRegistration && m_procCtl.initialRegistration == EInitialRegCause::EMERGENCY_SERVICES) + return true; + + if (m_procCtl.mobilityRegistration && m_procCtl.mobilityRegistration == ERegUpdateCause::EMERGENCY_CASE) + return true; + + if (m_procCtl.serviceRequest && m_procCtl.serviceRequest == EServiceReqCause::EMERGENCY_FALLBACK) + return true; + if (m_sm->anyEmergencySession()) return true; + + if (m_usim->m_emgIndication) + return true; + return false; } @@ -45,24 +56,20 @@ void NasMm::setN1Capability(bool enabled) bool NasMm::isInNonAllowedArea() { - if (!m_usim->isValid()) - return false; - if (!m_usim->m_currentPlmn.has_value()) + auto currentCell = m_base->shCtx.currentCell.get(); + if (!currentCell.hasValue()) return false; - auto &plmn = *m_usim->m_currentPlmn; - - if (nas::utils::ServiceAreaListForbidsPlmn(m_usim->m_serviceAreaList, nas::utils::PlmnFrom(plmn))) + auto plmn = currentCell.plmn; + if (nas::utils::ServiceAreaListForbidsPlmn(m_storage->serviceAreaList->get(), nas::utils::PlmnFrom(plmn))) return true; - if (m_usim->m_servingCell.has_value()) + int tac = currentCell.tac; + if (nas::utils::ServiceAreaListForbidsTai(m_storage->serviceAreaList->get(), + nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(plmn), octet3{tac}})) { - if (nas::utils::ServiceAreaListForbidsTai( - m_usim->m_serviceAreaList, - nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(plmn), octet3{m_usim->m_servingCell->tac}})) - return true; + return true; } - return false; } diff --git a/src/ue/nas/mm/auth.cpp b/src/ue/nas/mm/auth.cpp index 0ccbf471e..2c2829124 100644 --- a/src/ue/nas/mm/auth.cpp +++ b/src/ue/nas/mm/auth.cpp @@ -16,6 +16,8 @@ namespace nr::ue void NasMm::receiveAuthenticationRequest(const nas::AuthenticationRequest &msg) { + m_logger->debug("Authentication Request received"); + if (!m_usim->isValid()) { m_logger->warn("Authentication request is ignored. USIM is invalid"); @@ -32,6 +34,10 @@ void NasMm::receiveAuthenticationRequest(const nas::AuthenticationRequest &msg) void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &msg) { + Plmn currentPlmn = m_base->shCtx.getCurrentPlmn(); + if (!currentPlmn.hasValue()) + return; + auto sendEapFailure = [this](std::unique_ptr &&eap) { // Clear RAND and RES* stored in volatile memory m_usim->m_rand = {}; @@ -109,7 +115,7 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms return; } - auto snn = keys::ConstructServingNetworkName(*m_usim->m_currentPlmn); + auto snn = keys::ConstructServingNetworkName(currentPlmn); if (receivedEap.attributes.getKdfInput() != OctetString::FromAscii(snn)) { @@ -194,7 +200,7 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms m_usim->m_nonCurrentNsCtx->keys.kAusf = keys::CalculateKAusfFor5gAka(milenage.ck, milenage.ik, snn, sqnXorAk); m_usim->m_nonCurrentNsCtx->keys.abba = msg.abba.rawData.copy(); - keys::DeriveKeysSeafAmf(*m_base->config, *m_usim->m_currentPlmn, *m_usim->m_nonCurrentNsCtx); + keys::DeriveKeysSeafAmf(*m_base->config, currentPlmn, *m_usim->m_nonCurrentNsCtx); // Send response m_nwConsecutiveAuthFailure = 0; @@ -255,6 +261,10 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest &msg) { + Plmn currentPLmn = m_base->shCtx.getCurrentPlmn(); + if (!currentPLmn.hasValue()) + return; + auto sendFailure = [this](nas::EMmCause cause, std::optional &&auts = std::nullopt) { if (cause != nas::EMmCause::SYNCH_FAILURE) m_logger->err("Sending Authentication Failure with cause [%s]", nas::utils::EnumToString(cause)); @@ -344,7 +354,7 @@ void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest & auto milenage = calculateMilenage(m_usim->m_sqnMng->getSqn(), rand, false); auto ckIk = OctetString::Concat(milenage.ck, milenage.ik); auto sqnXorAk = OctetString::Xor(m_usim->m_sqnMng->getSqn(), milenage.ak); - auto snn = keys::ConstructServingNetworkName(*m_usim->m_currentPlmn); + auto snn = keys::ConstructServingNetworkName(currentPLmn); // Store the relevant parameters m_usim->m_rand = rand.copy(); @@ -357,7 +367,7 @@ void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest & m_usim->m_nonCurrentNsCtx->keys.kAusf = keys::CalculateKAusfFor5gAka(milenage.ck, milenage.ik, snn, sqnXorAk); m_usim->m_nonCurrentNsCtx->keys.abba = msg.abba.rawData.copy(); - keys::DeriveKeysSeafAmf(*m_base->config, *m_usim->m_currentPlmn, *m_usim->m_nonCurrentNsCtx); + keys::DeriveKeysSeafAmf(*m_base->config, currentPLmn, *m_usim->m_nonCurrentNsCtx); // Send response m_nwConsecutiveAuthFailure = 0; @@ -411,7 +421,7 @@ void NasMm::receiveAuthenticationResult(const nas::AuthenticationResult &msg) void NasMm::receiveAuthenticationReject(const nas::AuthenticationReject &msg) { - m_logger->err("Authentication Reject received."); + m_logger->err("Authentication Reject received"); // The RAND and RES* values stored in the ME shall be deleted and timer T3516, if running, shall be stopped m_usim->m_rand = {}; @@ -430,9 +440,9 @@ void NasMm::receiveAuthenticationReject(const nas::AuthenticationReject &msg) switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); // Delete the stored 5G-GUTI, TAI list, last visited registered TAI and ngKSI. The USIM shall be considered invalid // until switching off the UE or the UICC containing the USIM is removed - m_usim->m_storedGuti = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_taiList = {}; + m_storage->storedGuti->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->taiList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; m_usim->invalidate(); @@ -444,7 +454,7 @@ void NasMm::receiveAuthenticationReject(const nas::AuthenticationReject &msg) m_timers->t3519.stop(); m_timers->t3521.stop(); // .. and enter state 5GMM-DEREGISTERED. - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); } void NasMm::receiveEapSuccessMessage(const eap::Eap &eap) @@ -520,8 +530,11 @@ bool NasMm::networkFailingTheAuthCheck(bool hasChance) // END m_logger->err("Network failing the authentication check"); - localReleaseConnection(); - // TODO: treat the active cell as barred + + if (m_cmState == ECmState::CM_CONNECTED) + localReleaseConnection(true); + + m_timers->t3520.stop(); return true; } diff --git a/src/ue/nas/mm/base.cpp b/src/ue/nas/mm/base.cpp index 857cc91db..b9bb367bb 100644 --- a/src/ue/nas/mm/base.cpp +++ b/src/ue/nas/mm/base.cpp @@ -18,20 +18,59 @@ namespace nr::ue { -NasMm::NasMm(TaskBase *base, UeTimers *timers) : m_base{base}, m_timers{timers}, m_sm{nullptr} +static EMmState GetMmStateFromSubState(EMmSubState subState) +{ + switch (subState) + { + case EMmSubState::MM_NULL_PS: + return EMmState::MM_NULL; + case EMmSubState::MM_DEREGISTERED_PS: + case EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE: + case EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE: + case EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION: + case EMmSubState::MM_DEREGISTERED_PLMN_SEARCH: + case EMmSubState::MM_DEREGISTERED_NO_SUPI: + case EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE: + case EMmSubState::MM_DEREGISTERED_ECALL_INACTIVE: + case EMmSubState::MM_DEREGISTERED_INITIAL_REGISTRATION_NEEDED: + return EMmState::MM_DEREGISTERED; + case EMmSubState::MM_REGISTERED_INITIATED_PS: + return EMmState::MM_REGISTERED_INITIATED; + case EMmSubState::MM_REGISTERED_PS: + case EMmSubState::MM_REGISTERED_NORMAL_SERVICE: + case EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE: + case EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE: + case EMmSubState::MM_REGISTERED_LIMITED_SERVICE: + case EMmSubState::MM_REGISTERED_PLMN_SEARCH: + case EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE: + case EMmSubState::MM_REGISTERED_UPDATE_NEEDED: + return EMmState::MM_REGISTERED; + case EMmSubState::MM_DEREGISTERED_INITIATED_PS: + return EMmState::MM_DEREGISTERED_INITIATED; + case EMmSubState::MM_SERVICE_REQUEST_INITIATED_PS: + return EMmState::MM_SERVICE_REQUEST_INITIATED; + } + + std::terminate(); +} + +NasMm::NasMm(TaskBase *base, NasTimers *timers) : m_base{base}, m_timers{timers}, m_sm{}, m_usim{}, m_procCtl{} { m_logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "nas"); m_rmState = ERmState::RM_DEREGISTERED; m_cmState = ECmState::CM_IDLE; m_mmState = EMmState::MM_DEREGISTERED; - m_mmSubState = EMmSubState::MM_DEREGISTERED_NA; + m_mmSubState = EMmSubState::MM_DEREGISTERED_PS; + + m_storage = new MmStorage(m_base); } void NasMm::onStart(NasSm *sm, Usim *usim) { m_sm = sm; m_usim = usim; + triggerMmCycle(); } @@ -42,97 +81,143 @@ void NasMm::onQuit() void NasMm::triggerMmCycle() { - m_base->nasTask->push(new NwUeNasToNas(NwUeNasToNas::PERFORM_MM_CYCLE)); + m_base->nasTask->push(new NmUeNasToNas(NmUeNasToNas::PERFORM_MM_CYCLE)); } void NasMm::performMmCycle() { + /* Do nothing in case of MM-NULL */ if (m_mmState == EMmState::MM_NULL) return; - if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NA) - { - if (switchToECallInactivityIfNeeded()) - return; + auto currentCell = m_base->shCtx.currentCell.get(); + Tai currentTai = Tai{currentCell.plmn, currentCell.tac}; - if (m_usim->isValid()) + /* Perform substate selection in case of primary substate */ + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_PS) + { + if (currentCell.hasValue()) { - if (m_usim->m_servingCell.has_value()) + if (!m_usim->isValid()) + switchMmState(EMmSubState::MM_DEREGISTERED_NO_SUPI); + else if (isInNonAllowedArea()) { - auto cellCategory = m_usim->m_servingCell->cellCategory; - - if (cellCategory == ECellCategory::SUITABLE_CELL) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); - else if (cellCategory == ECellCategory::ACCEPTABLE_CELL) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); - else - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + // TODO: check this later + switchMmState(EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE); } + else if (currentCell.category == ECellCategory::SUITABLE_CELL) + switchMmState(EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); + else if (currentCell.category == ECellCategory::ACCEPTABLE_CELL) + switchMmState(EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); else - { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); - } + switchMmState(EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); } else { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NO_SUPI); + switchMmState(EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); } + return; } - if (m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH || - m_mmSubState == EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE || - m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH || - m_mmSubState == EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE) + if (m_mmSubState == EMmSubState::MM_REGISTERED_PS) { - int64_t current = utils::CurrentTimeMillis(); - int64_t backoff = (m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH || - m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH) - ? -1 - : 1500; - - if (current - m_lastPlmnSearchTrigger > backoff) + auto cell = m_base->shCtx.currentCell.get(); + if (cell.hasValue()) { - m_base->rrcTask->push(new NwUeNasToRrc(NwUeNasToRrc::PLMN_SEARCH_REQUEST)); - m_lastPlmnSearchTrigger = current; + if (isInNonAllowedArea()) + { + // TODO: check this later + switchMmState(EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE); + } + else if (cell.category == ECellCategory::SUITABLE_CELL) + switchMmState(EMmSubState::MM_REGISTERED_NORMAL_SERVICE); + else if (cell.category == ECellCategory::ACCEPTABLE_CELL) + switchMmState(EMmSubState::MM_REGISTERED_LIMITED_SERVICE); + else + switchMmState(EMmSubState::MM_REGISTERED_PLMN_SEARCH); + } + else + { + switchMmState(EMmSubState::MM_REGISTERED_PLMN_SEARCH); } return; } - if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE) + /* PLMN selection related */ + if (m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH || + m_mmSubState == EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE || + m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH || + m_mmSubState == EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE) { - if (!m_timers->t3346.isRunning()) - sendInitialRegistration(EInitialRegCause::MM_DEREG_NORMAL_SERVICE); - return; + performPlmnSelection(); } + /* eCall inactivity related */ + if (m_mmSubState != EMmSubState::MM_REGISTERED_PLMN_SEARCH && + m_mmSubState != EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE && + m_mmSubState != EMmSubState::MM_DEREGISTERED_PLMN_SEARCH && + m_mmSubState != EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE) + { + if (switchToECallInactivityIfNeeded()) + return; + } if (m_mmState == EMmState::MM_REGISTERED) { if (startECallInactivityIfNeeded()) return; } - if (m_mmSubState == EMmSubState::MM_REGISTERED_NA) + /* Try to start procedures */ + invokeProcedures(); + + /* Registration related */ + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE && !m_timers->t3346.isRunning()) + initialRegistrationRequired(EInitialRegCause::MM_DEREG_NORMAL_SERVICE); + else if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE && hasEmergency()) + initialRegistrationRequired(EInitialRegCause::EMERGENCY_SERVICES); + else if (hasEmergency()) + { + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE || + m_mmSubState == EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION || + m_mmSubState == EMmSubState::MM_DEREGISTERED_NO_SUPI || + m_mmSubState == EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE || + m_mmSubState == EMmSubState::MM_REGISTERED_LIMITED_SERVICE) + initialRegistrationRequired(EInitialRegCause::EMERGENCY_SERVICES); + } + + if (m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE && hasEmergency()) { - if (m_usim->m_servingCell.has_value()) - { - auto cellCategory = m_usim->m_servingCell->cellCategory; + // 5.2.3.2.3; f) + // "may perform de-registration locally and initiate a registration procedure for initial registration for + // emergency services even if timer T3346 is running" - if (cellCategory == ECellCategory::SUITABLE_CELL) - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE); - else if (cellCategory == ECellCategory::ACCEPTABLE_CELL) - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_LIMITED_SERVICE); - else - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_PLMN_SEARCH); - } - else - { - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_PLMN_SEARCH); - } + // UE will try to send initial emergency registration after local de-registration + performLocalDeregistration(); } + + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ECALL_INACTIVE && hasEmergency()) + { + // "If the UE receives a request from upper layers to establish an eCall over IMS, the UE enters state + // 5GMM-DEREGISTERED.ATTEMPTING-REGISTRATION. The UE then uses the relevant 5GMM and 5GSM procedures to + // establish the eCall over IMS at the earliest opportunity" + switchMmState(EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); + } + + /* Process TAI changes if any */ + if (currentTai.hasValue() && + !nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{currentTai})) + { + if (m_rmState == ERmState::RM_REGISTERED) + mobilityUpdatingRequired(ERegUpdateCause::ENTER_UNLISTED_TRACKING_AREA); + } + else + m_storage->lastVisitedRegisteredTai->set(currentTai); } -void NasMm::switchMmState(EMmState state, EMmSubState subState) +void NasMm::switchMmState(EMmSubState subState) { + EMmState state = GetMmStateFromSubState(subState); + ERmState oldRmState = m_rmState; if (state == EMmState::MM_DEREGISTERED || state == EMmState::MM_REGISTERED_INITIATED) m_rmState = ERmState::RM_DEREGISTERED; @@ -154,6 +239,8 @@ void NasMm::switchMmState(EMmState state, EMmSubState subState) m_mmState = state; m_mmSubState = subState; + m_lastTimeMmStateChange = utils::CurrentTimeMillis(); + onSwitchMmState(oldState, m_mmState, oldSubState, m_mmSubState); if (m_base->nodeListener) @@ -180,7 +267,7 @@ void NasMm::switchCmState(ECmState state) onSwitchCmState(oldState, m_cmState); - auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::CM_STATE); + auto *statusUpdate = new NmUeStatusUpdate(NmUeStatusUpdate::CM_STATE); statusUpdate->cmState = m_cmState; m_base->appTask->push(statusUpdate); @@ -195,15 +282,15 @@ void NasMm::switchCmState(ECmState state) void NasMm::switchUState(E5UState state) { - E5UState oldState = m_usim->m_uState; - m_usim->m_uState = state; + E5UState oldState = m_storage->uState->get(); + m_storage->uState->set(state); - onSwitchUState(oldState, m_usim->m_uState); + onSwitchUState(oldState, state); if (m_base->nodeListener) { m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::U5, - ToJson(oldState).str(), ToJson(m_usim->m_uState).str()); + ToJson(oldState).str(), ToJson(state).str()); } if (state != oldState) @@ -214,9 +301,9 @@ void NasMm::switchUState(E5UState state) void NasMm::onSwitchMmState(EMmState oldState, EMmState newState, EMmSubState oldSubState, EMmSubState newSubSate) { - // The UE shall mark the 5G NAS security context on the USIM or in the non-volatile memory as invalid when the UE + // "The UE shall mark the 5G NAS security context on the USIM or in the non-volatile memory as invalid when the UE // initiates an initial registration procedure as described in subclause 5.5.1.2 or when the UE leaves state - // 5GMM-DEREGISTERED for any other state except 5GMM-NULL. + // 5GMM-DEREGISTERED for any other state except 5GMM-NULL." if (oldState == EMmState::MM_DEREGISTERED && newState != EMmState::MM_DEREGISTERED && newState != EMmState::MM_NULL) { if (m_usim->m_currentNsCtx || m_usim->m_nonCurrentNsCtx) @@ -228,18 +315,38 @@ void NasMm::onSwitchMmState(EMmState oldState, EMmState newState, EMmSubState ol } } - // If the UE enters the 5GMM state 5GMM-DEREGISTERED or 5GMM-NULL, - // The RAND and RES* values stored in the ME shall be deleted and timer T3516, if running, shall be stopped + // "If the UE enters the 5GMM state 5GMM-DEREGISTERED or 5GMM-NULL, + // The RAND and RES* values stored in the ME shall be deleted and timer T3516, if running, shall be stopped" if (newState == EMmState::MM_DEREGISTERED || newState == EMmState::MM_NULL) { m_usim->m_rand = {}; m_usim->m_resStar = {}; m_timers->t3516.stop(); } + + // If NAS layer starts PLMN SEARCH in CM-CONNECTED, we switch to CM-IDLE. Because PLMN search is an idle + // operation and RRC expects it in RRC-IDLE state. (This may happen in for example initial registration reject with + // switch to PLMN search state) + if (m_cmState == ECmState::CM_CONNECTED && (m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH || + m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH)) + { + localReleaseConnection(false); + } + + // "Timer T3512 is stopped when the UE enters ... the 5GMM-DEREGISTERED state over 3GPP access" + if (newState == EMmState::MM_DEREGISTERED) + m_timers->t3512.stop(); } void NasMm::onSwitchRmState(ERmState oldState, ERmState newState) { + if (oldState == ERmState::RM_REGISTERED && newState == ERmState::RM_REGISTERED) + { + // "The UE shall delete (List of equivalent PLMNs) ... when the UE registered for emergency services + // enters the state 5GMM-DEREGISTERED" + if (m_registeredForEmergency) + m_storage->equivalentPlmnList->clear(); + } } void NasMm::onSwitchCmState(ECmState oldState, ECmState newState) @@ -258,7 +365,7 @@ void NasMm::onSwitchCmState(ECmState oldState, ECmState newState) if (regType == nas::ERegistrationType::INITIAL_REGISTRATION || regType == nas::ERegistrationType::EMERGENCY_REGISTRATION) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); switchUState(E5UState::U2_NOT_UPDATED); handleAbnormalInitialRegFailure(regType); @@ -271,23 +378,34 @@ void NasMm::onSwitchCmState(ECmState oldState, ECmState newState) // 5.5.2.2.6 Abnormal cases in the UE (in de-registration) else if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) { - // The de-registration procedure shall be aborted and the UE proceeds as follows: + // "The de-registration procedure shall be aborted and the UE proceeds as follows: // if the de-registration procedure was performed due to disabling of 5GS services, the UE shall enter the - // 5GMM-NULL state; + // 5GMM-NULL state;" if (m_lastDeregCause == EDeregCause::DISABLE_5G) - switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); - // if the de-registration type "normal de-registration" was requested for reasons other than disabling of - // 5GS services, the UE shall enter the 5GMM-DEREGISTERED state. + switchMmState(EMmSubState::MM_NULL_PS); + // "if the de-registration type "normal de-registration" was requested for reasons other than disabling of + // 5GS services, the UE shall enter the 5GMM-DEREGISTERED state." else if (m_lastDeregistrationRequest->deRegistrationType.switchOff == nas::ESwitchOff::NORMAL_DE_REGISTRATION) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); } - // If the UE enters the 5GMM-IDLE, the RAND and RES* values stored - // in the ME shall be deleted and timer T3516, if running, shall be stopped + // "If the UE enters the 5GMM-IDLE, the RAND and RES* values stored in the ME shall be deleted and timer T3516, + // if running, shall be stopped" m_usim->m_rand = {}; m_usim->m_resStar = {}; m_timers->t3516.stop(); + + // "Timer T3512 is reset and started with its initial value, when the UE changes from 5GMM-CONNECTED over 3GPP + // access to 5GMM-IDLE mode over 3GPP access" + m_timers->t3512.start(); + } + + if (oldState == ECmState::CM_IDLE && newState == ECmState::CM_CONNECTED) + { + // "Timer T3512 is stopped when the UE enters 5GMM-CONNECTED mode over 3GPP access or the 5GMM-DEREGISTERED + // state over 3GPP access" + m_timers->t3512.stop(); } } @@ -295,4 +413,36 @@ void NasMm::onSwitchUState(E5UState oldState, E5UState newState) { } +void NasMm::onSwitchOn() +{ + resetRegAttemptCounter(); +} + +void NasMm::onSimInsertion() +{ + resetRegAttemptCounter(); + + switchMmState(EMmSubState::MM_DEREGISTERED_PS); +} + +void NasMm::onSwitchOff() +{ + m_storage->serviceAreaList->clear(); + + m_storage->forbiddenTaiListRoaming->clear(); + m_storage->forbiddenTaiListRps->clear(); +} + +void NasMm::onSimRemoval() +{ + m_storage->equivalentPlmnList->clear(); + + m_storage->serviceAreaList->clear(); + + m_storage->forbiddenTaiListRoaming->clear(); + m_storage->forbiddenTaiListRps->clear(); + + switchMmState(EMmSubState::MM_DEREGISTERED_PS); +} + } // namespace nr::ue diff --git a/src/ue/nas/mm/config.cpp b/src/ue/nas/mm/config.cpp index ada775a52..71a78fe91 100644 --- a/src/ue/nas/mm/config.cpp +++ b/src/ue/nas/mm/config.cpp @@ -8,6 +8,8 @@ #include "mm.hpp" +#include + #include #include @@ -16,6 +18,8 @@ namespace nr::ue void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &msg) { + m_logger->debug("Configuration Update Command received"); + // Abnormal case: 5.4.4.5, c) Generic UE configuration update and de-registration procedure collision if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) { @@ -39,8 +43,8 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms if (msg.guti.has_value() && msg.guti->type == nas::EIdentityType::GUTI) { hasNewConfig = true; - m_usim->m_storedSuci = {}; - m_usim->m_storedGuti = *msg.guti; + m_storage->storedSuci->clear(); + m_storage->storedGuti->set(*msg.guti); m_timers->t3519.stop(); } @@ -48,18 +52,33 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms // list as valid and the old TAI list as invalid; otherwise, the UE shall consider the old TAI list as valid." if (msg.taiList.has_value()) { - hasNewConfig = true; - m_usim->m_taiList = *msg.taiList; + if (nas::utils::TaiListSize(*msg.taiList) == 0) + { + m_logger->err("Invalid TAI list size"); + sendMmStatus(nas::EMmCause::SEMANTICALLY_INCORRECT_MESSAGE); + } + else + { + hasNewConfig = true; + + m_storage->taiList->set(*msg.taiList); + updateForbiddenTaiListsForAllowedIndications(); + + Tai currentTai = m_base->shCtx.getCurrentTai(); + if (currentTai.hasValue() && + nas::utils::TaiListContains(*msg.taiList, nas::VTrackingAreaIdentity{currentTai})) + m_storage->lastVisitedRegisteredTai->set(currentTai); + } } // "If the UE receives a new service area list in the CONFIGURATION UPDATE COMMAND message, the UE shall consider - // the - // new service area list as valid and the old service area list as invalid; otherwise, the UE shall consider the - // old service area list, if any, as valid." + // the new service area list as valid and the old service area list as invalid; otherwise, the UE shall consider + // the old service area list, if any, as valid." if (msg.serviceAreaList.has_value()) { hasNewConfig = true; - m_usim->m_serviceAreaList = *msg.serviceAreaList; + m_storage->serviceAreaList->set(*msg.serviceAreaList); + updateForbiddenTaiListsForAllowedIndications(); } // "If the UE receives new NITZ information in the CONFIGURATION UPDATE COMMAND message, the UE considers the new @@ -68,27 +87,27 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms if (msg.networkFullName.has_value()) { hasNewConfig = true; - m_usim->m_networkFullName = nas::utils::DeepCopyIe(*msg.networkFullName); + m_storage->networkFullName->set(nas::utils::DeepCopyIe(*msg.networkFullName)); } if (msg.networkShortName.has_value()) { hasNewConfig = true; - m_usim->m_networkShortName = nas::utils::DeepCopyIe(*msg.networkShortName); + m_storage->networkShortName->set(nas::utils::DeepCopyIe(*msg.networkShortName)); } if (msg.localTimeZone.has_value()) { hasNewConfig = true; - m_usim->m_localTimeZone = *msg.localTimeZone; + m_storage->localTimeZone->set(*msg.localTimeZone); } if (msg.universalTimeAndLocalTimeZone.has_value()) { hasNewConfig = true; - m_usim->m_universalTimeAndLocalTimeZone = *msg.universalTimeAndLocalTimeZone; + m_storage->universalTimeAndLocalTimeZone->set(*msg.universalTimeAndLocalTimeZone); } if (msg.networkDaylightSavingTime.has_value()) { hasNewConfig = true; - m_usim->m_networkDaylightSavingTime = *msg.networkDaylightSavingTime; + m_storage->networkDaylightSavingTime->set(*msg.networkDaylightSavingTime); } // "If the UE receives a new allowed NSSAI for the associated access type in the CONFIGURATION UPDATE COMMAND @@ -99,7 +118,7 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms if (msg.allowedNssai.has_value()) { hasNewConfig = true; - m_usim->m_allowedNssai = nas::utils::NssaiTo(*msg.allowedNssai); + m_storage->allowedNssai->set(nas::utils::NssaiTo(*msg.allowedNssai)); } // "If the UE receives a new configured NSSAI in the CONFIGURATION UPDATE COMMAND message, the UE shall consider the @@ -109,7 +128,7 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms if (msg.configuredNssai.has_value()) { hasNewConfig = true; - m_usim->m_configuredNssai = nas::utils::NssaiTo(*msg.configuredNssai); + m_storage->configuredNssai->set(nas::utils::NssaiTo(*msg.configuredNssai)); } // "If the UE receives the Network slicing indication IE in the CONFIGURATION UPDATE COMMAND message with the @@ -130,13 +149,13 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms hasNewConfig = true; for (auto &rejectedSlice : msg.rejectedNssai->list) { - SingleSlice slice{}; + SingleSlice slice; slice.sst = rejectedSlice.sst; slice.sd = rejectedSlice.sd; - auto &list = rejectedSlice.cause == nas::ERejectedSNssaiCause::NA_IN_PLMN ? m_usim->m_rejectedNssaiInPlmn - : m_usim->m_rejectedNssaiInTa; - list.addIfNotExists(slice); + auto &nssai = rejectedSlice.cause == nas::ERejectedSNssaiCause::NA_IN_PLMN ? m_storage->rejectedNssaiInPlmn + : m_storage->rejectedNssaiInTa; + nssai->mutate([slice](auto &value) { value.addIfNotExists(slice); }); } } @@ -186,4 +205,36 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms } } +void NasMm::updateForbiddenTaiListsForAllowedIndications() +{ + // "A tracking area shall be removed from the list of "5GS forbidden tracking areas for roaming", as well as the + // list of "5GS forbidden tracking areas for regional provision of service", if the UE receives the tracking area in + // the TAI list or the Service area list of "allowed tracking areas" in REGISTRATION ACCEPT message or a + // CONFIGURATION UPDATE COMMAND message. The UE shall not remove the tracking area from "5GS forbidden tracking + // areas for roaming" or "5GS forbidden tracking areas for regional provision of service" if the UE is registered + // for emergency services" + + std::unordered_set taiSet; + + m_storage->forbiddenTaiListRoaming->forEach([&taiSet, this](auto &value) { + if (nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{value})) + taiSet.insert(value); + if (nas::utils::ServiceAreaListAllowsTai(m_storage->serviceAreaList->get(), nas::VTrackingAreaIdentity{value})) + taiSet.insert(value); + }); + + m_storage->forbiddenTaiListRps->forEach([&taiSet, this](auto &value) { + if (nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{value})) + taiSet.insert(value); + if (nas::utils::ServiceAreaListAllowsTai(m_storage->serviceAreaList->get(), nas::VTrackingAreaIdentity{value})) + taiSet.insert(value); + }); + + for (auto &tai : taiSet) + { + m_storage->forbiddenTaiListRoaming->remove(tai); + m_storage->forbiddenTaiListRps->remove(tai); + } +} + } // namespace nr::ue diff --git a/src/ue/nas/mm/dereg.cpp b/src/ue/nas/mm/dereg.cpp index 81b5aa599..af6aa5887 100644 --- a/src/ue/nas/mm/dereg.cpp +++ b/src/ue/nas/mm/dereg.cpp @@ -26,16 +26,38 @@ static nas::IEDeRegistrationType MakeDeRegistrationType(EDeregCause deregCause) return res; } -void NasMm::sendDeregistration(EDeregCause deregCause) +EProcRc NasMm::sendDeregistration(EDeregCause deregCause) { + auto currentTai = m_base->shCtx.getCurrentTai(); + if (!currentTai.hasValue()) + return EProcRc::STAY; + if (m_rmState != ERmState::RM_REGISTERED) { m_logger->warn("De-registration could not be triggered. UE is already de-registered"); - return; + return EProcRc::CANCEL; + } + + if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) + return EProcRc::CANCEL; + if (m_mmSubState == EMmSubState::MM_REGISTERED_UPDATE_NEEDED) + return EProcRc::STAY; + + // 5.2.3.2.3 "shall not initiate de-registration procedure unless timer T3346 is running and the current TAI is part + // of the TAI list." + if (m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE) + { + if (!m_timers->t3346.isRunning() || + !nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{currentTai})) + { + return EProcRc::STAY; + } } m_logger->debug("Starting de-registration procedure due to [%s]", ToJson(deregCause).str().c_str()); + updateProvidedGuti(); + auto request = std::make_unique(); request->deRegistrationType = MakeDeRegistrationType(deregCause); @@ -52,7 +74,10 @@ void NasMm::sendDeregistration(EDeregCause deregCause) request->mobileIdentity = getOrGeneratePreferredId(); - sendNasMessage(*request); + auto rc = sendNasMessage(*request); + if (rc != EProcRc::OK) + return rc; + m_lastDeregistrationRequest = std::move(request); m_lastDeregCause = deregCause; m_timers->t3521.resetExpiryCount(); @@ -63,30 +88,31 @@ void NasMm::sendDeregistration(EDeregCause deregCause) m_timers->t3521.start(); } + m_sm->localReleaseAllSessions(); + + switchMmState(EMmSubState::MM_DEREGISTERED_INITIATED_PS); + // TODO: Bu ikisinin burada olması gerektiğinden emin değilim if (deregCause == EDeregCause::SWITCH_OFF) - m_base->appTask->push(new NwUeNasToApp(NwUeNasToApp::PERFORM_SWITCH_OFF)); + { + onSwitchOff(); + m_base->appTask->push(new NmUeNasToApp(NmUeNasToApp::PERFORM_SWITCH_OFF)); + } else if (deregCause == EDeregCause::USIM_REMOVAL) { + onSimRemoval(); m_logger->info("SIM card has been invalidated"); m_usim->invalidate(); } - m_sm->localReleaseAllSessions(); - - if (m_lastDeregistrationRequest->deRegistrationType.switchOff == nas::ESwitchOff::NORMAL_DE_REGISTRATION) - switchMmState(EMmState::MM_DEREGISTERED_INITIATED, EMmSubState::MM_DEREGISTERED_INITIATED_NA); - else - { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); - } + return EProcRc::OK; } void NasMm::receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOriginating &msg) { m_logger->debug("De-registration accept received"); - if (m_mmSubState != EMmSubState::MM_DEREGISTERED_INITIATED_NA) + if (m_mmState != EMmState::MM_DEREGISTERED_INITIATED) { m_logger->warn("De-registration accept message ignored. UE is not in MM_DEREGISTERED_INITIATED"); sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE); @@ -96,14 +122,14 @@ void NasMm::receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOrigina m_timers->t3521.stop(); m_timers->t3519.stop(); - m_usim->m_storedSuci = {}; + m_storage->storedSuci->clear(); m_sm->localReleaseAllSessions(); if (m_lastDeregCause == EDeregCause::DISABLE_5G) - switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + switchMmState(EMmSubState::MM_NULL_PS); else - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); m_logger->info("De-registration is successful"); } @@ -182,8 +208,8 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi // "Upon sending a DEREGISTRATION ACCEPT message, the UE shall delete the rejected NSSAI as specified in // subclause 4.6.2.2." - m_usim->m_rejectedNssaiInTa = {}; - m_usim->m_rejectedNssaiInPlmn = {}; + m_storage->rejectedNssaiInTa->clear(); + m_storage->rejectedNssaiInPlmn->clear(); // Handle 5.5.2.3.4 Abnormal cases in the UE, item b) auto handleAbnormal = [this]() { @@ -191,15 +217,15 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi // set the 5GS update status to 5U2 NOT UPDATED and shall start timer T3502. A UE not supporting S1 mode may // enter the state 5GMM-DEREGISTERED.PLMN-SEARCH in order to perform a PLMN selection according to 3GPP // TS 23.122 [5]; otherwise the UE shall enter the state 5GMM-DEREGISTERED.ATTEMPTING-REGISTRATION." - m_usim->m_storedGuti = {}; - m_usim->m_taiList = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_equivalentPlmnList = {}; + m_storage->storedGuti->clear(); + m_storage->taiList->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->equivalentPlmnList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; switchUState(E5UState::U2_NOT_UPDATED); m_timers->t3502.start(); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + switchMmState(EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); }; // "If the de-registration type indicates "re-registration not required", the UE shall take the actions depending on @@ -214,9 +240,9 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA || cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) { switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); - m_usim->m_storedGuti = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_taiList = {}; + m_storage->storedGuti->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->taiList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; } @@ -228,7 +254,7 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED || cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA) - m_usim->m_equivalentPlmnList = {}; + m_storage->equivalentPlmnList->clear(); if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA || @@ -237,29 +263,49 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi if (cause == nas::EMmCause::PLMN_NOT_ALLOWED) { - nas::utils::AddToPlmnList(m_usim->m_forbiddenPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); + Plmn plmn = m_base->shCtx.getCurrentPlmn(); + if (plmn.hasValue()) + m_storage->forbiddenPlmnList->add(plmn); } - if (cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || - cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) + if (cause == nas::EMmCause::TA_NOT_ALLOWED) { - nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRoaming, *m_usim->m_currentTai); + Tai tai = m_base->shCtx.getCurrentTai(); + if (tai.hasValue()) + { + m_storage->forbiddenTaiListRps->add(tai); + m_storage->serviceAreaList->mutate([&tai](auto &value) { + nas::utils::RemoveFromServiceAreaList(value, nas::VTrackingAreaIdentity{tai}); + }); + } + } + + if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) + { + Tai tai = m_base->shCtx.getCurrentTai(); + if (tai.hasValue()) + { + m_storage->forbiddenTaiListRoaming->add(tai); + m_storage->serviceAreaList->mutate([&tai](auto &value) { + nas::utils::RemoveFromServiceAreaList(value, nas::VTrackingAreaIdentity{tai}); + }); + } } if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + switchMmState(EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); if (cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); + switchMmState(EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); if (cause == nas::EMmCause::CONGESTION) { m_timers->t3346.stop(); switchUState(E5UState::U2_NOT_UPDATED); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); + switchMmState(EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); if (msg.t3346Value.has_value() && nas::utils::HasValue(*msg.t3346Value)) m_timers->t3346.start(*msg.t3346Value); @@ -267,7 +313,7 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) { - switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + switchMmState(EMmSubState::MM_NULL_PS); setN1Capability(false); } @@ -299,4 +345,18 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi } } +void NasMm::performLocalDeregistration() +{ + m_logger->debug("Performing local de-registration"); + + m_timers->t3521.stop(); + m_timers->t3519.stop(); + + m_storage->storedSuci->clear(); + + m_sm->localReleaseAllSessions(); + + switchMmState(EMmSubState::MM_DEREGISTERED_PS); +} + } // namespace nr::ue diff --git a/src/ue/nas/mm/ecall.cpp b/src/ue/nas/mm/ecall.cpp index b1e870199..51c361cc9 100644 --- a/src/ue/nas/mm/ecall.cpp +++ b/src/ue/nas/mm/ecall.cpp @@ -27,6 +27,10 @@ bool NasMm::startECallInactivityIfNeeded() if (!m_usim->m_isECallOnly) return false; + // "5.2.2.3.7 The UE camps on a suitable cell or an acceptable cell in a PLMN selected as specified in 3GPP ..." + if (!m_base->shCtx.hasActiveCell()) + return false; + // The procedure shall be started when // a) the UE is in any 5GMM-REGISTERED substate except substates 5GMM-REGISTERED.PLMN-SEARCH or // 5GMM-REGISTERED.NO-CELL-AVAILABLE;" @@ -57,26 +61,22 @@ bool NasMm::startECallInactivityIfNeeded() m_timers->t3511.stop(); m_timers->t3512.stop(); - // Spec says if the UE is currently registered to the network for 5GS services, but it also says procedure shall - // be started if ... and 5GMM-REGISTERED .... - if (m_rmState != ERmState::RM_REGISTERED) - { - // Therefore just assert that the UE is registered - throw std::runtime_error("UE MM eCall - MM and RM state inconsistent"); - } // And perform de-registration. // NOTE: The items c) and d) is performed after de-registration by the other function, therefore we are just // performing de-registration for now. - sendDeregistration(EDeregCause::ECALL_INACTIVITY); + deregistrationRequired(EDeregCause::ECALL_INACTIVITY); return true; } bool NasMm::switchToECallInactivityIfNeeded() { + if (!m_usim->isValid()) + return false; + if (!m_usim->m_isECallOnly) return false; - if (m_mmState != EMmState::MM_DEREGISTERED) + if (m_mmState != EMmState::MM_DEREGISTERED && m_mmState != EMmState::MM_REGISTERED) return false; if (m_cmState != ECmState::CM_IDLE) @@ -87,15 +87,15 @@ bool NasMm::switchToECallInactivityIfNeeded() return false; // Perform item c) in 5.5.3 - m_usim->m_storedGuti = {}; - m_usim->m_taiList = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_equivalentPlmnList = {}; + m_storage->storedGuti->clear(); + m_storage->taiList->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->equivalentPlmnList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; // Perform item d) in 5.5.3 - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ECALL_INACTIVE); + switchMmState(EMmSubState::MM_DEREGISTERED_ECALL_INACTIVE); return true; } diff --git a/src/ue/nas/mm/identity.cpp b/src/ue/nas/mm/identity.cpp index 7e470d6b9..d95393e9e 100644 --- a/src/ue/nas/mm/identity.cpp +++ b/src/ue/nas/mm/identity.cpp @@ -33,12 +33,12 @@ void NasMm::receiveIdentityRequest(const nas::IdentityRequest &msg) } else if (msg.identityType.value == nas::EIdentityType::GUTI) { - resp.mobileIdentity = m_usim->m_storedGuti; + resp.mobileIdentity = m_storage->storedGuti->get(); } else if (msg.identityType.value == nas::EIdentityType::TMSI) { // TMSI is already a part of GUTI - resp.mobileIdentity = m_usim->m_storedGuti; + resp.mobileIdentity = m_storage->storedGuti->get(); if (resp.mobileIdentity.type != nas::EIdentityType::NO_IDENTITY) { resp.mobileIdentity.type = nas::EIdentityType::TMSI; @@ -57,15 +57,16 @@ void NasMm::receiveIdentityRequest(const nas::IdentityRequest &msg) nas::IE5gsMobileIdentity NasMm::getOrGenerateSuci() { - if (m_timers->t3519.isRunning() && m_usim->m_storedSuci.type != nas::EIdentityType::NO_IDENTITY) - return m_usim->m_storedSuci; + if (m_timers->t3519.isRunning() && m_storage->storedSuci->get().type != nas::EIdentityType::NO_IDENTITY) + return m_storage->storedSuci->get(); + + m_storage->storedSuci->set(generateSuci()); - m_usim->m_storedSuci = generateSuci(); m_timers->t3519.start(); - if (m_usim->m_storedSuci.type == nas::EIdentityType::NO_IDENTITY) + if (m_storage->storedSuci->get().type == nas::EIdentityType::NO_IDENTITY) return {}; - return m_usim->m_storedSuci; + return m_storage->storedSuci->get(); } nas::IE5gsMobileIdentity NasMm::generateSuci() @@ -99,8 +100,8 @@ nas::IE5gsMobileIdentity NasMm::generateSuci() nas::IE5gsMobileIdentity NasMm::getOrGeneratePreferredId() { - if (m_usim->m_storedGuti.type != nas::EIdentityType::NO_IDENTITY) - return m_usim->m_storedGuti; + if (m_storage->storedGuti->get().type != nas::EIdentityType::NO_IDENTITY) + return m_storage->storedGuti->get(); auto suci = getOrGenerateSuci(); if (suci.type != nas::EIdentityType::NO_IDENTITY) diff --git a/src/ue/nas/mm/interface.cpp b/src/ue/nas/mm/interface.cpp deleted file mode 100644 index ba21beb84..000000000 --- a/src/ue/nas/mm/interface.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// -// This file is a part of UERANSIM open source project. -// Copyright (c) 2021 ALİ GÜNGÖR. -// -// The software and all associated files are licensed under GPL-3.0 -// and subject to the terms and conditions defined in LICENSE file. -// - -#include "mm.hpp" - -#include - -static constexpr const int64_t SERVICE_REQUEST_NEEDED_FOR_DATA_THRESHOLD = 1000; - -namespace nr::ue -{ - -void NasMm::handleRrcEvent(const NwUeRrcToNas &msg) -{ - switch (msg.present) - { - case NwUeRrcToNas::RRC_CONNECTION_SETUP: { - handleRrcConnectionSetup(); - break; - } - case NwUeRrcToNas::PLMN_SEARCH_RESPONSE: { - handlePlmnSearchResponse(msg.measurements); - break; - } - case NwUeRrcToNas::NAS_DELIVERY: { - OctetView buffer{msg.nasPdu}; - auto nasMessage = nas::DecodeNasMessage(buffer); - if (nasMessage != nullptr) - receiveNasMessage(*nasMessage); - break; - } - case NwUeRrcToNas::RRC_CONNECTION_RELEASE: { - handleRrcConnectionRelease(); - break; - } - case NwUeRrcToNas::RADIO_LINK_FAILURE: { - handleRadioLinkFailure(); - break; - } - case NwUeRrcToNas::SERVING_CELL_CHANGE: { - handleServingCellChange(msg.servingCell); - break; - } - case NwUeRrcToNas::PAGING: { - handlePaging(msg.pagingTmsi); - break; - } - } -} - -void NasMm::handleNasEvent(const NwUeNasToNas &msg) -{ - switch (msg.present) - { - case NwUeNasToNas::PERFORM_MM_CYCLE: - performMmCycle(); - break; - case NwUeNasToNas::NAS_TIMER_EXPIRE: - onTimerExpire(*msg.timer); - break; - default: - break; - } -} - -bool NasMm::isRegistered() -{ - return m_rmState == ERmState::RM_REGISTERED; -} - -bool NasMm::isRegisteredForEmergency() -{ - return isRegistered() && m_registeredForEmergency; -} - -void NasMm::serviceNeededForUplinkData() -{ - auto currentTime = utils::CurrentTimeMillis(); - if (currentTime - m_lastTimeServiceReqNeededIndForData > SERVICE_REQUEST_NEEDED_FOR_DATA_THRESHOLD) - { - sendServiceRequest(m_cmState == ECmState::CM_CONNECTED ? EServiceReqCause::CONNECTED_UPLINK_DATA_PENDING - : EServiceReqCause::IDLE_UPLINK_DATA_PENDING); - - m_lastTimeServiceReqNeededIndForData = currentTime; - } -} - -} // namespace nr::ue diff --git a/src/ue/nas/mm/messaging.cpp b/src/ue/nas/mm/messaging.cpp index a6078e6ec..cca587ca1 100644 --- a/src/ue/nas/mm/messaging.cpp +++ b/src/ue/nas/mm/messaging.cpp @@ -13,8 +13,6 @@ #include #include -#include - namespace nr::ue { @@ -105,14 +103,20 @@ static void RemoveCleartextIEs(nas::PlainMmMessage &msg, OctetString &&nasMsgCon } } -void NasMm::sendNasMessage(const nas::PlainMmMessage &msg) +EProcRc NasMm::sendNasMessage(const nas::PlainMmMessage &msg) { + if (!m_base->shCtx.hasActiveCell()) + { + m_logger->debug("NAS Transport aborted, no active cell"); + return EProcRc::STAY; + } + if (m_cmState == ECmState::CM_IDLE && !IsInitialNasMessage(msg)) { m_logger->warn("NAS Transport aborted, Service Request is needed for uplink signalling"); if (m_mmState != EMmState::MM_SERVICE_REQUEST_INITIATED) - sendServiceRequest(EServiceReqCause::IDLE_UPLINK_SIGNAL_PENDING); - return; + serviceRequestRequiredForSignalling(); + return EProcRc::STAY; } bool hasNsCtx = @@ -125,7 +129,7 @@ void NasMm::sendNasMessage(const nas::PlainMmMessage &msg) if (msg.messageType == nas::EMessageType::REGISTRATION_REQUEST || msg.messageType == nas::EMessageType::SERVICE_REQUEST) { - if (HasNonCleartext(msg)) + if (HasNonCleartext(msg) && m_cmState == ECmState::CM_IDLE) { OctetString originalPdu; nas::EncodeNasMessage(msg, originalPdu); @@ -168,19 +172,12 @@ void NasMm::sendNasMessage(const nas::PlainMmMessage &msg) } } - if (m_cmState == ECmState::CM_IDLE) - { - auto *nw = new NwUeNasToRrc(NwUeNasToRrc::INITIAL_NAS_DELIVERY); - nw->nasPdu = std::move(pdu); - nw->rrcEstablishmentCause = ASN_RRC_EstablishmentCause_mo_Data; - m_base->rrcTask->push(nw); - } - else - { - auto *nw = new NwUeNasToRrc(NwUeNasToRrc::UPLINK_NAS_DELIVERY); - nw->nasPdu = std::move(pdu); - m_base->rrcTask->push(nw); - } + auto *m = new NmUeNasToRrc(NmUeNasToRrc::UPLINK_NAS_DELIVERY); + m->pduId = 0; + m->nasPdu = std::move(pdu); + m_base->rrcTask->push(m); + + return EProcRc::OK; } void NasMm::receiveNasMessage(const nas::NasMessage &msg) diff --git a/src/ue/nas/mm/mm.hpp b/src/ue/nas/mm/mm.hpp index e65b68ca5..1c09bf633 100644 --- a/src/ue/nas/mm/mm.hpp +++ b/src/ue/nas/mm/mm.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -26,16 +26,19 @@ class NasMm { private: TaskBase *m_base; - UeTimers *m_timers; + NasTimers *m_timers; std::unique_ptr m_logger; NasSm *m_sm; - Usim *m_usim{}; + Usim *m_usim; + MmStorage *m_storage; ERmState m_rmState; ECmState m_cmState; EMmState m_mmState; EMmSubState m_mmSubState; + // Procedure management + ProcControl m_procCtl; // Most recent registration request std::unique_ptr m_lastRegistrationRequest{}; // Most recent service request @@ -48,25 +51,27 @@ class NasMm EDeregCause m_lastDeregCause{}; // Indicates the last service request cause EServiceReqCause m_lastServiceReqCause{}; - // Last time PLMN search is triggered - long m_lastPlmnSearchTrigger{}; // Registration attempt counter int m_regCounter{}; // Service request attempt counter int m_serCounter{}; - // Indicates registered for emergency services (Only meaningful in RM-REGISTERED state) + // Indicates registered for emergency services (Only meaningful in RM-REGISTERED state, or implies the last one) bool m_registeredForEmergency{}; // Network feature support information nas::IE5gsNetworkFeatureSupport m_nwFeatureSupport{}; - // Last time Service Request needed indication for Data - long m_lastTimeServiceReqNeededIndForData{}; // Number of times the network failing the authentication check int m_nwConsecutiveAuthFailure{}; + // Last time PLMN search failure logged + int64_t m_lastTimePlmnSearchFailureLogged{}; + // Last time MM state changed + int64_t m_lastTimeMmStateChange{}; friend class UeCmdHandler; + friend class NasSm; + friend class NasTask; public: - NasMm(TaskBase *base, UeTimers *timers); + NasMm(TaskBase *base, NasTimers *timers); public: /* Base */ void onStart(NasSm *sm, Usim *usim); @@ -75,16 +80,20 @@ class NasMm private: /* Base */ void triggerMmCycle(); void performMmCycle(); - void switchMmState(EMmState state, EMmSubState subState); + void switchMmState(EMmSubState subState); void switchCmState(ECmState state); void switchUState(E5UState state); void onSwitchMmState(EMmState oldState, EMmState newState, EMmSubState oldSubState, EMmSubState newSubSate); void onSwitchRmState(ERmState oldState, ERmState newState); void onSwitchCmState(ECmState oldState, ECmState newState); void onSwitchUState(E5UState oldState, E5UState newState); + void onSwitchOn(); + void onSimInsertion(); + void onSwitchOff(); + void onSimRemoval(); private: /* Messaging */ - void sendNasMessage(const nas::PlainMmMessage &msg); + EProcRc sendNasMessage(const nas::PlainMmMessage &msg); void receiveNasMessage(const nas::NasMessage &msg); void receiveMmMessage(const nas::PlainMmMessage &msg); void receiveMmStatus(const nas::FiveGMmStatus &msg); @@ -92,15 +101,11 @@ class NasMm private: /* Transport */ void receiveDlNasTransport(const nas::DlNasTransport &msg); - - public: /* Transport */ - void deliverUlTransport(const nas::UlNasTransport &msg); - - public: /* Registration */ - void sendMobilityRegistration(ERegUpdateCause updateCause); + EProcRc deliverUlTransport(const nas::UlNasTransport &msg); private: /* Registration */ - void sendInitialRegistration(EInitialRegCause regCause); + EProcRc sendInitialRegistration(EInitialRegCause regCause); + EProcRc sendMobilityRegistration(ERegUpdateCause updateCause); void receiveRegistrationAccept(const nas::RegistrationAccept &msg); void receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg); void receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg); @@ -127,15 +132,15 @@ class NasMm void receiveSecurityModeCommand(const nas::SecurityModeCommand &msg); nas::IEUeSecurityCapability createSecurityCapabilityIe(); - public: /* De-registration */ - void sendDeregistration(EDeregCause deregCause); - private: /* De-registration */ + EProcRc sendDeregistration(EDeregCause deregCause); void receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOriginating &msg); void receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTerminated &msg); + void performLocalDeregistration(); private: /* Configuration */ void receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &msg); + void updateForbiddenTaiListsForAllowedIndications(); private: /* Identity */ void receiveIdentityRequest(const nas::IdentityRequest &msg); @@ -144,7 +149,7 @@ class NasMm nas::IE5gsMobileIdentity getOrGeneratePreferredId(); private: /* Service */ - void sendServiceRequest(EServiceReqCause reqCause); + EProcRc sendServiceRequest(EServiceReqCause reqCause); void receiveServiceAccept(const nas::ServiceAccept &msg); void receiveServiceReject(const nas::ServiceReject &msg); @@ -153,13 +158,16 @@ class NasMm void handleNetworkSlicingSubscriptionChange(); private: /* Radio */ - void localReleaseConnection(); - void handlePlmnSearchResponse(const std::vector &measures); + void performPlmnSelection(); + void localReleaseConnection(bool treatBarred); + void handleActiveCellChange(const Tai &prevTai); void handleRrcConnectionSetup(); void handleRrcConnectionRelease(); - void handleServingCellChange(const UeCellInfo &servingCell); + void handleRrcEstablishmentFailure(); void handleRadioLinkFailure(); + void handleRrcFallbackIndication(); void handlePaging(const std::vector &tmsiIds); + void updateProvidedGuti(bool provide = true); private: /* Access Control */ bool isHighPriority(); @@ -172,15 +180,21 @@ class NasMm bool switchToECallInactivityIfNeeded(); private: /* Timer */ - void onTimerExpire(nas::NasTimer &timer); - - public: - /* Interface */ - void handleRrcEvent(const NwUeRrcToNas &msg); // used by RRC - void handleNasEvent(const NwUeNasToNas &msg); // used by NAS - bool isRegistered(); // used by SM - bool isRegisteredForEmergency(); // used by SM - void serviceNeededForUplinkData(); // used by SM + void onTimerExpire(UeTimer &timer); + + private: /* Procedure Control */ + void initialRegistrationRequired(EInitialRegCause cause); + void mobilityUpdatingRequired(ERegUpdateCause cause); + void serviceRequestRequiredForData(); + void serviceRequestRequiredForSignalling(); + void serviceRequestRequired(EServiceReqCause cause); + void deregistrationRequired(EDeregCause cause); + void invokeProcedures(); + bool hasPendingProcedure(); + + private: /* Service Access Point */ + void handleRrcEvent(const NmUeRrcToNas &msg); + void handleNasEvent(const NmUeNasToNas &msg); }; } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/mm/proc.cpp b/src/ue/nas/mm/proc.cpp new file mode 100644 index 000000000..b98086982 --- /dev/null +++ b/src/ue/nas/mm/proc.cpp @@ -0,0 +1,204 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "mm.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace nr::ue +{ + +static void AssignCause(std::optional &oldCause, EInitialRegCause newCause) +{ + oldCause = newCause; +} + +static void AssignCause(std::optional &oldCause, ERegUpdateCause newCause) +{ + oldCause = newCause; +} + +static void AssignCause(std::optional &oldCause, EServiceReqCause newCause) +{ + oldCause = newCause; +} + +static void AssignCause(std::optional &oldCause, EDeregCause newCause) +{ + if ((oldCause == EDeregCause::SWITCH_OFF || oldCause == EDeregCause::USIM_REMOVAL) && + (newCause == EDeregCause::SWITCH_OFF || newCause == EDeregCause::USIM_REMOVAL)) + return; + if (oldCause == EDeregCause::DISABLE_5G && newCause == EDeregCause::ECALL_INACTIVITY) + return; + + oldCause = newCause; +} + +void NasMm::initialRegistrationRequired(EInitialRegCause cause) +{ + if (m_mmState == EMmState::MM_NULL) + return; + + if (m_procCtl.initialRegistration == cause) + return; + + m_logger->debug("Initial registration required due to [%s]", ToJson(cause).str().c_str()); + + AssignCause(m_procCtl.initialRegistration, cause); + triggerMmCycle(); +} + +void NasMm::mobilityUpdatingRequired(ERegUpdateCause cause) +{ + if (m_mmState == EMmState::MM_NULL) + return; + + if (m_procCtl.mobilityRegistration == cause) + return; + + m_logger->debug("Mobility registration updating required due to [%s]", ToJson(cause).str().c_str()); + + AssignCause(m_procCtl.mobilityRegistration, cause); + triggerMmCycle(); +} + +void NasMm::serviceRequestRequired(EServiceReqCause cause) +{ + if (m_mmState == EMmState::MM_NULL) + return; + + if (m_procCtl.serviceRequest == cause) + return; + + m_logger->debug("Service request required due to [%s]", ToJson(cause).str().c_str()); + + AssignCause(m_procCtl.serviceRequest, cause); + triggerMmCycle(); +} + +void NasMm::deregistrationRequired(EDeregCause cause) +{ + if (m_mmState == EMmState::MM_NULL) + return; + + if (m_procCtl.deregistration == cause) + return; + + m_logger->debug("De-registration required due to [%s]", ToJson(cause).str().c_str()); + + AssignCause(m_procCtl.deregistration, cause); + triggerMmCycle(); +} + +void NasMm::serviceRequestRequiredForSignalling() +{ + serviceRequestRequired(EServiceReqCause::IDLE_UPLINK_SIGNAL_PENDING); +} + +void NasMm::serviceRequestRequiredForData() +{ + serviceRequestRequired(m_cmState == ECmState::CM_CONNECTED ? EServiceReqCause::CONNECTED_UPLINK_DATA_PENDING + : EServiceReqCause::IDLE_UPLINK_DATA_PENDING); +} + +void NasMm::invokeProcedures() +{ + auto activeCell = m_base->shCtx.currentCell.get(); + bool hasActiveCell = activeCell.hasValue(); + + if (hasActiveCell && m_procCtl.deregistration) + { + EProcRc rc = sendDeregistration(*m_procCtl.deregistration); + if (rc == EProcRc::OK) + { + m_procCtl.deregistration = std::nullopt; + m_procCtl.initialRegistration = std::nullopt; + m_procCtl.mobilityRegistration = std::nullopt; + m_procCtl.serviceRequest = std::nullopt; + } + else if (rc == EProcRc::CANCEL) + { + m_procCtl.deregistration = std::nullopt; + } + return; + } + + if (hasActiveCell && m_procCtl.initialRegistration) + { + EProcRc rc = sendInitialRegistration(*m_procCtl.initialRegistration); + if (rc == EProcRc::OK) + { + m_procCtl.initialRegistration = std::nullopt; + m_procCtl.mobilityRegistration = std::nullopt; + m_procCtl.serviceRequest = std::nullopt; + return; + } + else if (rc == EProcRc::CANCEL) + { + m_procCtl.initialRegistration = std::nullopt; + return; + } + } + + if (hasActiveCell && m_procCtl.mobilityRegistration) + { + EProcRc rc = sendMobilityRegistration(*m_procCtl.mobilityRegistration); + if (rc == EProcRc::OK) + { + m_procCtl.mobilityRegistration = std::nullopt; + m_procCtl.serviceRequest = std::nullopt; + return; + } + else if (rc == EProcRc::CANCEL) + { + m_procCtl.mobilityRegistration = std::nullopt; + return; + } + return; + } + + if (hasActiveCell && m_procCtl.serviceRequest) + { + EProcRc rc = sendServiceRequest(*m_procCtl.serviceRequest); + if (rc == EProcRc::OK || rc == EProcRc::CANCEL) + { + m_procCtl.serviceRequest = std::nullopt; + return; + } + } + + if (m_mmSubState == EMmSubState::MM_REGISTERED_NORMAL_SERVICE || + m_mmSubState == EMmSubState::MM_REGISTERED_LIMITED_SERVICE) + { + m_sm->establishRequiredSessions(); + } +} + +bool NasMm::hasPendingProcedure() +{ + if (m_procCtl.initialRegistration) + return true; + if (m_procCtl.mobilityRegistration) + return true; + if (m_procCtl.serviceRequest) + return true; + if (m_procCtl.deregistration) + return true; + + // TODO: Check for SM sublayer, (and other stacks in the future such as SMS) because they are transported over MM. + // NOTE: Other MM common procedures are ignored. (Except UL/DL NAS Transport) + return false; +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/mm/radio.cpp b/src/ue/nas/mm/radio.cpp index 0472c7564..ea5c1e400 100644 --- a/src/ue/nas/mm/radio.cpp +++ b/src/ue/nas/mm/radio.cpp @@ -7,232 +7,275 @@ // #include "mm.hpp" + #include + #include #include #include #include +#include namespace nr::ue { -void NasMm::handlePlmnSearchResponse(const std::vector &measures) +void NasMm::performPlmnSelection() { - if (m_mmSubState != EMmSubState::MM_REGISTERED_PLMN_SEARCH && - m_mmSubState != EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE && - m_mmSubState != EMmSubState::MM_DEREGISTERED_PLMN_SEARCH && - m_mmSubState != EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE) - { - m_logger->warn("PLMN search response received without being requested"); - return; - } + int64_t currentTime = utils::CurrentTimeMillis(); - if (measures.empty()) + // After some timeout in PLMN_SEARCH states, NO_CELL_AVAILABLE state is selected + if (currentTime - m_lastTimeMmStateChange >= 5'000LL) { if (m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH) { - m_logger->err("PLMN selection failure, no cell available"); - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE); + switchMmState(EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE); return; } else if (m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH) { - m_logger->err("PLMN selection failure, no cell available"); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE); + switchMmState(EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE); return; } - return; // otherwise it's one of the no-cell-available states, no need to print an error } - int listedAsForbiddenPlmn = 0; - int listedAsForbiddenTai = 0; - int listedAsForbiddenServiceAreaPlmn = 0; - int listedAsForbiddenServiceAreaTai = 0; - int unlistedPlmn = 0; + // If the state is PLMN_SEARCH instead of NO_CELL_AVAILABLE, then we log the errors more intensely. + int64_t loggingThreshold = m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH || + m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH + ? 2'000LL + : 30'000LL; - std::vector suitable{}; - std::vector acceptable{}; + bool logFailures = currentTime - m_lastTimePlmnSearchFailureLogged >= loggingThreshold; - for (auto &item : measures) + // If the device just switched on, then no error logging for PLMN selection failures + if (m_lastTimePlmnSearchFailureLogged == 0) { - acceptable.push_back(item); + m_lastTimePlmnSearchFailureLogged = currentTime; + logFailures = false; + } - if (nas::utils::PlmnListContains(m_usim->m_forbiddenPlmnList, item.cellId.plmn)) - { - listedAsForbiddenPlmn++; - continue; - } - if (nas::utils::TaiListContains( - m_usim->m_forbiddenTaiListRps, - nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(item.cellId.plmn), octet3{item.tac}}) || - nas::utils::TaiListContains( - m_usim->m_forbiddenTaiListRoaming, - nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(item.cellId.plmn), octet3{item.tac}})) - { - listedAsForbiddenTai++; - continue; - } - if (nas::utils::ServiceAreaListForbidsPlmn(m_usim->m_serviceAreaList, nas::utils::PlmnFrom(item.cellId.plmn))) - { - listedAsForbiddenServiceAreaPlmn++; - continue; - } - if (nas::utils::ServiceAreaListForbidsTai( - m_usim->m_serviceAreaList, - nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(item.cellId.plmn), octet3{item.tac}})) - { - listedAsForbiddenServiceAreaTai++; - continue; - } + Plmn lastSelectedPlmn = m_base->shCtx.selectedPlmn.get(); - if (item.cellId.plmn == m_base->config->hplmn || item.cellId.plmn == m_usim->m_currentPlmn || - nas::utils::PlmnListContains(m_usim->m_equivalentPlmnList, item.cellId.plmn)) - { - suitable.push_back(item); - } - else + std::unordered_set plmns = m_base->shCtx.availablePlmns.get(); + + if (!m_usim->isValid() || plmns.empty()) + { + if (logFailures) { - unlistedPlmn++; - continue; + if (!m_usim->isValid()) + m_logger->warn("No PLMN can be selected, USIM is invalid"); + else + m_logger->err("PLMN selection failure, no cells in coverage"); + m_lastTimePlmnSearchFailureLogged = currentTime; } - } - int totalForbidden = listedAsForbiddenPlmn + listedAsForbiddenTai + listedAsForbiddenServiceAreaPlmn + - listedAsForbiddenServiceAreaTai; + m_base->shCtx.selectedPlmn.set({}); + return; + } - auto logErrorSuitableAcceptable = [this, totalForbidden, unlistedPlmn]() { - m_logger->err("PLMN selection failure, no suitable or acceptable cell can be found"); - if (totalForbidden > 0 || unlistedPlmn > 0) - m_logger->err("[%d] cell was in forbidden list, [%d] was in unknown PLMN", totalForbidden, unlistedPlmn); - }; + // Determine candidate PLMNs from the list. Candidates are in priority order + std::vector candidates; - auto logWarningAcceptable = [this, totalForbidden, unlistedPlmn]() { - m_logger->warn("PLMN selection failure, no suitable cell can be found, an acceptable cell is selected instead"); - if (totalForbidden > 0 || unlistedPlmn > 0) - m_logger->warn("[%d] cell was in forbidden list, [%d] was in unknown PLMN", totalForbidden, unlistedPlmn); - }; + // Highest priority is for HPLMN, so just look for HPLMN first. + for (auto &plmn : plmns) + if (plmn == m_base->config->hplmn) + candidates.push_back(plmn); - if (!suitable.empty()) + // Then again look for the all PLMNS + for (auto &plmn : plmns) { - std::stable_sort(suitable.begin(), suitable.end(), [](auto &x, auto &y) { return x.dbm >= y.dbm; }); - - auto *w = new NwUeNasToRrc(NwUeNasToRrc::CELL_SELECTION_COMMAND); - w->cellId = suitable[0].cellId; - w->isSuitableCell = true; - m_base->rrcTask->push(w); + if (plmn == m_base->config->hplmn) + continue; // If it's the HPLMN, it's already added above + if (m_storage->forbiddenPlmnList->contains(plmn)) + continue; + if (nas::utils::ServiceAreaListForbidsPlmn(m_storage->serviceAreaList->get(), nas::utils::PlmnFrom(plmn))) + continue; + if (m_storage->equivalentPlmnList->contains(plmn)) + candidates.push_back(plmn); } - else if (!acceptable.empty()) - { - std::stable_sort(acceptable.begin(), acceptable.end(), [](auto &x, auto &y) { return x.dbm >= y.dbm; }); - logWarningAcceptable(); + Plmn selected = candidates.empty() ? Plmn{} : candidates[0]; - auto *w = new NwUeNasToRrc(NwUeNasToRrc::CELL_SELECTION_COMMAND); - w->cellId = acceptable[0].cellId; - w->isSuitableCell = false; - m_base->rrcTask->push(w); - } - else + if (!selected.hasValue()) { - if (m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH) - { - logErrorSuitableAcceptable(); - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE); - return; - } - else if (m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH) + if (logFailures) { - logErrorSuitableAcceptable(); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE); - return; + m_logger->err("No PLMN could be selected among [%d] PLMNs", static_cast(plmns.size())); + m_lastTimePlmnSearchFailureLogged = currentTime; } } + else if (lastSelectedPlmn != selected) + { + m_logger->info("Selected plmn[%s]", ToJson(selected).str().c_str()); + m_base->rrcTask->push(new NmUeNasToRrc(NmUeNasToRrc::RRC_NOTIFY)); + + resetRegAttemptCounter(); + } + + m_base->shCtx.selectedPlmn.set(selected); } -void NasMm::handleServingCellChange(const UeCellInfo &servingCell) +void NasMm::handleActiveCellChange(const Tai &prevTai) { if (m_cmState == ECmState::CM_CONNECTED) { + // TODO m_logger->err("Serving cell change in CM-CONNECTED"); return; } - if (servingCell.cellCategory != ECellCategory::ACCEPTABLE_CELL && - servingCell.cellCategory != ECellCategory::SUITABLE_CELL) - { - m_logger->err("Serving cell change with unhandled cell category"); - return; - } + auto currentCell = m_base->shCtx.currentCell.get(); + Tai currentTai = Tai{currentCell.plmn, currentCell.tac}; - m_logger->info("Serving cell determined [%s]", servingCell.gnbName.c_str()); + if (currentCell.hasValue() && !m_storage->equivalentPlmnList->contains(currentCell.plmn)) + m_timers->t3346.stop(); - if (m_mmState == EMmState::MM_REGISTERED || m_mmState == EMmState::MM_DEREGISTERED) + if (currentCell.hasValue() && prevTai != currentTai) { - bool isSuitable = servingCell.cellCategory == ECellCategory::SUITABLE_CELL; + // "Additionally, the registration attempt counter shall be reset when the UE is in substate + // 5GMM-DEREGISTERED.ATTEMPTING-REGISTRATION or 5GMM-REGISTERED.ATTEMPTING-REGISTRATION-UPDATE, and a new + // tracking area is entered" + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION || + m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE) + { + resetRegAttemptCounter(); + } - if (m_mmState == EMmState::MM_REGISTERED) - switchMmState(EMmState::MM_REGISTERED, isSuitable ? EMmSubState::MM_REGISTERED_NORMAL_SERVICE - : EMmSubState::MM_REGISTERED_LIMITED_SERVICE); - else - switchMmState(EMmState::MM_DEREGISTERED, isSuitable ? EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE - : EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); + // "Shall initiate a registration procedure for mobility and periodic registration update when the tracking area + // of the serving cell has changed, if timer T3346 is not running, the PLMN identity of the new cell is not in + // one of the forbidden PLMN lists and the tracking area is not in one of the lists of 5GS forbidden tracking + // area" + if (m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE && !m_timers->t3346.isRunning() && + currentCell.category == ECellCategory::SUITABLE_CELL) + { + mobilityUpdatingRequired(ERegUpdateCause::TAI_CHANGE_IN_ATT_UPD); + } + + // "shall initiate an initial registration procedure when the tracking area of the serving cell has changed, if + // timer T3346 is not running, the PLMN identity of the new cell is not in one of the forbidden PLMN lists and + // the tracking area of the new cell is not in one of the lists of 5GS forbidden tracking areas" + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION && !m_timers->t3346.isRunning() && + currentCell.category == ECellCategory::SUITABLE_CELL) + { + initialRegistrationRequired(EInitialRegCause::TAI_CHANGE_IN_ATT_REG); + } } - // todo: else, other states abnormal case - resetRegAttemptCounter(); + if (currentCell.hasValue() && prevTai.plmn != currentTai.plmn) + { + // "Shall initiate a registration procedure for mobility and periodic registration update when entering a new + // PLMN, if timer T3346 is running and the new PLMN is not equivalent to the PLMN where the UE started timer + // T3346, the PLMN identity of the new cell is not in the forbidden PLMN lists, and the tracking area is not in + // one of the lists of 5GS forbidden tracking areas" + if (m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE && m_timers->t3346.isRunning() && + !m_storage->equivalentPlmnList->contains(currentTai.plmn) && + currentCell.category == ECellCategory::SUITABLE_CELL) + { + mobilityUpdatingRequired(ERegUpdateCause::PLMN_CHANGE_IN_ATT_UPD); + } - m_usim->m_servingCell = servingCell; - m_usim->m_currentPlmn = servingCell.cellId.plmn; - m_usim->m_currentTai = - nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(servingCell.cellId.plmn), octet3{servingCell.tac}}; + // "shall initiate an initial registration procedure when entering a new PLMN, if timer T3346 is running and the + // new PLMN is not equivalent to the PLMN where the UE started timer T3346, the PLMN identity of the new cell is + // not in the forbidden PLMN lists and the tracking area is not in one of the lists of 5GS forbidden tracking + // areas" + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION && m_timers->t3346.isRunning() && + !m_storage->equivalentPlmnList->contains(currentTai.plmn) && + currentCell.category == ECellCategory::SUITABLE_CELL) + { + initialRegistrationRequired(EInitialRegCause::PLMN_CHANGE_IN_ATT_REG); + } + } + + if (m_mmState == EMmState::MM_REGISTERED) + { + switchMmState(EMmSubState::MM_REGISTERED_PS); + } + else if (m_mmState == EMmState::MM_DEREGISTERED) + { + switchMmState(EMmSubState::MM_DEREGISTERED_PS); + } + else if (m_mmState == EMmState::MM_REGISTERED_INITIATED || m_mmState == EMmState::MM_DEREGISTERED_INITIATED || + m_mmState == EMmState::MM_SERVICE_REQUEST_INITIATED) + { + // This should never happen + m_logger->err("Active cell change in [CM-IDLE] state while MM specific procedure is ongoing"); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); + } } void NasMm::handleRrcConnectionSetup() { + // TODO switchCmState(ECmState::CM_CONNECTED); } void NasMm::handleRrcConnectionRelease() { + // TODO switchCmState(ECmState::CM_IDLE); } +void NasMm::handleRrcEstablishmentFailure() +{ + // TODO + + m_logger->err("RRC Establishment failure"); + + if (m_mmState == EMmState::MM_REGISTERED_INITIATED) + { + switchMmState(m_rmState == ERmState::RM_REGISTERED ? EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE + : EMmSubState::MM_DEREGISTERED_INITIAL_REGISTRATION_NEEDED); + } + else if (m_mmState == EMmState::MM_SERVICE_REQUEST_INITIATED) + { + switchMmState(EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE); + } + else if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) + { + } +} + void NasMm::handleRadioLinkFailure() { + // TODO + if (m_cmState == ECmState::CM_CONNECTED) { m_logger->err("Radio link failure detected"); } - m_usim->m_servingCell = std::nullopt; - m_usim->m_currentPlmn = std::nullopt; - m_usim->m_currentTai = std::nullopt; - handleRrcConnectionRelease(); if (m_mmState == EMmState::MM_REGISTERED) - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + switchMmState(EMmSubState::MM_REGISTERED_PS); else if (m_mmState == EMmState::MM_DEREGISTERED) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); } -void NasMm::localReleaseConnection() +void NasMm::localReleaseConnection(bool treatBarred) { - m_logger->info("Performing local release of NAS connection"); + if (m_cmState != ECmState::CM_IDLE) + m_logger->info("Performing local release of NAS connection"); - m_base->rrcTask->push(new NwUeNasToRrc(NwUeNasToRrc::LOCAL_RELEASE_CONNECTION)); + auto *w = new NmUeNasToRrc(NmUeNasToRrc::LOCAL_RELEASE_CONNECTION); + w->treatBarred = treatBarred; + m_base->rrcTask->push(w); } void NasMm::handlePaging(const std::vector &tmsiIds) { - if (m_usim->m_storedGuti.type == nas::EIdentityType::NO_IDENTITY) + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ECALL_INACTIVE) return; + + auto guti = m_storage->storedGuti->get(); + + if (guti.type == nas::EIdentityType::NO_IDENTITY) + return; + bool tmsiMatches = false; for (auto &tmsi : tmsiIds) { - if (tmsi.amfSetId == m_usim->m_storedGuti.gutiOrTmsi.amfSetId && - tmsi.amfPointer == m_usim->m_storedGuti.gutiOrTmsi.amfPointer && - tmsi.tmsi == m_usim->m_storedGuti.gutiOrTmsi.tmsi) + if (tmsi.amfSetId == guti.gutiOrTmsi.amfSetId && tmsi.amfPointer == guti.gutiOrTmsi.amfPointer && + tmsi.tmsi == guti.gutiOrTmsi.tmsi) { tmsiMatches = true; break; @@ -242,6 +285,12 @@ void NasMm::handlePaging(const std::vector &tmsiIds) if (!tmsiMatches) return; + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ECALL_INACTIVE) + { + m_logger->debug("Ignoring received Paging due to eCall inactive"); + return; + } + m_timers->t3346.stop(); if (m_mmState == EMmState::MM_REGISTERED_INITIATED || m_mmState == EMmState::MM_DEREGISTERED_INITIATED || @@ -254,9 +303,69 @@ void NasMm::handlePaging(const std::vector &tmsiIds) m_logger->debug("Responding to received Paging"); if (m_cmState == ECmState::CM_CONNECTED) - sendMobilityRegistration(ERegUpdateCause::PAGING_OR_NOTIFICATION); + mobilityUpdatingRequired(ERegUpdateCause::PAGING_OR_NOTIFICATION); else - sendServiceRequest(EServiceReqCause::IDLE_PAGING); + serviceRequestRequired(EServiceReqCause::IDLE_PAGING); +} + +void NasMm::updateProvidedGuti(bool provide) +{ + if (m_cmState != ECmState::CM_IDLE) + return; + + auto &guti = m_storage->storedGuti->get(); + if (!provide || guti.type == nas::EIdentityType::NO_IDENTITY) + { + m_base->shCtx.providedGuti.set({}); + m_base->shCtx.providedTmsi.set({}); + } + else + { + auto tai = m_base->shCtx.getCurrentTai(); + if (tai.hasValue() && nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{tai})) + { + m_base->shCtx.providedGuti.set({}); + m_base->shCtx.providedTmsi.set(guti.gutiOrTmsi); + } + else + { + m_base->shCtx.providedGuti.set(guti.gutiOrTmsi); + m_base->shCtx.providedTmsi.set({}); + } + } +} + +void NasMm::handleRrcFallbackIndication() +{ + // TODO: RRC does not send this indication yet + + if (m_cmState == ECmState::CM_IDLE) + { + m_logger->err("RRC fallback indication in CM-IDLE"); + return; + } + + bool pendingProc = hasPendingProcedure(); + bool pendingData = m_sm->anyUplinkDataPending(); + + switchCmState(ECmState::CM_IDLE); + + if (!pendingProc) + { + if (pendingData) + serviceRequestRequired(EServiceReqCause::FALLBACK_INDICATION); + else + mobilityUpdatingRequired(ERegUpdateCause::FALLBACK_INDICATION); + } + else + { + if (m_procCtl.initialRegistration || m_procCtl.deregistration) + (void)0; + else if (m_procCtl.mobilityRegistration) + mobilityUpdatingRequired(ERegUpdateCause::FALLBACK_INDICATION); + else + serviceRequestRequired(EServiceReqCause::FALLBACK_INDICATION); + } } } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/mm/register.cpp b/src/ue/nas/mm/register.cpp index 443723fda..1d2a4053b 100644 --- a/src/ue/nas/mm/register.cpp +++ b/src/ue/nas/mm/register.cpp @@ -11,19 +11,25 @@ #include #include #include -#include namespace nr::ue { -void NasMm::sendInitialRegistration(EInitialRegCause regCause) +EProcRc NasMm::sendInitialRegistration(EInitialRegCause regCause) { if (m_rmState != ERmState::RM_DEREGISTERED) { m_logger->warn("Registration could not be triggered. UE is not in RM-DEREGISTERED state."); - return; + return EProcRc::CANCEL; } + if (m_mmState == EMmState::MM_REGISTERED_INITIATED) + return EProcRc::CANCEL; + if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) + return EProcRc::STAY; + if (m_mmSubState == EMmSubState::MM_REGISTERED_UPDATE_NEEDED) + return EProcRc::STAY; + bool isEmergencyReg = regCause == EInitialRegCause::EMERGENCY_SERVICES; // 5.5.1.2.7 Abnormal cases in the UE @@ -38,7 +44,7 @@ void NasMm::sendInitialRegistration(EInitialRegCause regCause) if (!highPriority && regCause != EInitialRegCause::DUE_TO_DEREGISTRATION) { m_logger->debug("Initial registration canceled, T3346 is running"); - return; + return EProcRc::STAY; } } @@ -46,13 +52,12 @@ void NasMm::sendInitialRegistration(EInitialRegCause regCause) nas::utils::EnumToString(isEmergencyReg ? nas::ERegistrationType::EMERGENCY_REGISTRATION : nas::ERegistrationType::INITIAL_REGISTRATION)); + updateProvidedGuti(); + // The UE shall mark the 5G NAS security context on the USIM or in the non-volatile memory as invalid when the UE // initiates an initial registration procedure m_usim->m_currentNsCtx = {}; - // Switch MM state - switchMmState(EMmState::MM_REGISTERED_INITIATED, EMmSubState::MM_REGISTERED_INITIATED_NA); - // Prepare requested NSSAI bool isDefaultNssai{}; auto requestedNssai = makeRequestedNssai(isDefaultNssai); @@ -80,7 +85,8 @@ void NasMm::sendInitialRegistration(EInitialRegCause regCause) // Assign other fields request->mobileIdentity = getOrGeneratePreferredId(); - request->lastVisitedRegisteredTai = m_usim->m_lastVisitedRegisteredTai; + if (m_storage->lastVisitedRegisteredTai->get().hasValue()) + request->lastVisitedRegisteredTai = nas::IE5gsTrackingAreaIdentity{m_storage->lastVisitedRegisteredTai->get()}; if (!requestedNssai.slices.empty()) request->requestedNSSAI = nas::utils::NssaiFrom(requestedNssai); request->ueSecurityCapability = createSecurityCapabilityIe(); @@ -95,7 +101,13 @@ void NasMm::sendInitialRegistration(EInitialRegCause regCause) } // Send the message - sendNasMessage(*request); + auto rc = sendNasMessage(*request); + if (rc != EProcRc::OK) + return rc; + + // Switch MM state + switchMmState(EMmSubState::MM_REGISTERED_INITIATED_PS); + m_lastRegistrationRequest = std::move(request); m_lastRegWithoutNsc = m_usim->m_currentNsCtx == nullptr; @@ -103,14 +115,27 @@ void NasMm::sendInitialRegistration(EInitialRegCause regCause) m_timers->t3510.start(); m_timers->t3502.stop(); m_timers->t3511.stop(); + + return EProcRc::OK; } -void NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) +EProcRc NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) { if (m_rmState == ERmState::RM_DEREGISTERED) { - m_logger->warn("Registration updating could not be triggered. UE is in RM-DEREGISTERED state."); - return; + m_logger->warn("Mobility updating could not be triggered. UE is in RM-DEREGISTERED state."); + return EProcRc::CANCEL; + } + + if (m_mmState == EMmState::MM_REGISTERED_INITIATED) + return EProcRc::CANCEL; + if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) + return EProcRc::STAY; + + if (updateCause == ERegUpdateCause::T3512_EXPIRY && m_mmSubState == EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE) + { + if (!isHighPriority() && !hasEmergency()) + return EProcRc::STAY; } // 5.5.1.3.7 Abnormal cases in the UE @@ -121,8 +146,8 @@ void NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) isHighPriority() || hasEmergency() || updateCause == ERegUpdateCause::CONFIGURATION_UPDATE; if (!allowed) { - m_logger->debug("Registration updating canceled, T3346 is running"); - return; + m_logger->debug("Mobility updating canceled, T3346 is running"); + return EProcRc::STAY; } } @@ -139,8 +164,10 @@ void NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) : nas::ERegistrationType::MOBILITY_REGISTRATION_UPDATING), ToJson(updateCause).str().c_str()); - // Switch state - switchMmState(EMmState::MM_REGISTERED_INITIATED, EMmSubState::MM_REGISTERED_INITIATED_NA); + // "if the registration procedure for mobility and periodic update was triggered due to the last CONFIGURATION + // UPDATE COMMAND message that indicates "registration requested" including: ... the UE NAS shall not provide the + // lower layers with the 5G-S-TMSI or the registered GUAMI; " + updateProvidedGuti(updateCause != ERegUpdateCause::CONFIGURATION_UPDATE); // Prepare FOR pending field nas::EFollowOnRequest followOn = nas::EFollowOnRequest::FOR_PENDING; @@ -163,6 +190,20 @@ void NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) ? nas::ENgRanRadioCapabilityUpdate::NEEDED : nas::ENgRanRadioCapabilityUpdate::NOT_NEEDED; + // Assign uplink data status + if (updateCause == ERegUpdateCause::FALLBACK_INDICATION || updateCause == ERegUpdateCause::CONNECTION_RECOVERY) + { + request->uplinkDataStatus = nas::IEUplinkDataStatus{}; + request->uplinkDataStatus->psi = m_sm->getUplinkDataStatus(); + } + + // Assign PDU session status + if (m_cmState == ECmState::CM_IDLE) + { + request->pduSessionStatus = nas::IEPduSessionStatus{}; + request->pduSessionStatus->psi = m_sm->getPduSessionStatus(); + } + // MM capability should be included if it is not a periodic registration if (updateCause != ERegUpdateCause::T3512_EXPIRY) { @@ -173,7 +214,8 @@ void NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) } // Assign other fields - request->lastVisitedRegisteredTai = m_usim->m_lastVisitedRegisteredTai; + if (m_storage->lastVisitedRegisteredTai->get().hasValue()) + request->lastVisitedRegisteredTai = nas::IE5gsTrackingAreaIdentity{m_storage->lastVisitedRegisteredTai->get()}; request->mobileIdentity = getOrGeneratePreferredId(); if (!requestedNssai.slices.empty()) request->requestedNSSAI = nas::utils::NssaiFrom(requestedNssai); @@ -186,14 +228,21 @@ void NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) // : nas::EDefaultConfiguredNssaiIndication::NOT_CREATED_FROM_DEFAULT_CONFIGURED_NSSAI; // Send the message - sendNasMessage(*request); + auto rc = sendNasMessage(*request); + if (rc != EProcRc::OK) + return rc; m_lastRegistrationRequest = std::move(request); m_lastRegWithoutNsc = m_usim->m_currentNsCtx == nullptr; + // Switch state + switchMmState(EMmSubState::MM_REGISTERED_INITIATED_PS); + // Process timers m_timers->t3510.start(); m_timers->t3502.stop(); m_timers->t3511.stop(); + + return EProcRc::OK; } void NasMm::receiveRegistrationAccept(const nas::RegistrationAccept &msg) @@ -229,29 +278,48 @@ void NasMm::receiveRegistrationAccept(const nas::RegistrationAccept &msg) void NasMm::receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg) { + Tai currentTai = m_base->shCtx.getCurrentTai(); + Plmn currentPlmn = currentTai.plmn; + + if (!currentTai.hasValue()) + return; + // Store the TAI list as a registration area - m_usim->m_taiList = msg.taiList.value_or(nas::IE5gsTrackingAreaIdentityList{}); + if (msg.taiList.has_value() && nas::utils::TaiListSize(*msg.taiList) == 0) + { + m_logger->err("Invalid TAI list size"); + sendMmStatus(nas::EMmCause::SEMANTICALLY_INCORRECT_MESSAGE); + return; + } + m_storage->taiList->set(msg.taiList.value_or(nas::IE5gsTrackingAreaIdentityList{})); + if (currentTai.hasValue() && + nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{currentTai})) + m_storage->lastVisitedRegisteredTai->set(currentTai); + // Store the service area list - m_usim->m_serviceAreaList = msg.serviceAreaList.value_or(nas::IEServiceAreaList{}); + m_storage->serviceAreaList->set(msg.serviceAreaList.value_or(nas::IEServiceAreaList{})); + + updateForbiddenTaiListsForAllowedIndications(); // Store the E-PLMN list and .. - m_usim->m_equivalentPlmnList = msg.equivalentPLMNs.value_or(nas::IEPlmnList{}); + m_storage->equivalentPlmnList->clear(); + if (msg.equivalentPLMNs.has_value()) + for (auto &item : msg.equivalentPLMNs->plmns) + m_storage->equivalentPlmnList->add(nas::utils::PlmnFrom(item)); // .. if the initial registration procedure is not for emergency services, the UE shall remove from the list any // PLMN code that is already in the list of "forbidden PLMNs". .. if (!hasEmergency()) { - utils::EraseWhere(m_usim->m_equivalentPlmnList.plmns, [this](auto &plmn) { - return std::any_of(m_usim->m_forbiddenPlmnList.plmns.begin(), m_usim->m_forbiddenPlmnList.plmns.end(), - [&plmn](auto &forbidden) { return nas::utils::DeepEqualsV(plmn, forbidden); }); - }); + m_storage->forbiddenPlmnList->forEach( + [this](auto &forbiddenPlmn) { m_storage->equivalentPlmnList->remove(forbiddenPlmn); }); } // .. in addition, the UE shall add to the stored list the PLMN code of the registered PLMN that sent the list - nas::utils::AddToPlmnList(m_usim->m_equivalentPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); + m_storage->equivalentPlmnList->add(currentPlmn); // Upon receipt of the REGISTRATION ACCEPT message, the UE shall reset the registration attempt counter, enter state // 5GMM-REGISTERED and set the 5GS update status to 5U1 UPDATED. resetRegAttemptCounter(); - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE); + switchMmState(EMmSubState::MM_REGISTERED_NORMAL_SERVICE); switchUState(E5UState::U1_UPDATED); // If the REGISTRATION ACCEPT message included a T3512 value IE, the UE shall use the value in the T3512 value IE as @@ -268,7 +336,8 @@ void NasMm::receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg) { if (msg.mobileIdentity->type == nas::EIdentityType::GUTI) { - m_usim->m_storedGuti = *msg.mobileIdentity; + m_storage->storedSuci->clear(); + m_storage->storedGuti->set(*msg.mobileIdentity); m_timers->t3519.stop(); sendComplete = true; } @@ -283,13 +352,13 @@ void NasMm::receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg) { for (auto &rejectedSlice : msg.rejectedNSSAI->list) { - SingleSlice slice{}; + SingleSlice slice; slice.sst = rejectedSlice.sst; slice.sd = rejectedSlice.sd; - auto &list = rejectedSlice.cause == nas::ERejectedSNssaiCause::NA_IN_PLMN ? m_usim->m_rejectedNssaiInPlmn - : m_usim->m_rejectedNssaiInTa; - list.addIfNotExists(slice); + auto &nssai = rejectedSlice.cause == nas::ERejectedSNssaiCause::NA_IN_PLMN ? m_storage->rejectedNssaiInPlmn + : m_storage->rejectedNssaiInTa; + nssai->mutate([slice](auto &value) { value.addIfNotExists(slice); }); } } @@ -302,12 +371,12 @@ void NasMm::receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg) } // Store the allowed NSSAI - m_usim->m_allowedNssai = nas::utils::NssaiTo(msg.allowedNSSAI.value_or(nas::IENssai{})); + m_storage->allowedNssai->set(nas::utils::NssaiTo(msg.allowedNSSAI.value_or(nas::IENssai{}))); // Process configured NSSAI IE if (msg.configuredNSSAI.has_value()) { - m_usim->m_configuredNssai = nas::utils::NssaiTo(msg.configuredNSSAI.value_or(nas::IENssai{})); + m_storage->configuredNssai->set(nas::utils::NssaiTo(msg.configuredNSSAI.value_or(nas::IENssai{}))); sendComplete = true; } @@ -315,16 +384,13 @@ void NasMm::receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg) m_nwFeatureSupport = msg.networkFeatureSupport.value_or(nas::IE5gsNetworkFeatureSupport{}); if (sendComplete) + { + m_logger->debug("Sending Registration Complete"); sendNasMessage(nas::RegistrationComplete{}); + } auto regType = m_lastRegistrationRequest->registrationType.registrationType; - if (regType == nas::ERegistrationType::INITIAL_REGISTRATION || - regType == nas::ERegistrationType::EMERGENCY_REGISTRATION) - { - m_base->nasTask->push(new NwUeNasToNas(NwUeNasToNas::ESTABLISH_INITIAL_SESSIONS)); - } - if (regType == nas::ERegistrationType::INITIAL_REGISTRATION) m_registeredForEmergency = false; else if (regType == nas::ERegistrationType::EMERGENCY_REGISTRATION) @@ -335,33 +401,52 @@ void NasMm::receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg) void NasMm::receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg) { + Tai currentTai = m_base->shCtx.getCurrentTai(); + Plmn currentPlmn = currentTai.plmn; + + if (!currentTai.hasValue()) + return; + // "The UE, upon receiving a REGISTRATION ACCEPT message, shall delete its old TAI list and store the received TAI // list. If there is no TAI list received, the UE shall consider the old TAI list as valid." if (msg.taiList.has_value()) - m_usim->m_taiList = *msg.taiList; + { + if (nas::utils::TaiListSize(*msg.taiList) == 0) + { + m_logger->err("Invalid TAI list size"); + sendMmStatus(nas::EMmCause::SEMANTICALLY_INCORRECT_MESSAGE); + return; + } + + m_storage->taiList->set(*msg.taiList); + if (nas::utils::TaiListContains(*msg.taiList, nas::VTrackingAreaIdentity{currentTai})) + m_storage->lastVisitedRegisteredTai->set(currentTai); + } // Store the E-PLMN list and .. - m_usim->m_equivalentPlmnList = msg.equivalentPLMNs.value_or(nas::IEPlmnList{}); + m_storage->equivalentPlmnList->clear(); + if (msg.equivalentPLMNs.has_value()) + for (auto &item : msg.equivalentPLMNs->plmns) + m_storage->equivalentPlmnList->add(nas::utils::PlmnFrom(item)); // .. if the initial registration procedure is not for emergency services, the UE shall remove from the list any // PLMN code that is already in the list of "forbidden PLMNs". .. if (!hasEmergency()) { - utils::EraseWhere(m_usim->m_equivalentPlmnList.plmns, [this](auto &plmn) { - return std::any_of(m_usim->m_forbiddenPlmnList.plmns.begin(), m_usim->m_forbiddenPlmnList.plmns.end(), - [&plmn](auto &forbidden) { return nas::utils::DeepEqualsV(plmn, forbidden); }); - }); + m_storage->forbiddenPlmnList->forEach( + [this](auto &forbiddenPlmn) { m_storage->equivalentPlmnList->remove(forbiddenPlmn); }); } // .. in addition, the UE shall add to the stored list the PLMN code of the registered PLMN that sent the list - nas::utils::AddToPlmnList(m_usim->m_equivalentPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); + m_storage->equivalentPlmnList->add(currentPlmn); // Store the service area list - m_usim->m_serviceAreaList = msg.serviceAreaList.value_or(nas::IEServiceAreaList{}); + m_storage->serviceAreaList->set(msg.serviceAreaList.value_or(nas::IEServiceAreaList{})); + updateForbiddenTaiListsForAllowedIndications(); // "Upon receipt of the REGISTRATION ACCEPT message, the UE shall reset the registration attempt counter and service // request attempt counter, enter state 5GMM-REGISTERED and set the 5GS update status to 5U1 UPDATED." resetRegAttemptCounter(); m_serCounter = 0; - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE); + switchMmState(EMmSubState::MM_REGISTERED_NORMAL_SERVICE); switchUState(E5UState::U1_UPDATED); // "If the ACCEPT message included a T3512 value IE, the UE shall use the value in T3512 value IE as @@ -379,7 +464,8 @@ void NasMm::receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg { if (msg.mobileIdentity->type == nas::EIdentityType::GUTI) { - m_usim->m_storedGuti = *msg.mobileIdentity; + m_storage->storedSuci->clear(); + m_storage->storedGuti->set(*msg.mobileIdentity); m_timers->t3519.stop(); sendComplete = true; } @@ -398,9 +484,9 @@ void NasMm::receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg slice.sst = rejectedSlice.sst; slice.sd = rejectedSlice.sd; - auto &list = rejectedSlice.cause == nas::ERejectedSNssaiCause::NA_IN_PLMN ? m_usim->m_rejectedNssaiInPlmn - : m_usim->m_rejectedNssaiInTa; - list.addIfNotExists(slice); + auto &nssai = rejectedSlice.cause == nas::ERejectedSNssaiCause::NA_IN_PLMN ? m_storage->rejectedNssaiInPlmn + : m_storage->rejectedNssaiInTa; + nssai->mutate([slice](auto &value) { value.addIfNotExists(slice); }); } } @@ -413,12 +499,12 @@ void NasMm::receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg } // Store the allowed NSSAI - m_usim->m_allowedNssai = nas::utils::NssaiTo(msg.allowedNSSAI.value_or(nas::IENssai{})); + m_storage->allowedNssai->set(nas::utils::NssaiTo(msg.allowedNSSAI.value_or(nas::IENssai{}))); // Process configured NSSAI IE if (msg.configuredNSSAI.has_value()) { - m_usim->m_configuredNssai = nas::utils::NssaiTo(msg.configuredNSSAI.value_or(nas::IENssai{})); + m_storage->configuredNssai->set(nas::utils::NssaiTo(msg.configuredNSSAI.value_or(nas::IENssai{}))); sendComplete = true; } @@ -426,7 +512,7 @@ void NasMm::receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg if (msg.networkFeatureSupport.has_value()) m_nwFeatureSupport = *msg.networkFeatureSupport; - // The service request attempt counter shall be reset when registration procedure for mobility and periodic + // The service request attempt counter shall be reset when registration procedure for mobility and periodic // registration update is successfully completed m_serCounter = 0; @@ -512,9 +598,9 @@ void NasMm::receiveInitialRegistrationReject(const nas::RegistrationReject &msg) cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA || cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) { - m_usim->m_storedGuti = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_taiList = {}; + m_storage->storedGuti->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->taiList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; } @@ -528,24 +614,24 @@ void NasMm::receiveInitialRegistrationReject(const nas::RegistrationReject &msg) if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); } if (cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); + switchMmState(EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); } if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) { - switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + switchMmState(EMmSubState::MM_NULL_PS); setN1Capability(false); } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + switchMmState(EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::TA_NOT_ALLOWED || @@ -558,17 +644,26 @@ void NasMm::receiveInitialRegistrationReject(const nas::RegistrationReject &msg) if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA) { - m_usim->m_equivalentPlmnList = {}; + m_storage->equivalentPlmnList->clear(); } if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRoaming, *m_usim->m_currentTai); + Tai tai = m_base->shCtx.getCurrentTai(); + if (tai.hasValue()) + { + m_storage->forbiddenTaiListRoaming->add(tai); + m_storage->serviceAreaList->mutate([&tai](auto &value) { + nas::utils::RemoveFromServiceAreaList(value, nas::VTrackingAreaIdentity{tai}); + }); + } } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) { - nas::utils::AddToPlmnList(m_usim->m_forbiddenPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); + Plmn plmn = m_base->shCtx.getCurrentPlmn(); + if (plmn.hasValue()) + m_storage->forbiddenPlmnList->add(m_base->shCtx.getCurrentPlmn()); } if (cause == nas::EMmCause::CONGESTION) @@ -576,7 +671,7 @@ void NasMm::receiveInitialRegistrationReject(const nas::RegistrationReject &msg) if (msg.t3346value.has_value() && nas::utils::HasValue(*msg.t3346value)) { switchUState(E5UState::U2_NOT_UPDATED); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); + switchMmState(EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); m_timers->t3346.stop(); if (msg.sht != nas::ESecurityHeaderType::NOT_PROTECTED) @@ -602,7 +697,7 @@ void NasMm::receiveInitialRegistrationReject(const nas::RegistrationReject &msg) else if (regType == nas::ERegistrationType::EMERGENCY_REGISTRATION) { if (cause == nas::EMmCause::PEI_NOT_ACCEPTED) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NO_SUPI); + switchMmState(EMmSubState::MM_DEREGISTERED_NO_SUPI); else { // Spec says that upper layers should be informed as well, for additional action for emergency @@ -658,9 +753,9 @@ void NasMm::receiveMobilityRegistrationReject(const nas::RegistrationReject &msg cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA || cause == nas::EMmCause::N1_MODE_NOT_ALLOWED || cause == nas::EMmCause::UE_IDENTITY_CANNOT_BE_DERIVED_FROM_NETWORK) { - m_usim->m_storedGuti = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_taiList = {}; + m_storage->storedGuti->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->taiList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; } @@ -681,29 +776,29 @@ void NasMm::receiveMobilityRegistrationReject(const nas::RegistrationReject &msg cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED || cause == nas::EMmCause::UE_IDENTITY_CANNOT_BE_DERIVED_FROM_NETWORK) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); } if (cause == nas::EMmCause::IMPLICITY_DEREGISTERED) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); + switchMmState(EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); } if (cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); + switchMmState(EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); } if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) { - switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + switchMmState(EMmSubState::MM_NULL_PS); setN1Capability(false); } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + switchMmState(EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::TA_NOT_ALLOWED || @@ -716,17 +811,25 @@ void NasMm::receiveMobilityRegistrationReject(const nas::RegistrationReject &msg if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA) { - m_usim->m_equivalentPlmnList = {}; + m_storage->equivalentPlmnList->clear(); } if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRoaming, *m_usim->m_currentTai); + Tai tai = m_base->shCtx.getCurrentTai(); + if (tai.hasValue()) + { + m_storage->forbiddenTaiListRoaming->add(tai); + m_storage->serviceAreaList->mutate( + [&tai](auto &value) { nas::utils::RemoveFromServiceAreaList(value, nas::VTrackingAreaIdentity{tai}); }); + } } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) { - nas::utils::AddToPlmnList(m_usim->m_forbiddenPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); + Plmn plmn = m_base->shCtx.getCurrentPlmn(); + if (plmn.hasValue()) + m_storage->forbiddenPlmnList->add(plmn); } if (cause == nas::EMmCause::CONGESTION) @@ -736,7 +839,7 @@ void NasMm::receiveMobilityRegistrationReject(const nas::RegistrationReject &msg if (!hasEmergency()) { switchUState(E5UState::U2_NOT_UPDATED); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); + switchMmState(EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); } m_timers->t3346.stop(); @@ -790,16 +893,16 @@ void NasMm::handleAbnormalInitialRegFailure(nas::ERegistrationType regType) if (!hasEmergency()) { m_timers->t3511.start(); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); + switchMmState(EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); } } else { // The UE shall delete 5G-GUTI, TAI list, last visited TAI, list of equivalent PLMNs and ngKSI, .. - m_usim->m_storedGuti = {}; - m_usim->m_taiList = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_equivalentPlmnList = {}; + m_storage->storedGuti->clear(); + m_storage->taiList->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->equivalentPlmnList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; @@ -810,7 +913,7 @@ void NasMm::handleAbnormalInitialRegFailure(nas::ERegistrationType regType) // 5GMM-DEREGISTERED.ATTEMPTING-REGISTRATION or optionally to 5GMM-DEREGISTERED.PLMN-SEARCH in order to perform // a PLMN selection according to 3GPP TS 23.122 [5]. switchUState(E5UState::U2_NOT_UPDATED); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); + switchMmState(EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); } } @@ -826,29 +929,29 @@ void NasMm::handleAbnormalMobilityRegFailure(nas::ERegistrationType regType) // "If the registration attempt counter is less than 5:" if (m_regCounter < 5) { - bool includedInTaiList = nas::utils::TaiListContains( - m_usim->m_taiList, nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(m_usim->m_servingCell->cellId.plmn), - octet3{m_usim->m_servingCell->tac}}); + auto tai = m_base->shCtx.getCurrentTai(); + bool includedInTaiList = + tai.hasValue() && nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{tai}); // "If the TAI of the current serving cell is not included in the TAI list or the 5GS update status is different // to 5U1 UPDATED" - if (!includedInTaiList || m_usim->m_uState != E5UState::U1_UPDATED) + if (!includedInTaiList || m_storage->uState->get() != E5UState::U1_UPDATED) { // "The UE shall start timer T3511, shall set the 5GS update status to 5U2 NOT UPDATED and change to state // 5GMM-REGISTERED.ATTEMPTING-REGISTRATION-UPDATE. When timer T3511 expires and the registration update // procedure is triggered again" m_timers->t3511.start(); // todo switchUState(E5UState::U2_NOT_UPDATED); - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE); + switchMmState(EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE); } // "If the TAI of the current serving cell is included in the TAI list, the 5GS update status is equal to 5U1 // UPDATED, and the UE is not performing the registration procedure after an inter-system change from S1 mode to // N1 mode" - if (includedInTaiList && m_usim->m_uState == E5UState::U1_UPDATED) + if (includedInTaiList && m_storage->uState->get() == E5UState::U1_UPDATED) { // "The UE shall keep the 5GS update status to 5U1 UPDATED and enter state 5GMM-REGISTERED.NORMAL-SERVICE." - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE); + switchMmState(EMmSubState::MM_REGISTERED_NORMAL_SERVICE); // "The UE shall start timer T3511" m_timers->t3511.start(); } @@ -861,8 +964,8 @@ void NasMm::handleAbnormalMobilityRegFailure(nas::ERegistrationType regType) // "The UE shall delete the list of equivalent PLMNs and shall change to state // 5GMM-REGISTERED.ATTEMPTING-REGISTRATION-UPDATE UPDATE" - m_usim->m_equivalentPlmnList = {}; - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE); + m_storage->equivalentPlmnList->clear(); + switchMmState(EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE); } } @@ -872,15 +975,7 @@ void NasMm::resetRegAttemptCounter() // the UE shall stop timer T3519 if running, and delete any stored SUCI" m_regCounter = 0; m_timers->t3519.stop(); - m_usim->m_storedSuci = {}; - - // TODO: Registration attempt counter shall be reset for these cases as well (not implemented yet) - // - the UE is powered on; - // - a USIM is inserted - // "Additionally, the registration attempt counter shall be reset when the UE is in substate - // 5GMM-DEREGISTERED.ATTEMPTING-REGISTRATION or 5GMM-REGISTERED.ATTEMPTING-REGISTRATION-UPDATE, and:" - // - a new tracking area is entered; - // - timer T3346 is started. + m_storage->storedSuci->clear(); } } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/mm/sap.cpp b/src/ue/nas/mm/sap.cpp new file mode 100644 index 000000000..ff0f0f54c --- /dev/null +++ b/src/ue/nas/mm/sap.cpp @@ -0,0 +1,83 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "mm.hpp" + +#include + +namespace nr::ue +{ + +void NasMm::handleRrcEvent(const NmUeRrcToNas &msg) +{ + if (m_mmState == EMmState::MM_NULL) + return; + + switch (msg.present) + { + case NmUeRrcToNas::RRC_CONNECTION_SETUP: { + handleRrcConnectionSetup(); + break; + } + case NmUeRrcToNas::NAS_DELIVERY: { + OctetView buffer{msg.nasPdu}; + auto nasMessage = nas::DecodeNasMessage(buffer); + if (nasMessage != nullptr) + receiveNasMessage(*nasMessage); + break; + } + case NmUeRrcToNas::RRC_CONNECTION_RELEASE: { + handleRrcConnectionRelease(); + break; + } + case NmUeRrcToNas::RADIO_LINK_FAILURE: { + handleRadioLinkFailure(); + break; + } + case NmUeRrcToNas::PAGING: { + handlePaging(msg.pagingTmsi); + break; + } + case NmUeRrcToNas::NAS_NOTIFY: { + triggerMmCycle(); + break; + } + case NmUeRrcToNas::ACTIVE_CELL_CHANGED: { + handleActiveCellChange(msg.previousTai); + break; + } + case NmUeRrcToNas::RRC_ESTABLISHMENT_FAILURE: { + handleRrcEstablishmentFailure(); + break; + } + case NmUeRrcToNas::RRC_FALLBACK_INDICATION: { + handleRrcFallbackIndication(); + break; + } + } +} + +void NasMm::handleNasEvent(const NmUeNasToNas &msg) +{ + if (m_mmState == EMmState::MM_NULL) + return; + + switch (msg.present) + { + case NmUeNasToNas::PERFORM_MM_CYCLE: + performMmCycle(); + break; + case NmUeNasToNas::NAS_TIMER_EXPIRE: + onTimerExpire(*msg.timer); + break; + default: + break; + } +} + +} // namespace nr::ue diff --git a/src/ue/nas/mm/service.cpp b/src/ue/nas/mm/service.cpp index 2774dce3e..7491fd41d 100644 --- a/src/ue/nas/mm/service.cpp +++ b/src/ue/nas/mm/service.cpp @@ -14,30 +14,55 @@ namespace nr::ue { -void NasMm::sendServiceRequest(EServiceReqCause reqCause) +EProcRc NasMm::sendServiceRequest(EServiceReqCause reqCause) { - m_logger->debug("Sending Service Request due to [%s]", ToJson(reqCause).str().c_str()); - // "The procedure shall only be initiated by the UE when the following conditions are fulfilled ..." if (m_mmState == EMmState::MM_REGISTERED_INITIATED || m_mmState == EMmState::MM_DEREGISTERED_INITIATED) { m_logger->warn("Service Request canceled, MM specific procedure is ongoing"); - return; + return EProcRc::STAY; } if (m_mmState == EMmState::MM_SERVICE_REQUEST_INITIATED) { m_logger->debug("Service Request canceled, already in 5GMM-SERVICE-REQUEST-INITIATED"); - return; + return EProcRc::CANCEL; } - if (m_usim->m_uState != E5UState::U1_UPDATED) + if (m_storage->uState->get() != E5UState::U1_UPDATED) { m_logger->err("Service Request canceled, UE not in 5U1 UPDATED state"); - return; + return EProcRc::STAY; } - if (!nas::utils::TaiListContains(m_usim->m_taiList, *m_usim->m_currentTai)) + Tai currentTai = m_base->shCtx.getCurrentTai(); + if (!currentTai.hasValue()) + { + m_logger->err("Service Request canceled, no active cell exists"); + return EProcRc::STAY; + } + if (!nas::utils::TaiListContains(m_storage->taiList->get(), nas::VTrackingAreaIdentity{currentTai})) { m_logger->err("Service Request canceled, current TAI is not in the TAI list"); - return; + return EProcRc::CANCEL; + } + + if (m_mmSubState == EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE) + { + if (reqCause != EServiceReqCause::IDLE_PAGING && + reqCause != EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP && + reqCause != EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP && !isHighPriority() && !hasEmergency()) + { + m_logger->debug("Service Request canceled, registered in non allowed service"); + return EProcRc::CANCEL; + } + } + + if (m_mmSubState == EMmSubState::MM_REGISTERED_UPDATE_NEEDED) + { + if (reqCause != EServiceReqCause::IDLE_PAGING && + reqCause != EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP && + reqCause != EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP) + { + return EProcRc::STAY; + } } // 5.6.1.7 Abnormal cases in the UE @@ -46,11 +71,11 @@ void NasMm::sendServiceRequest(EServiceReqCause reqCause) { if (reqCause != EServiceReqCause::IDLE_PAGING && reqCause != EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP && - reqCause != EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP && !isHighPriority() && !hasEmergency() && - reqCause != EServiceReqCause::EMERGENCY_FALLBACK) + reqCause != EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP && + reqCause != EServiceReqCause::EMERGENCY_FALLBACK && !isHighPriority() && !hasEmergency()) { m_logger->debug("Service Request canceled, T3346 is running"); - return; + return EProcRc::STAY; } } // c) Timer T3346 is running. @@ -62,10 +87,14 @@ void NasMm::sendServiceRequest(EServiceReqCause reqCause) reqCause != EServiceReqCause::EMERGENCY_FALLBACK) { m_logger->debug("Service Request canceled, T3346 is running"); - return; + return EProcRc::STAY; } } + m_logger->debug("Sending Service Request due to [%s]", ToJson(reqCause).str().c_str()); + + updateProvidedGuti(); + auto request = std::make_unique(); if (reqCause == EServiceReqCause::IDLE_PAGING) @@ -130,7 +159,14 @@ void NasMm::sendServiceRequest(EServiceReqCause reqCause) request->serviceType.serviceType = nas::EServiceType::HIGH_PRIORITY_ACCESS; else { - // TODO: fallback indication not supported yet + // From 5.6.1.2: + // "a) if the pending message is an UL NAS TRANSPORT message with the Request type IE set to "initial + // emergency request" or "existing emergency PDU session", the UE shall set the Service type IE in the + // SERVICE REQUEST message to "emergency services"; or + // b) otherwise, the UE shall set the Service type IE in the SERVICE REQUEST message to "signalling"." + // Just check if the UE has an emergency + request->serviceType.serviceType = + hasEmergency() ? nas::EServiceType::EMERGENCY_SERVICES : nas::EServiceType::SIGNALLING; } } @@ -142,7 +178,7 @@ void NasMm::sendServiceRequest(EServiceReqCause reqCause) } // Assign TMSI (TMSI is a part of GUTI) - request->tmsi = m_usim->m_storedGuti; + request->tmsi = m_storage->storedGuti->get(); if (request->tmsi.type != nas::EIdentityType::NO_IDENTITY) { request->tmsi.type = nas::EIdentityType::TMSI; @@ -155,11 +191,16 @@ void NasMm::sendServiceRequest(EServiceReqCause reqCause) request->pduSessionStatus->psi = m_sm->getPduSessionStatus(); // Send the message and process the timers - sendNasMessage(*request); + auto rc = sendNasMessage(*request); + if (rc != EProcRc::OK) + return rc; m_lastServiceRequest = std::move(request); m_lastServiceReqCause = reqCause; m_timers->t3517.start(); - switchMmState(EMmState::MM_SERVICE_REQUEST_INITIATED, EMmSubState::MM_SERVICE_REQUEST_INITIATED_NA); + + switchMmState(EMmSubState::MM_SERVICE_REQUEST_INITIATED_PS); + + return EProcRc::OK; } void NasMm::receiveServiceAccept(const nas::ServiceAccept &msg) @@ -176,7 +217,7 @@ void NasMm::receiveServiceAccept(const nas::ServiceAccept &msg) m_logger->info("Service Accept received"); m_serCounter = 0; m_timers->t3517.stop(); - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + switchMmState(EMmSubState::MM_REGISTERED_PS); } else { @@ -245,7 +286,7 @@ void NasMm::receiveServiceReject(const nas::ServiceReject &msg) auto handleAbnormalCase = [this]() { m_logger->debug("Handling Service Reject abnormal case"); - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + switchMmState(EMmSubState::MM_REGISTERED_PS); m_timers->t3517.stop(); }; @@ -286,9 +327,9 @@ void NasMm::receiveServiceReject(const nas::ServiceReject &msg) cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) { - m_usim->m_storedGuti = {}; - m_usim->m_lastVisitedRegisteredTai = {}; - m_usim->m_taiList = {}; + m_storage->storedGuti->clear(); + m_storage->lastVisitedRegisteredTai->clear(); + m_storage->taiList->clear(); m_usim->m_currentNsCtx = {}; m_usim->m_nonCurrentNsCtx = {}; } @@ -300,23 +341,36 @@ void NasMm::receiveServiceReject(const nas::ServiceReject &msg) if (cause == nas::EMmCause::PLMN_NOT_ALLOWED) { - m_usim->m_equivalentPlmnList = {}; + m_storage->equivalentPlmnList->clear(); } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) { - nas::utils::AddToPlmnList(m_usim->m_forbiddenPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); + Plmn plmn = m_base->shCtx.getCurrentPlmn(); + if (plmn.hasValue()) + m_storage->forbiddenPlmnList->add(plmn); } if (cause == nas::EMmCause::TA_NOT_ALLOWED) { - nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRps, *m_usim->m_currentTai); + Tai tai = m_base->shCtx.getCurrentTai(); + if (tai.hasValue()) + { + m_storage->forbiddenTaiListRps->add(tai); + m_storage->serviceAreaList->mutate( + [&tai](auto &value) { nas::utils::RemoveFromServiceAreaList(value, nas::VTrackingAreaIdentity{tai}); }); + } } if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRoaming, *m_usim->m_currentTai); - nas::utils::RemoveFromTaiList(m_usim->m_taiList, *m_usim->m_currentTai); + Tai tai = m_base->shCtx.getCurrentTai(); + if (tai.hasValue()) + { + m_storage->forbiddenTaiListRoaming->add(tai); + m_storage->taiList->mutate( + [&tai](auto &value) { nas::utils::RemoveFromTaiList(value, nas::VTrackingAreaIdentity{tai}); }); + } } if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || @@ -329,56 +383,50 @@ void NasMm::receiveServiceReject(const nas::ServiceReject &msg) cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED || cause == nas::EMmCause::UE_IDENTITY_CANNOT_BE_DERIVED_FROM_NETWORK) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); } if (cause == nas::EMmCause::IMPLICITY_DEREGISTERED) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); + switchMmState(EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); } if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + switchMmState(EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); } if (cause == nas::EMmCause::TA_NOT_ALLOWED) { - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); + switchMmState(EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); } if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA) { - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_PLMN_SEARCH); + switchMmState(EMmSubState::MM_REGISTERED_PLMN_SEARCH); } if (cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_LIMITED_SERVICE); - } - - if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) - { - switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); - - setN1Capability(false); + switchMmState(EMmSubState::MM_REGISTERED_LIMITED_SERVICE); } if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) { + switchMmState(EMmSubState::MM_NULL_PS); setN1Capability(false); } if (cause == nas::EMmCause::UE_IDENTITY_CANNOT_BE_DERIVED_FROM_NETWORK) { if (m_lastServiceReqCause != EServiceReqCause::EMERGENCY_FALLBACK) - sendInitialRegistration(EInitialRegCause::DUE_TO_SERVICE_REJECT); + initialRegistrationRequired(EInitialRegCause::DUE_TO_SERVICE_REJECT); } if (cause == nas::EMmCause::IMPLICITY_DEREGISTERED) { if (!hasEmergency()) - sendInitialRegistration(EInitialRegCause::DUE_TO_SERVICE_REJECT); + initialRegistrationRequired(EInitialRegCause::DUE_TO_SERVICE_REJECT); } if (cause == nas::EMmCause::CONGESTION) @@ -387,7 +435,7 @@ void NasMm::receiveServiceReject(const nas::ServiceReject &msg) { if (!hasEmergency()) { - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + switchMmState(EMmSubState::MM_REGISTERED_PS); m_timers->t3517.stop(); } @@ -407,10 +455,10 @@ void NasMm::receiveServiceReject(const nas::ServiceReject &msg) if (cause == nas::EMmCause::RESTRICTED_SERVICE_AREA) { - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE); + switchMmState(EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE); if (m_lastServiceRequest->serviceType.serviceType != nas::EServiceType::ELEVATED_SIGNALLING) - sendMobilityRegistration(ERegUpdateCause::RESTRICTED_SERVICE_AREA); + mobilityUpdatingRequired(ERegUpdateCause::RESTRICTED_SERVICE_AREA); } if (hasEmergency()) diff --git a/src/ue/nas/mm/slice.cpp b/src/ue/nas/mm/slice.cpp index 6dda9b236..6629f62aa 100644 --- a/src/ue/nas/mm/slice.cpp +++ b/src/ue/nas/mm/slice.cpp @@ -43,25 +43,25 @@ NetworkSlice NasMm::makeRequestedNssai(bool &isDefaultNssai) const NetworkSlice res{}; - if (!m_usim->m_allowedNssai.slices.empty() || !m_usim->m_configuredNssai.slices.empty()) + if (!m_storage->allowedNssai->get().slices.empty() || !m_storage->configuredNssai->get().slices.empty()) { - if (!m_usim->m_allowedNssai.slices.empty()) + if (!m_storage->allowedNssai->get().slices.empty()) { - AppendSubset(m_usim->m_allowedNssai, res, m_usim->m_rejectedNssaiInPlmn, m_usim->m_rejectedNssaiInTa, - 8); - AppendSubset(m_usim->m_configuredNssai, res, m_usim->m_rejectedNssaiInPlmn, - m_usim->m_rejectedNssaiInTa, static_cast(8) - res.slices.size()); + AppendSubset(m_storage->allowedNssai->get(), res, m_storage->rejectedNssaiInPlmn->get(), + m_storage->rejectedNssaiInTa->get(), 8); + AppendSubset(m_storage->configuredNssai->get(), res, m_storage->rejectedNssaiInPlmn->get(), + m_storage->rejectedNssaiInTa->get(), static_cast(8) - res.slices.size()); } else { - AppendSubset(m_usim->m_configuredNssai, res, m_usim->m_rejectedNssaiInPlmn, - m_usim->m_rejectedNssaiInTa, 8); + AppendSubset(m_storage->configuredNssai->get(), res, m_storage->rejectedNssaiInPlmn->get(), + m_storage->rejectedNssaiInTa->get(), 8); } } - else if (!m_usim->m_defConfiguredNssai.slices.empty()) + else if (!m_storage->defConfiguredNssai->get().slices.empty()) { - AppendSubset(m_usim->m_defConfiguredNssai, res, m_usim->m_rejectedNssaiInPlmn, - m_usim->m_rejectedNssaiInTa, 8); + AppendSubset(m_storage->defConfiguredNssai->get(), res, m_storage->rejectedNssaiInPlmn->get(), + m_storage->rejectedNssaiInTa->get(), 8); isDefaultNssai = true; } diff --git a/src/ue/nas/mm/timer.cpp b/src/ue/nas/mm/timer.cpp index 9b94cbda9..187d96b8b 100644 --- a/src/ue/nas/mm/timer.cpp +++ b/src/ue/nas/mm/timer.cpp @@ -16,7 +16,7 @@ namespace nr::ue { -void NasMm::onTimerExpire(nas::NasTimer &timer) +void NasMm::onTimerExpire(UeTimer &timer) { auto logExpired = [this, &timer]() { m_logger->debug("NAS timer[%d] expired [%d]", timer.getCode(), timer.getExpiryCount()); @@ -28,7 +28,17 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE) { logExpired(); - sendInitialRegistration(EInitialRegCause::T3346_EXPIRY); + initialRegistrationRequired(EInitialRegCause::T3346_EXPIRY); + } + else if (m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE) + { + logExpired(); + mobilityUpdatingRequired(ERegUpdateCause::T3346_EXPIRY_IN_ATT_UPD); + } + else if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION) + { + logExpired(); + initialRegistrationRequired(EInitialRegCause::T3346_EXPIRY_IN_ATT_REG); } break; } @@ -38,6 +48,11 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) { logExpired(); resetRegAttemptCounter(); + + if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION) + initialRegistrationRequired(EInitialRegCause::T3502_EXPIRY_IN_ATT_REG); + if (m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE) + mobilityUpdatingRequired(ERegUpdateCause::T3502_EXPIRY_IN_ATT_UPD); } break; } @@ -53,13 +68,13 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) // The UE shall abort the registration procedure for initial registration and the NAS signalling // connection, if any, shall be released locally if the initial registration request is not for // emergency services.. - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); switchUState(E5UState::U2_NOT_UPDATED); if (m_lastRegistrationRequest->registrationType.registrationType != nas::ERegistrationType::EMERGENCY_REGISTRATION) { - localReleaseConnection(); + localReleaseConnection(false); } handleAbnormalInitialRegFailure(regType); @@ -67,21 +82,34 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) else if (regType == nas::ERegistrationType::MOBILITY_REGISTRATION_UPDATING || regType == nas::ERegistrationType::PERIODIC_REGISTRATION_UPDATING) { - localReleaseConnection(); + localReleaseConnection(false); handleAbnormalMobilityRegFailure(regType); } } break; } case 3511: { - // TODO + if (m_mmSubState == EMmSubState::MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE) + { + logExpired(); + mobilityUpdatingRequired(ERegUpdateCause::T3511_EXPIRY_IN_ATT_UPD); + } + else if (m_mmSubState == EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION) + { + logExpired(); + initialRegistrationRequired(EInitialRegCause::T3511_EXPIRY_IN_ATT_REG); + } break; } case 3512: { - if (m_mmState == EMmState::MM_REGISTERED && m_cmState == ECmState::CM_CONNECTED) + if (m_mmState == EMmState::MM_REGISTERED) { logExpired(); - sendMobilityRegistration(ERegUpdateCause::T3512_EXPIRY); + + if (m_registeredForEmergency) + performLocalDeregistration(); + else + mobilityUpdatingRequired(ERegUpdateCause::T3512_EXPIRY); } break; } @@ -95,7 +123,7 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) { logExpired(); - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + switchMmState(EMmSubState::MM_REGISTERED_PS); if (m_cmState == ECmState::CM_IDLE && m_lastServiceReqCause != EServiceReqCause::EMERGENCY_FALLBACK) { @@ -111,7 +139,7 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) break; } case 3519: { - m_usim->m_storedSuci = {}; + m_storage->storedSuci->clear(); break; } case 3520: { @@ -129,10 +157,10 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) m_logger->debug("De-registration aborted"); if (m_lastDeregCause == EDeregCause::DISABLE_5G) - switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + switchMmState(EMmSubState::MM_NULL_PS); else if (m_lastDeregistrationRequest->deRegistrationType.switchOff == nas::ESwitchOff::NORMAL_DE_REGISTRATION) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + switchMmState(EMmSubState::MM_DEREGISTERED_PS); } } else diff --git a/src/ue/nas/mm/transport.cpp b/src/ue/nas/mm/transport.cpp index 633a021e3..54e354027 100644 --- a/src/ue/nas/mm/transport.cpp +++ b/src/ue/nas/mm/transport.cpp @@ -82,8 +82,8 @@ void NasMm::receiveDlNasTransport(const nas::DlNasTransport &msg) case nas::EMmCause::RESTRICTED_SERVICE_AREA: { if (m_rmState == ERmState::RM_REGISTERED) { - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE); - sendMobilityRegistration(ERegUpdateCause::RESTRICTED_SERVICE_AREA); + switchMmState(EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE); + mobilityUpdatingRequired(ERegUpdateCause::RESTRICTED_SERVICE_AREA); } m_sm->receiveForwardingFailure(smMessage, msg.mmCause->value, std::nullopt); break; @@ -107,7 +107,7 @@ void NasMm::receiveDlNasTransport(const nas::DlNasTransport &msg) } } -void NasMm::deliverUlTransport(const nas::UlNasTransport &msg) +EProcRc NasMm::deliverUlTransport(const nas::UlNasTransport &msg) { // 5.4.5.2.6 Abnormal cases in the UE // "The UE shall not send the UL NAS TRANSPORT message when the UE is in non-allowed area or @@ -126,12 +126,16 @@ void NasMm::deliverUlTransport(const nas::UlNasTransport &msg) msg.payloadContainerType.payloadContainerType == nas::EPayloadContainerType::SMS) { m_logger->err("Ul Nas Transport procedure canceled, UE is not in allowed area"); - return; + return EProcRc::STAY; } } // Send the UL NAS Transport Message - sendNasMessage(msg); + auto rc = sendNasMessage(msg); + if (rc != EProcRc::OK) + return rc; + + return EProcRc::OK; } } // namespace nr::ue diff --git a/src/ue/nas/sm/base.cpp b/src/ue/nas/sm/base.cpp index 6850756d1..c4207df2c 100644 --- a/src/ue/nas/sm/base.cpp +++ b/src/ue/nas/sm/base.cpp @@ -11,7 +11,7 @@ namespace nr::ue { -NasSm::NasSm(TaskBase *base, UeTimers *timers) : m_base(base), m_timers(timers), m_mm(nullptr) +NasSm::NasSm(TaskBase *base, NasTimers *timers) : m_base(base), m_timers(timers), m_mm(nullptr) { m_logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "nas"); @@ -29,18 +29,4 @@ void NasSm::onQuit() // TODO } -void NasSm::establishInitialSessions() -{ - if (m_base->config->initSessions.empty()) - { - m_logger->warn("No initial PDU sessions are configured"); - return; - } - - m_logger->info("Initial PDU sessions are establishing [%d#]", m_base->config->initSessions.size()); - - for (auto &sess : m_base->config->initSessions) - sendEstablishmentRequest(sess); -} - } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/sm/establishment.cpp b/src/ue/nas/sm/establishment.cpp index 8ab8863b8..e6b60c048 100644 --- a/src/ue/nas/sm/establishment.cpp +++ b/src/ue/nas/sm/establishment.cpp @@ -41,19 +41,25 @@ void NasSm::sendEstablishmentRequest(const SessionConfig &config) m_logger->debug("Sending PDU Session Establishment Request"); /* Control the protocol state */ - if (!m_mm->isRegistered()) + if (m_mm->m_rmState != ERmState::RM_REGISTERED) { m_logger->err("PDU session establishment could not be triggered, UE is not registered"); return; } + if (m_mm->m_mmSubState == EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE && !m_mm->hasEmergency() && !m_mm->isHighPriority()) + { + m_logger->err("PDU session establishment could not be triggered, non allowed service condition"); + return; + } + /* Control the received config */ if (config.type != nas::EPduSessionType::IPV4) { m_logger->err("PDU session type [%s] is not supported", nas::utils::EnumToString(config.type)); return; } - if (m_mm->isRegisteredForEmergency() && !config.isEmergency) + if (m_mm->m_rmState == ERmState::RM_REGISTERED && m_mm->m_registeredForEmergency && !config.isEmergency) { m_logger->err("Non-emergency PDU session cannot be requested, UE is registered for emergency only"); return; @@ -164,7 +170,7 @@ void NasSm::receiveEstablishmentAccept(const nas::PduSessionEstablishmentAccept else pduSession->pduAddress = {}; - auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::SESSION_ESTABLISHMENT); + auto *statusUpdate = new NmUeStatusUpdate(NmUeStatusUpdate::SESSION_ESTABLISHMENT); statusUpdate->pduSession = pduSession; m_base->appTask->push(statusUpdate); diff --git a/src/ue/nas/sm/interface.cpp b/src/ue/nas/sm/interface.cpp deleted file mode 100644 index 6fc6a982b..000000000 --- a/src/ue/nas/sm/interface.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// -// This file is a part of UERANSIM open source project. -// Copyright (c) 2021 ALİ GÜNGÖR. -// -// The software and all associated files are licensed under GPL-3.0 -// and subject to the terms and conditions defined in LICENSE file. -// - -#include "sm.hpp" -#include -#include -#include -#include -#include - -namespace nr::ue -{ - -void NasSm::handleNasEvent(const NwUeNasToNas &msg) -{ - switch (msg.present) - { - case NwUeNasToNas::NAS_TIMER_EXPIRE: - onTimerExpire(*msg.timer); - break; - default: - break; - } -} - -void NasSm::onTimerTick() -{ - int pti = 0; - for (auto &pt : m_procedureTransactions) - { - if (pt.timer && pt.timer->performTick()) - onTransactionTimerExpire(pti); - pti++; - } -} - -} // namespace nr::ue diff --git a/src/ue/nas/sm/release.cpp b/src/ue/nas/sm/release.cpp index 8cadb9f16..006385cb8 100644 --- a/src/ue/nas/sm/release.cpp +++ b/src/ue/nas/sm/release.cpp @@ -18,6 +18,13 @@ namespace nr::ue void NasSm::sendReleaseRequest(int psi) { + /* Control the protocol state */ + if (m_mm->m_mmSubState == EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE && !m_mm->hasEmergency() && !m_mm->isHighPriority()) + { + m_logger->err("PDU session release could not start, non allowed service condition"); + return; + } + /* Control the PDU session state */ auto &ps = m_pduSessions[psi]; if (ps->psState != EPsState::ACTIVE) @@ -143,7 +150,7 @@ void NasSm::receiveReleaseCommand(const nas::PduSessionReleaseCommand &msg) } /* Construct Release Complete message */ - nas::PduSessionReleaseComplete resp{}; + nas::PduSessionReleaseComplete resp; resp.pduSessionId = psi; resp.pti = pti; diff --git a/src/ue/nas/sm/resource.cpp b/src/ue/nas/sm/resource.cpp index 733f6fe69..86783785e 100644 --- a/src/ue/nas/sm/resource.cpp +++ b/src/ue/nas/sm/resource.cpp @@ -39,7 +39,7 @@ void NasSm::localReleaseSession(int psi) if (isEstablished) { - auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::SESSION_RELEASE); + auto *statusUpdate = new NmUeStatusUpdate(NmUeStatusUpdate::SESSION_RELEASE); statusUpdate->psi = psi; m_base->appTask->push(statusUpdate); } @@ -62,15 +62,18 @@ bool NasSm::anyEmergencySession() void NasSm::handleUplinkStatusChange(int psi, bool isPending) { m_logger->debug("Uplink data status changed PSI[%d] pending[%s]", psi, isPending ? "true" : "false"); - m_pduSessions[psi]->uplinkPending = isPending; if (isPending) - m_mm->serviceNeededForUplinkData(); + m_mm->serviceRequestRequiredForData(); + + m_mm->triggerMmCycle(); } bool NasSm::anyUplinkDataPending() { + // TODO: look for only resources are already established etc + auto status = getUplinkDataStatus(); for (int i = 1; i < 16; i++) if (status[i]) @@ -105,4 +108,51 @@ std::bitset<16> NasSm::getPduSessionStatus() return res; } +void NasSm::establishRequiredSessions() +{ + if (m_mm->hasEmergency()) + { + if (!anyEmergencySession()) + { + SessionConfig config; + config.type = nas::EPduSessionType::IPV4; + config.apn = std::nullopt; + config.sNssai = std::nullopt; + config.isEmergency = true; + sendEstablishmentRequest(config); + } + + return; + } + + for (auto &config : m_base->config->defaultSessions) + { + if (!anySessionMatches(config)) + sendEstablishmentRequest(config); + } +} + +bool NasSm::anySessionMatches(const SessionConfig &config) +{ + // ACTIVE_PENDING etc. are also included + return std::any_of(m_pduSessions.begin(), m_pduSessions.end(), [&config](auto &ps) { + if (ps->psState == EPsState::INACTIVE) + return false; + + if (config.isEmergency) + return ps->isEmergency; + + if (config.isEmergency != ps->isEmergency) + return false; + if (config.type != ps->sessionType) + return false; + if (config.apn != ps->apn) + return false; + if (config.sNssai != ps->sNssai) + return false; + + return true; + }); +} + } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/sm/sap.cpp b/src/ue/nas/sm/sap.cpp new file mode 100644 index 000000000..6c1270367 --- /dev/null +++ b/src/ue/nas/sm/sap.cpp @@ -0,0 +1,107 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "sm.hpp" + +#include + +#include +#include +#include +#include + +namespace nr::ue +{ + +void NasSm::handleNasEvent(const NmUeNasToNas &msg) +{ + if (m_mm->m_mmState == EMmState::MM_NULL) + return; + + switch (msg.present) + { + case NmUeNasToNas::NAS_TIMER_EXPIRE: + onTimerExpire(*msg.timer); + break; + default: + break; + } +} + +void NasSm::onTimerTick() +{ + if (m_mm->m_mmState == EMmState::MM_NULL) + return; + + int pti = 0; + for (auto &pt : m_procedureTransactions) + { + if (pt.timer && pt.timer->performTick()) + onTransactionTimerExpire(pti); + pti++; + } +} + +void NasSm::handleUplinkDataRequest(int psi, OctetString &&data) +{ + auto state = m_mm->m_mmSubState; + if (state != EMmSubState::MM_REGISTERED_INITIATED_PS && state != EMmSubState::MM_REGISTERED_NORMAL_SERVICE && + state != EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE && + state != EMmSubState::MM_REGISTERED_LIMITED_SERVICE && state != EMmSubState::MM_DEREGISTERED_INITIATED_PS && + state != EMmSubState::MM_SERVICE_REQUEST_INITIATED_PS) + return; + + if (m_pduSessions[psi]->psState != EPsState::ACTIVE) + return; + + if (m_mm->m_cmState == ECmState::CM_CONNECTED) + { + // TODO: We should also check if radio resources are established by RRC. + // Checking CM state is not sufficient + + if (m_pduSessions[psi]->uplinkPending) + { + m_pduSessions[psi]->uplinkPending = false; + handleUplinkStatusChange(psi, false); + } + + auto *m = new NmUeNasToRls(NmUeNasToRls::DATA_PDU_DELIVERY); + m->psi = psi; + m->pdu = std::move(data); + m_base->rlsTask->push(m); + } + else + { + if (!m_pduSessions[psi]->uplinkPending) + { + m_pduSessions[psi]->uplinkPending = true; + handleUplinkStatusChange(psi, true); + } + } +} + +void NasSm::handleDownlinkDataRequest(int psi, OctetString &&data) +{ + if (m_mm->m_cmState == ECmState::CM_IDLE) + return; + + auto state = m_mm->m_mmSubState; + if (state != EMmSubState::MM_REGISTERED_INITIATED_PS && state != EMmSubState::MM_REGISTERED_NORMAL_SERVICE && + state != EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE && + state != EMmSubState::MM_REGISTERED_LIMITED_SERVICE && state != EMmSubState::MM_DEREGISTERED_INITIATED_PS && + state != EMmSubState::MM_SERVICE_REQUEST_INITIATED_PS) + return; + + auto *w = new NmUeNasToApp(NmUeNasToApp::DOWNLINK_DATA_DELIVERY); + w->psi = psi; + w->data = std::move(data); + + m_base->appTask->push(w); +} + +} // namespace nr::ue diff --git a/src/ue/nas/sm/sm.hpp b/src/ue/nas/sm/sm.hpp index 00127973b..ab9a3bde9 100644 --- a/src/ue/nas/sm/sm.hpp +++ b/src/ue/nas/sm/sm.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -25,7 +24,7 @@ class NasSm { private: TaskBase *m_base; - UeTimers *m_timers; + NasTimers *m_timers; std::unique_ptr m_logger; NasMm *m_mm; @@ -33,22 +32,17 @@ class NasSm std::array m_procedureTransactions{}; friend class UeCmdHandler; + friend class NasMm; + friend class NasTask; public: - NasSm(TaskBase *base, UeTimers *timers); + NasSm(TaskBase *base, NasTimers *timers); - public: - /* Base */ + public: /* Base */ void onStart(NasMm *mm); void onQuit(); - void establishInitialSessions(); - - /* Transport */ - void receiveSmMessage(const nas::SmMessage &msg); - void receiveForwardingFailure(const nas::SmMessage &msg, nas::EMmCause cause, - const std::optional &backoffTimer); - /* Resource */ + private: /* Resource */ void localReleaseSession(int psi); void localReleaseAllSessions(); bool anyEmergencySession(); @@ -57,46 +51,49 @@ class NasSm bool anyEmergencyUplinkDataPending(); std::bitset<16> getUplinkDataStatus(); std::bitset<16> getPduSessionStatus(); + void establishRequiredSessions(); + bool anySessionMatches(const SessionConfig &config); - /* Session Release */ - void sendReleaseRequest(int psi); - void sendReleaseRequestForAll(); - - private: - /* Transport */ + private: /* Transport */ + void receiveSmMessage(const nas::SmMessage &msg); void sendSmMessage(int psi, const nas::SmMessage &msg); void receiveSmStatus(const nas::FiveGSmStatus &msg); void sendSmCause(const nas::ESmCause &cause, int pti, int psi); + void receiveForwardingFailure(const nas::SmMessage &msg, nas::EMmCause cause, + const std::optional &backoffTimer); - /* Allocation */ + private: /* Allocation */ int allocatePduSessionId(const SessionConfig &config); int allocateProcedureTransactionId(); void freeProcedureTransactionId(int pti); void freePduSessionId(int psi); - /* Session Establishment */ + private: /* Session Establishment */ void sendEstablishmentRequest(const SessionConfig &config); void receiveEstablishmentAccept(const nas::PduSessionEstablishmentAccept &msg); void receiveEstablishmentReject(const nas::PduSessionEstablishmentReject &msg); - /* Session Release */ + private: /* Session Release */ + void sendReleaseRequest(int psi); + void sendReleaseRequestForAll(); void receiveReleaseReject(const nas::PduSessionReleaseReject &msg); void receiveReleaseCommand(const nas::PduSessionReleaseCommand &msg); - /* Timer */ - std::unique_ptr newTransactionTimer(int code); - void onTimerExpire(nas::NasTimer &timer); + private: /* Timer */ + std::unique_ptr newTransactionTimer(int code); + void onTimerExpire(UeTimer &timer); void onTransactionTimerExpire(int pti); - /* Procedure */ + private: /* Procedure */ bool checkPtiAndPsi(const nas::SmMessage &msg); void abortProcedureByPti(int pti); void abortProcedureByPtiOrPsi(int pti, int psi); - public: - /* Interface */ - void handleNasEvent(const NwUeNasToNas &msg); // used by NAS - void onTimerTick(); // used by NAS + private: /* Service Access Point */ + void handleNasEvent(const NmUeNasToNas &msg); + void onTimerTick(); + void handleUplinkDataRequest(int psi, OctetString &&data); + void handleDownlinkDataRequest(int psi, OctetString &&data); }; } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/sm/timer.cpp b/src/ue/nas/sm/timer.cpp index 3043cbb8d..b1ad0a901 100644 --- a/src/ue/nas/sm/timer.cpp +++ b/src/ue/nas/sm/timer.cpp @@ -16,20 +16,20 @@ namespace nr::ue { -std::unique_ptr NasSm::newTransactionTimer(int code) +std::unique_ptr NasSm::newTransactionTimer(int code) { - std::unique_ptr timer; + std::unique_ptr timer; switch (code) { case 3580: - timer = std::make_unique(3580, false, 16); + timer = std::make_unique(3580, false, 16); break; case 3581: - timer = std::make_unique(3581, false, 16); + timer = std::make_unique(3581, false, 16); break; case 3582: - timer = std::make_unique(3582, false, 16); + timer = std::make_unique(3582, false, 16); break; default: m_logger->err("Bad SM transaction timer code"); @@ -40,7 +40,7 @@ std::unique_ptr NasSm::newTransactionTimer(int code) return timer; } -void NasSm::onTimerExpire(nas::NasTimer &timer) +void NasSm::onTimerExpire(UeTimer &timer) { } @@ -79,7 +79,7 @@ void NasSm::onTransactionTimerExpire(int pti) { m_logger->err("PDU Session Release procedure failure, no response from the network after 5 attempts"); abortProcedureByPti(pti); - m_mm->sendMobilityRegistration(ERegUpdateCause::PS_STATUS_INFORM); + m_mm->mobilityUpdatingRequired(ERegUpdateCause::PS_STATUS_INFORM); } break; } diff --git a/src/ue/nas/storage.cpp b/src/ue/nas/storage.cpp new file mode 100644 index 000000000..6af0682c4 --- /dev/null +++ b/src/ue/nas/storage.cpp @@ -0,0 +1,79 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "storage.hpp" + +static void BackupTaiListInSharedCtx(const std::vector &buffer, size_t count, Locked> &target) +{ + target.mutate([count, &buffer](auto &value) { + value.clear(); + for (size_t i = 0; i < count; i++) + value.push_back(buffer[i]); + }); +} + +namespace nr::ue +{ + +MmStorage::MmStorage(TaskBase *base) : m_base{base} +{ + uState = std::make_unique>(0, std::nullopt); + + storedSuci = std::make_unique>(0, std::nullopt); + + storedGuti = std::make_unique>(0, std::nullopt); + + lastVisitedRegisteredTai = std::make_unique>(0, std::nullopt); + + forbiddenTaiListRoaming = std::make_unique>( + 40, (1000ll * 60ll * 60ll * 12ll), [this](const std::vector &buffer, size_t count) { + BackupTaiListInSharedCtx(buffer, count, m_base->shCtx.forbiddenTaiRoaming); + }); + + forbiddenTaiListRps = std::make_unique>( + 40, (1000ll * 60ll * 60ll * 12ll), [this](const std::vector &buffer, size_t count) { + BackupTaiListInSharedCtx(buffer, count, m_base->shCtx.forbiddenTaiRps); + }); + + serviceAreaList = std::make_unique>(0, std::nullopt); + + taiList = std::make_unique>(0, std::nullopt); + + equivalentPlmnList = std::make_unique>(16, 0, std::nullopt); + + forbiddenPlmnList = std::make_unique>(16, 0, std::nullopt); + + defConfiguredNssai = std::make_unique>(0, std::nullopt); + + configuredNssai = std::make_unique>(0, std::nullopt); + + allowedNssai = std::make_unique>(0, std::nullopt); + + rejectedNssaiInPlmn = std::make_unique>(0, std::nullopt); + + rejectedNssaiInTa = std::make_unique>(0, std::nullopt); + + networkFullName = std::make_unique>>(0, std::nullopt); + + networkShortName = std::make_unique>>(0, std::nullopt); + + localTimeZone = std::make_unique>>(0, std::nullopt); + + universalTimeAndLocalTimeZone = + std::make_unique>>(0, std::nullopt); + + networkDaylightSavingTime = + std::make_unique>>(0, std::nullopt); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + + defConfiguredNssai->set(m_base->config->defaultConfiguredNssai); + configuredNssai->set(m_base->config->configuredNssai); +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/storage.hpp b/src/ue/nas/storage.hpp new file mode 100644 index 000000000..d8f0a31c1 --- /dev/null +++ b/src/ue/nas/storage.hpp @@ -0,0 +1,53 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include +#include +#include + +namespace nr::ue +{ + +class MmStorage +{ + private: + TaskBase *m_base; + + public: + std::unique_ptr> uState; + + std::unique_ptr> storedSuci; + std::unique_ptr> storedGuti; + + std::unique_ptr> equivalentPlmnList; + std::unique_ptr> forbiddenPlmnList; + + std::unique_ptr> taiList; + std::unique_ptr> lastVisitedRegisteredTai; + std::unique_ptr> forbiddenTaiListRoaming; + std::unique_ptr> forbiddenTaiListRps; + + std::unique_ptr> serviceAreaList; + + std::unique_ptr> defConfiguredNssai; + std::unique_ptr> configuredNssai; + std::unique_ptr> allowedNssai; + std::unique_ptr> rejectedNssaiInPlmn; + std::unique_ptr> rejectedNssaiInTa; + + std::unique_ptr>> networkFullName; + std::unique_ptr>> networkShortName; + std::unique_ptr>> localTimeZone; + std::unique_ptr>> universalTimeAndLocalTimeZone; + std::unique_ptr>> networkDaylightSavingTime; + + public: + explicit MmStorage(TaskBase *base); +}; + +} // namespace nr::ue diff --git a/src/ue/nas/task.cpp b/src/ue/nas/task.cpp index 302beb8a4..f1be3905c 100644 --- a/src/ue/nas/task.cpp +++ b/src/ue/nas/task.cpp @@ -28,7 +28,7 @@ NasTask::NasTask(TaskBase *base) : base{base}, timers{} void NasTask::onStart() { - usim->initialize(base->config->supi.has_value(), base->config->initials); + usim->initialize(base->config->supi.has_value()); sm->onStart(mm); mm->onStart(sm, usim); @@ -57,39 +57,35 @@ void NasTask::onLoop() switch (msg->msgType) { case NtsMessageType::UE_RRC_TO_NAS: { - mm->handleRrcEvent(*dynamic_cast(msg)); + mm->handleRrcEvent(*dynamic_cast(msg)); break; } case NtsMessageType::UE_NAS_TO_NAS: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeNasToNas::PERFORM_MM_CYCLE: { + case NmUeNasToNas::PERFORM_MM_CYCLE: { mm->handleNasEvent(*w); break; } - case NwUeNasToNas::NAS_TIMER_EXPIRE: { + case NmUeNasToNas::NAS_TIMER_EXPIRE: { if (w->timer->isMmTimer()) mm->handleNasEvent(*w); else sm->handleNasEvent(*w); break; } - case NwUeNasToNas::ESTABLISH_INITIAL_SESSIONS: { - sm->establishInitialSessions(); - break; - } default: break; } break; } case NtsMessageType::UE_APP_TO_NAS: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeAppToNas::UPLINK_STATUS_CHANGE: { - sm->handleUplinkStatusChange(w->psi, w->isPending); + case NmUeAppToNas::UPLINK_DATA_DELIVERY: { + sm->handleUplinkDataRequest(w->psi, std::move(w->data)); break; } default: @@ -97,8 +93,20 @@ void NasTask::onLoop() } break; } + case NtsMessageType::UE_RLS_TO_NAS: { + auto *w = dynamic_cast(msg); + switch (w->present) + { + case NmUeRlsToNas::DATA_PDU_DELIVERY: { + sm->handleDownlinkDataRequest(w->psi, std::move(w->pdu)); + break; + } + } + + break; + } case NtsMessageType::TIMER_EXPIRED: { - auto *w = dynamic_cast(msg); + auto *w = dynamic_cast(msg); int timerId = w->timerId; if (timerId == NTS_TIMER_ID_NAS_TIMER_CYCLE) { @@ -108,7 +116,7 @@ void NasTask::onLoop() if (timerId == NTS_TIMER_ID_MM_CYCLE) { setTimer(NTS_TIMER_ID_MM_CYCLE, NTS_TIMER_INTERVAL_MM_CYCLE); - mm->handleNasEvent(NwUeNasToNas{NwUeNasToNas::PERFORM_MM_CYCLE}); + mm->handleNasEvent(NmUeNasToNas{NmUeNasToNas::PERFORM_MM_CYCLE}); } break; } @@ -122,10 +130,10 @@ void NasTask::onLoop() void NasTask::performTick() { - auto sendExpireMsg = [this](nas::NasTimer *timer) { - auto *nw = new NwUeNasToNas(NwUeNasToNas::NAS_TIMER_EXPIRE); - nw->timer = timer; - push(nw); + auto sendExpireMsg = [this](UeTimer *timer) { + auto *m = new NmUeNasToNas(NmUeNasToNas::NAS_TIMER_EXPIRE); + m->timer = timer; + push(m); }; if (timers.t3346.performTick()) diff --git a/src/ue/nas/task.hpp b/src/ue/nas/task.hpp index 285a2ef29..99906b369 100644 --- a/src/ue/nas/task.hpp +++ b/src/ue/nas/task.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -27,7 +26,7 @@ class NasTask : public NtsTask TaskBase *base; std::unique_ptr logger; - UeTimers timers; + NasTimers timers; NasMm *mm; NasSm *sm; Usim *usim; diff --git a/src/ue/nas/usim/usim.cpp b/src/ue/nas/usim/usim.cpp index 26fb7af5e..1610c5fdb 100644 --- a/src/ue/nas/usim/usim.cpp +++ b/src/ue/nas/usim/usim.cpp @@ -11,15 +11,10 @@ namespace nr::ue { -void ue::Usim::initialize(bool hasSupi, const UeConfig::Initials &initials) +void ue::Usim::initialize(bool hasSupi) { m_isValid = hasSupi; - m_uState = E5UState::U1_UPDATED; - - m_defConfiguredNssai = initials.defaultConfiguredNssai; - m_configuredNssai = initials.configuredNssai; - m_sqnMng = std::make_unique(5ull, 1ull << 28ull); } diff --git a/src/ue/nas/usim/usim.hpp b/src/ue/nas/usim/usim.hpp index 9d1987e05..9b0822dec 100644 --- a/src/ue/nas/usim/usim.hpp +++ b/src/ue/nas/usim/usim.hpp @@ -27,25 +27,6 @@ class Usim bool m_isValid{}; public: - // State related - E5UState m_uState{}; - - // Identity related - nas::IE5gsMobileIdentity m_storedSuci{}; - nas::IE5gsMobileIdentity m_storedGuti{}; - - // Plmn related - std::optional m_servingCell{}; - std::optional m_currentPlmn{}; - std::optional m_currentTai{}; - std::optional m_lastVisitedRegisteredTai{}; - nas::IE5gsTrackingAreaIdentityList m_taiList{}; - nas::IE5gsTrackingAreaIdentityList m_forbiddenTaiListRoaming{}; // 5GS forbidden TAs for roaming - nas::IE5gsTrackingAreaIdentityList m_forbiddenTaiListRps{}; // 5GS forbidden TAs for regional provision of service - nas::IEPlmnList m_equivalentPlmnList{}; - nas::IEPlmnList m_forbiddenPlmnList{}; - nas::IEServiceAreaList m_serviceAreaList{}; - // Security related std::unique_ptr m_currentNsCtx{}; std::unique_ptr m_nonCurrentNsCtx{}; @@ -53,25 +34,12 @@ class Usim OctetString m_resStar{}; std::unique_ptr m_sqnMng{}; - // NSSAI related - NetworkSlice m_defConfiguredNssai{}; - NetworkSlice m_configuredNssai{}; - NetworkSlice m_allowedNssai{}; - NetworkSlice m_rejectedNssaiInPlmn{}; - NetworkSlice m_rejectedNssaiInTa{}; - - // NITZ related - std::optional m_networkFullName{}; - std::optional m_networkShortName{}; - std::optional m_localTimeZone{}; - std::optional m_universalTimeAndLocalTimeZone{}; - std::optional m_networkDaylightSavingTime{}; - // Others - bool m_isECallOnly{}; + bool m_isECallOnly = false; // todo: configurable + bool m_emgIndication = false; // todo: configurable public: - void initialize(bool hasSupi, const UeConfig::Initials &initials); + void initialize(bool hasSupi); bool isValid(); void invalidate(); diff --git a/src/ue/nts.hpp b/src/ue/nts.hpp index 9e676b4f6..5a9a24f6f 100644 --- a/src/ue/nts.hpp +++ b/src/ue/nts.hpp @@ -10,10 +10,12 @@ #include "types.hpp" #include "ue.hpp" + +#include + #include -#include +#include #include -#include #include #include #include @@ -21,7 +23,7 @@ namespace nr::ue { -struct NwAppToTun : NtsMessage +struct NmAppToTun : NtsMessage { enum PR { @@ -32,12 +34,12 @@ struct NwAppToTun : NtsMessage int psi{}; OctetString data{}; - explicit NwAppToTun(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_TUN), present(present) + explicit NmAppToTun(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_TUN), present(present) { } }; -struct NwUeTunToApp : NtsMessage +struct NmUeTunToApp : NtsMessage { enum PR { @@ -52,162 +54,173 @@ struct NwUeTunToApp : NtsMessage // TUN_ERROR std::string error{}; - explicit NwUeTunToApp(PR present) : NtsMessage(NtsMessageType::UE_TUN_TO_APP), present(present) + explicit NmUeTunToApp(PR present) : NtsMessage(NtsMessageType::UE_TUN_TO_APP), present(present) { } }; -struct NwUeRrcToNas : NtsMessage +struct NmUeRrcToNas : NtsMessage { enum PR { + NAS_NOTIFY, NAS_DELIVERY, - PLMN_SEARCH_RESPONSE, RRC_CONNECTION_SETUP, RRC_CONNECTION_RELEASE, + RRC_ESTABLISHMENT_FAILURE, RADIO_LINK_FAILURE, - SERVING_CELL_CHANGE, PAGING, + ACTIVE_CELL_CHANGED, + RRC_FALLBACK_INDICATION, } present; // NAS_DELIVERY - OctetString nasPdu{}; - - // PLMN_SEARCH_RESPONSE - std::vector measurements{}; - - // SERVING_CELL_CHANGE - UeCellInfo servingCell{}; + OctetString nasPdu; // PAGING - std::vector pagingTmsi{}; + std::vector pagingTmsi; - explicit NwUeRrcToNas(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_NAS), present(present) + // ACTIVE_CELL_CHANGED + Tai previousTai; + + explicit NmUeRrcToNas(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_NAS), present(present) { } }; -struct NwUeNasToRrc : NtsMessage +struct NmUeNasToRrc : NtsMessage { enum PR { - PLMN_SEARCH_REQUEST, LOCAL_RELEASE_CONNECTION, - INITIAL_NAS_DELIVERY, UPLINK_NAS_DELIVERY, - CELL_SELECTION_COMMAND, + RRC_NOTIFY, } present; - // INITIAL_NAS_DELIVERY // UPLINK_NAS_DELIVERY - OctetString nasPdu{}; - - // INITIAL_NAS_DELIVERY - long rrcEstablishmentCause{}; + uint32_t pduId{}; + OctetString nasPdu; - // CELL_SELECTION_COMMAND - GlobalNci cellId{}; - bool isSuitableCell{}; // otherwise 'acceptable' + // LOCAL_RELEASE_CONNECTION + bool treatBarred{}; - explicit NwUeNasToRrc(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_RRC), present(present) + explicit NmUeNasToRrc(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_RRC), present(present) { } }; -struct NwUeRrcToRls : NtsMessage +struct NmUeRrcToRls : NtsMessage { enum PR { - PLMN_SEARCH_REQUEST, - CELL_SELECTION_COMMAND, + ASSIGN_CURRENT_CELL, RRC_PDU_DELIVERY, RESET_STI, } present; - // CELL_SELECTION_COMMAND - GlobalNci cellId{}; - bool isSuitableCell{}; // otherwise 'acceptable' + // ASSIGN_CURRENT_CELL + int cellId{}; // RRC_PDU_DELIVERY rrc::RrcChannel channel{}; + uint32_t pduId{}; OctetString pdu{}; - explicit NwUeRrcToRls(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_RLS), present(present) + explicit NmUeRrcToRls(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_RLS), present(present) { } }; -struct NwUeRlsToRrc : NtsMessage +struct NmUeRrcToRrc : NtsMessage { enum PR { - PLMN_SEARCH_RESPONSE, - SERVING_CELL_CHANGE, - RRC_PDU_DELIVERY, - RADIO_LINK_FAILURE + TRIGGER_CYCLE, } present; - // PLMN_SEARCH_RESPONSE - std::vector measurements{}; + explicit NmUeRrcToRrc(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_RRC), present(present) + { + } +}; - // SERVING_CELL_CHANGE - UeCellInfo servingCell{}; +struct NmUeRlsToRrc : NtsMessage +{ + enum PR + { + DOWNLINK_RRC_DELIVERY, + SIGNAL_CHANGED, + RADIO_LINK_FAILURE + } present; - // RRC_PDU_DELIVERY + // DOWNLINK_RRC_DELIVERY + // SIGNAL_CHANGED + int cellId{}; + + // DOWNLINK_RRC_DELIVERY rrc::RrcChannel channel{}; - OctetString pdu{}; + OctetString pdu; + + // SIGNAL_CHANGED + int dbm{}; + + // RADIO_LINK_FAILURE + rls::ERlfCause rlfCause{}; - explicit NwUeRlsToRrc(PR present) : NtsMessage(NtsMessageType::UE_RLS_TO_RRC), present(present) + explicit NmUeRlsToRrc(PR present) : NtsMessage(NtsMessageType::UE_RLS_TO_RRC), present(present) { } }; -struct NwUeNasToNas : NtsMessage +struct NmUeNasToNas : NtsMessage { enum PR { PERFORM_MM_CYCLE, NAS_TIMER_EXPIRE, - ESTABLISH_INITIAL_SESSIONS } present; // NAS_TIMER_EXPIRE - nas::NasTimer *timer{}; + UeTimer *timer{}; - explicit NwUeNasToNas(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_NAS), present(present) + explicit NmUeNasToNas(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_NAS), present(present) { } }; -struct NwUeNasToApp : NtsMessage +struct NmUeNasToApp : NtsMessage { enum PR { PERFORM_SWITCH_OFF, + DOWNLINK_DATA_DELIVERY } present; - explicit NwUeNasToApp(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_APP), present(present) + // DOWNLINK_DATA_DELIVERY + int psi{}; + OctetString data; + + explicit NmUeNasToApp(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_APP), present(present) { } }; -struct NwUeAppToNas : NtsMessage +struct NmUeAppToNas : NtsMessage { enum PR { - UPLINK_STATUS_CHANGE, + UPLINK_DATA_DELIVERY, } present; - // UPLINK_STATUS_CHANGE + // UPLINK_DATA_DELIVERY int psi{}; - bool isPending{}; + OctetString data; - explicit NwUeAppToNas(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_NAS), present(present) + explicit NmUeAppToNas(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_NAS), present(present) { } }; -struct NwUeAppToRls : NtsMessage +struct NmUeNasToRls : NtsMessage { enum PR { @@ -216,14 +229,14 @@ struct NwUeAppToRls : NtsMessage // DATA_PDU_DELIVERY int psi{}; - OctetString pdu{}; + OctetString pdu; - explicit NwUeAppToRls(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_RLS), present(present) + explicit NmUeNasToRls(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_RLS), present(present) { } }; -struct NwUeRlsToApp : NtsMessage +struct NmUeRlsToNas : NtsMessage { enum PR { @@ -234,12 +247,68 @@ struct NwUeRlsToApp : NtsMessage int psi{}; OctetString pdu{}; - explicit NwUeRlsToApp(PR present) : NtsMessage(NtsMessageType::UE_RLS_TO_APP), present(present) + explicit NmUeRlsToNas(PR present) : NtsMessage(NtsMessageType::UE_RLS_TO_NAS), present(present) + { + } +}; + +struct NmUeRlsToRls : NtsMessage +{ + enum PR + { + RECEIVE_RLS_MESSAGE, + SIGNAL_CHANGED, + UPLINK_DATA, + UPLINK_RRC, + DOWNLINK_DATA, + DOWNLINK_RRC, + RADIO_LINK_FAILURE, + TRANSMISSION_FAILURE, + ASSIGN_CURRENT_CELL, + } present; + + // RECEIVE_RLS_MESSAGE + // UPLINK_RRC + // DOWNLINK_RRC + // SIGNAL_CHANGED + // ASSIGN_CURRENT_CELL + int cellId{}; + + // RECEIVE_RLS_MESSAGE + std::unique_ptr msg{}; + + // SIGNAL_CHANGED + int dbm{}; + + // UPLINK_DATA + // DOWNLINK_DATA + int psi{}; + + // UPLINK_DATA + // DOWNLINK_DATA + // UPLINK_RRC + // DOWNLINK_RRC + OctetString data; + + // UPLINK_RRC + // DOWNLINK_RRC + rrc::RrcChannel rrcChannel{}; + + // UPLINK_RRC + uint32_t pduId{}; + + // RADIO_LINK_FAILURE + rls::ERlfCause rlfCause{}; + + // TRANSMISSION_FAILURE + std::vector pduList; + + explicit NmUeRlsToRls(PR present) : NtsMessage(NtsMessageType::UE_RLS_TO_RLS), present(present) { } }; -struct NwUeStatusUpdate : NtsMessage +struct NmUeStatusUpdate : NtsMessage { static constexpr const int SESSION_ESTABLISHMENT = 1; static constexpr const int SESSION_RELEASE = 2; @@ -256,17 +325,17 @@ struct NwUeStatusUpdate : NtsMessage // CM_STATE ECmState cmState{}; - explicit NwUeStatusUpdate(const int what) : NtsMessage(NtsMessageType::UE_STATUS_UPDATE), what(what) + explicit NmUeStatusUpdate(const int what) : NtsMessage(NtsMessageType::UE_STATUS_UPDATE), what(what) { } }; -struct NwUeCliCommand : NtsMessage +struct NmUeCliCommand : NtsMessage { std::unique_ptr cmd; InetAddress address; - NwUeCliCommand(std::unique_ptr cmd, InetAddress address) + NmUeCliCommand(std::unique_ptr cmd, InetAddress address) : NtsMessage(NtsMessageType::UE_CLI_COMMAND), cmd(std::move(cmd)), address(address) { } diff --git a/src/ue/rls/ctl_task.cpp b/src/ue/rls/ctl_task.cpp new file mode 100644 index 000000000..81a5a63d9 --- /dev/null +++ b/src/ue/rls/ctl_task.cpp @@ -0,0 +1,253 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "ctl_task.hpp" + +#include + +static constexpr const size_t MAX_PDU_COUNT = 128; +static constexpr const int MAX_PDU_TTL = 3000; + +static constexpr const int TIMER_ID_ACK_CONTROL = 1; +static constexpr const int TIMER_ID_ACK_SEND = 2; + +static constexpr const int TIMER_PERIOD_ACK_CONTROL = 1500; +static constexpr const int TIMER_PERIOD_ACK_SEND = 2250; + +namespace nr::ue +{ + +RlsControlTask::RlsControlTask(TaskBase *base, RlsSharedContext *shCtx) + : m_shCtx{shCtx}, m_servingCell{}, m_mainTask{}, m_udpTask{}, m_pduMap{}, m_pendingAck{} +{ + m_logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "rls-ctl"); +} + +void RlsControlTask::initialize(NtsTask *mainTask, RlsUdpTask *udpTask) +{ + m_mainTask = mainTask; + m_udpTask = udpTask; +} + +void RlsControlTask::onStart() +{ + setTimer(TIMER_ID_ACK_CONTROL, TIMER_PERIOD_ACK_CONTROL); + setTimer(TIMER_ID_ACK_SEND, TIMER_PERIOD_ACK_SEND); +} + +void RlsControlTask::onLoop() +{ + NtsMessage *msg = take(); + if (!msg) + return; + + switch (msg->msgType) + { + case NtsMessageType::UE_RLS_TO_RLS: { + auto *w = dynamic_cast(msg); + switch (w->present) + { + case NmUeRlsToRls::SIGNAL_CHANGED: + handleSignalChange(w->cellId, w->dbm); + break; + case NmUeRlsToRls::RECEIVE_RLS_MESSAGE: + handleRlsMessage(w->cellId, *w->msg); + break; + case NmUeRlsToRls::UPLINK_DATA: + handleUplinkDataDelivery(w->psi, std::move(w->data)); + break; + case NmUeRlsToRls::UPLINK_RRC: + handleUplinkRrcDelivery(w->cellId, w->pduId, w->rrcChannel, std::move(w->data)); + break; + case NmUeRlsToRls::ASSIGN_CURRENT_CELL: + m_servingCell = w->cellId; + break; + default: + m_logger->unhandledNts(msg); + break; + } + break; + } + case NtsMessageType::TIMER_EXPIRED: { + auto *w = dynamic_cast(msg); + if (w->timerId == TIMER_ID_ACK_CONTROL) + { + setTimer(TIMER_ID_ACK_CONTROL, TIMER_PERIOD_ACK_CONTROL); + onAckControlTimerExpired(); + } + else if (w->timerId == TIMER_ID_ACK_SEND) + { + setTimer(TIMER_ID_ACK_SEND, TIMER_PERIOD_ACK_SEND); + onAckSendTimerExpired(); + } + break; + } + default: + m_logger->unhandledNts(msg); + break; + } + + delete msg; +} + +void RlsControlTask::onQuit() +{ +} + +void RlsControlTask::handleRlsMessage(int cellId, rls::RlsMessage &msg) +{ + if (msg.msgType == rls::EMessageType::PDU_TRANSMISSION_ACK) + { + auto &m = (rls::RlsPduTransmissionAck &)msg; + for (auto pduId : m.pduIds) + m_pduMap.erase(pduId); + } + else if (msg.msgType == rls::EMessageType::PDU_TRANSMISSION) + { + auto &m = (rls::RlsPduTransmission &)msg; + if (m.pduId != 0) + m_pendingAck[cellId].push_back(m.pduId); + + if (m.pduType == rls::EPduType::DATA) + { + if (cellId != m_servingCell) + { + // NOTE: Data packet may be received from a cell other than serving cell + // Ignore the packet if this is the case. Other cell can only send RRC, but not DATA + return; + } + + auto *w = new NmUeRlsToRls(NmUeRlsToRls::DOWNLINK_DATA); + w->psi = static_cast(m.payload); + w->data = std::move(m.pdu); + m_mainTask->push(w); + } + else if (m.pduType == rls::EPduType::RRC) + { + auto *w = new NmUeRlsToRls(NmUeRlsToRls::DOWNLINK_RRC); + w->cellId = cellId; + w->rrcChannel = static_cast(m.payload); + w->data = std::move(m.pdu); + m_mainTask->push(w); + } + else + { + m_logger->err("Unhandled RLS PDU type"); + } + } + else + { + m_logger->err("Unhandled RLS message type"); + } +} + +void RlsControlTask::handleSignalChange(int cellId, int dbm) +{ + auto *w = new NmUeRlsToRls(NmUeRlsToRls::SIGNAL_CHANGED); + w->cellId = cellId; + w->dbm = dbm; + m_mainTask->push(w); +} + +void RlsControlTask::handleUplinkRrcDelivery(int cellId, uint32_t pduId, rrc::RrcChannel channel, OctetString &&data) +{ + if (pduId != 0) + { + if (m_pduMap.count(pduId)) + { + m_pduMap.clear(); + + auto *w = new NmUeRlsToRls(NmUeRlsToRls::RADIO_LINK_FAILURE); + w->rlfCause = rls::ERlfCause::PDU_ID_EXISTS; + m_mainTask->push(w); + return; + } + + if (m_pduMap.size() > MAX_PDU_COUNT) + { + m_pduMap.clear(); + + auto *w = new NmUeRlsToRls(NmUeRlsToRls::RADIO_LINK_FAILURE); + w->rlfCause = rls::ERlfCause::PDU_ID_FULL; + m_mainTask->push(w); + return; + } + + m_pduMap[pduId].endPointId = cellId; + m_pduMap[pduId].id = pduId; + m_pduMap[pduId].pdu = data.copy(); + m_pduMap[pduId].rrcChannel = channel; + m_pduMap[pduId].sentTime = utils::CurrentTimeMillis(); + } + + rls::RlsPduTransmission msg{m_shCtx->sti}; + msg.pduType = rls::EPduType::RRC; + msg.pdu = std::move(data); + msg.payload = static_cast(channel); + msg.pduId = pduId; + + m_udpTask->send(cellId, msg); +} + +void RlsControlTask::handleUplinkDataDelivery(int psi, OctetString &&data) +{ + rls::RlsPduTransmission msg{m_shCtx->sti}; + msg.pduType = rls::EPduType::DATA; + msg.pdu = std::move(data); + msg.payload = static_cast(psi); + msg.pduId = 0; + + m_udpTask->send(m_servingCell, msg); +} + +void RlsControlTask::onAckControlTimerExpired() +{ + int64_t current = utils::CurrentTimeMillis(); + + std::vector transmissionFailureIds; + std::vector transmissionFailures; + + for (auto &pdu : m_pduMap) + { + auto delta = current - pdu.second.sentTime; + if (delta > MAX_PDU_TTL) + { + transmissionFailureIds.push_back(pdu.first); + transmissionFailures.push_back(std::move(pdu.second)); + } + } + + for (auto id : transmissionFailureIds) + m_pduMap.erase(id); + + if (!transmissionFailures.empty()) + { + auto *w = new NmUeRlsToRls(NmUeRlsToRls::TRANSMISSION_FAILURE); + w->pduList = std::move(transmissionFailures); + m_mainTask->push(w); + } +} + +void RlsControlTask::onAckSendTimerExpired() +{ + auto copy = m_pendingAck; + m_pendingAck.clear(); + + for (auto &item : copy) + { + if (!item.second.empty()) + continue; + + rls::RlsPduTransmissionAck msg{m_shCtx->sti}; + msg.pduIds = std::move(item.second); + + m_udpTask->send(item.first, msg); + } +} + +} // namespace nr::ue diff --git a/src/ue/rls/ctl_task.hpp b/src/ue/rls/ctl_task.hpp new file mode 100644 index 000000000..b1944c96b --- /dev/null +++ b/src/ue/rls/ctl_task.hpp @@ -0,0 +1,56 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include "udp_task.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace nr::ue +{ + +class RlsControlTask : public NtsTask +{ + private: + std::unique_ptr m_logger; + RlsSharedContext *m_shCtx; + int m_servingCell; + NtsTask *m_mainTask; + RlsUdpTask *m_udpTask; + std::unordered_map m_pduMap; + std::unordered_map> m_pendingAck; + + public: + explicit RlsControlTask(TaskBase *base, RlsSharedContext *shCtx); + ~RlsControlTask() override = default; + + protected: + void onStart() override; + void onLoop() override; + void onQuit() override; + + public: + void initialize(NtsTask *mainTask, RlsUdpTask *udpTask); + + private: + void handleRlsMessage(int cellId, rls::RlsMessage &msg); + void handleSignalChange(int cellId, int dbm); + void handleUplinkRrcDelivery(int cellId, uint32_t pduId, rrc::RrcChannel channel, OctetString &&data); + void handleUplinkDataDelivery(int psi, OctetString &&data); + void onAckControlTimerExpired(); + void onAckSendTimerExpired(); +}; + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rls/measurement.cpp b/src/ue/rls/measurement.cpp deleted file mode 100644 index 38a5e1f25..000000000 --- a/src/ue/rls/measurement.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// -// This file is a part of UERANSIM open source project. -// Copyright (c) 2021 ALİ GÜNGÖR. -// -// The software and all associated files are licensed under GPL-3.0 -// and subject to the terms and conditions defined in LICENSE file. -// - -#include "task.hpp" -#include -#include -#include - -namespace nr::ue -{ - -void UeRlsTask::onMeasurement() -{ - std::vector entered{}; - std::vector exited{}; - - // compare active and pending measurements - for (auto &m : m_activeMeasurements) - { - if (!m_pendingMeasurements.count(m.first)) - exited.push_back(m.first); - } - for (auto &m : m_pendingMeasurements) - { - if (!m_activeMeasurements.count(m.first)) - entered.push_back(m.first); - } - if (!entered.empty() || !exited.empty()) - onCoverageChange(entered, exited); - - // copy from pending to active measurements - m_activeMeasurements = m_pendingMeasurements; - // clear pending measurements - m_pendingMeasurements = {}; - - // Issue another cell info request for each address in the search space - for (auto &ip : m_cellSearchSpace) - { - rls::RlsCellInfoRequest req{m_sti}; - sendRlsMessage(ip, req); - } - - // Send PLMN search response to the RRC if it is requested - if (m_pendingPlmnResponse) - { - m_pendingPlmnResponse = false; - - std::vector measurements{}; - for (auto &m : m_activeMeasurements) - measurements.push_back(m.second); - - auto *w = new NwUeRlsToRrc(NwUeRlsToRrc::PLMN_SEARCH_RESPONSE); - w->measurements = std::move(measurements); - m_base->rrcTask->push(w); - } -} - -void UeRlsTask::receiveCellInfoResponse(const rls::RlsCellInfoResponse &msg) -{ - UeCellMeasurement meas{}; - meas.sti = msg.sti; - meas.cellId = msg.cellId; - meas.tac = msg.tac; - meas.dbm = msg.dbm; - meas.gnbName = msg.gnbName; - meas.linkIp = msg.linkIp; - - m_pendingMeasurements[meas.cellId] = meas; -} - -void UeRlsTask::onCoverageChange(const std::vector &entered, const std::vector &exited) -{ - m_logger->debug("Coverage change detected. [%d] cell entered, [%d] cell exited", static_cast(entered.size()), - static_cast(exited.size())); - - bool campedCellLost = m_servingCell.has_value() && std::any_of(exited.begin(), exited.end(), [this](auto &i) { - return i == m_servingCell->cellId; - }); - if (campedCellLost) - { - m_logger->warn("Signal lost from camped cell"); - m_servingCell = std::nullopt; - m_base->rrcTask->push(new NwUeRlsToRrc(NwUeRlsToRrc::RADIO_LINK_FAILURE)); - } -} - -void UeRlsTask::plmnSearchRequested() -{ - m_pendingPlmnResponse = true; -} - -void UeRlsTask::handleCellSelectionCommand(const GlobalNci &cellId, bool isSuitable) -{ - slowDownMeasurements(); - - if (!m_activeMeasurements.count(cellId)) - { - m_logger->err("Selected cell is no longer available for camping"); - return; - } - - auto &measurement = m_activeMeasurements[cellId]; - - m_servingCell = UeCellInfo{}; - m_servingCell->sti = measurement.sti; - m_servingCell->cellId = measurement.cellId; - m_servingCell->tac = measurement.tac; - m_servingCell->gnbName = measurement.gnbName; - m_servingCell->linkIp = measurement.linkIp; - m_servingCell->cellCategory = isSuitable ? ECellCategory::SUITABLE_CELL : ECellCategory::ACCEPTABLE_CELL; - - auto *w = new NwUeRlsToRrc(NwUeRlsToRrc::SERVING_CELL_CHANGE); - w->servingCell = *m_servingCell; - m_base->rrcTask->push(w); -} - -} // namespace nr::ue diff --git a/src/ue/rls/task.cpp b/src/ue/rls/task.cpp index a1de7d1ab..213b49523 100644 --- a/src/ue/rls/task.cpp +++ b/src/ue/rls/task.cpp @@ -7,45 +7,34 @@ // #include "task.hpp" -#include + +#include +#include +#include #include #include -static constexpr const int TIMER_ID_MEASUREMENT = 1; -static constexpr const int TIMER_PERIOD_MEASUREMENT_MIN = 1; -static constexpr const int TIMER_PERIOD_MEASUREMENT_MAX = 2000; - -static constexpr const int TIMER_ID_RAPID_LAUNCH = 2; -static constexpr const int TIMER_PERIOD_RAPID_LAUNCH = 750; - namespace nr::ue { -UeRlsTask::UeRlsTask(TaskBase *base) - : m_base{base}, m_udpTask{}, m_cellSearchSpace{}, m_pendingMeasurements{}, m_activeMeasurements{}, - m_pendingPlmnResponse{}, m_measurementPeriod{TIMER_PERIOD_MEASUREMENT_MIN}, m_servingCell{} +UeRlsTask::UeRlsTask(TaskBase *base) : m_base{base} { m_logger = m_base->logBase->makeUniqueLogger(m_base->config->getLoggerPrefix() + "rls"); - for (auto &addr : m_base->config->gnbSearchList) - m_cellSearchSpace.emplace_back(addr, cons::PortalPort); + m_shCtx = new RlsSharedContext(); + m_shCtx->sti = utils::Random64(); + + m_udpTask = new RlsUdpTask(base, m_shCtx, base->config->gnbSearchList); + m_ctlTask = new RlsControlTask(base, m_shCtx); - m_sti = utils::Random64(); + m_udpTask->initialize(m_ctlTask); + m_ctlTask->initialize(this, m_udpTask); } void UeRlsTask::onStart() { - m_udpTask = new udp::UdpServerTask(this); - - std::vector gnbSearchList{}; - for (auto &ip : m_base->config->gnbSearchList) - gnbSearchList.emplace_back(ip, cons::PortalPort); - m_udpTask->start(); - - setTimer(TIMER_ID_MEASUREMENT, m_measurementPeriod); - setTimer(TIMER_ID_RAPID_LAUNCH, TIMER_PERIOD_RAPID_LAUNCH); - onMeasurement(); + m_ctlTask->start(); } void UeRlsTask::onLoop() @@ -56,59 +45,87 @@ void UeRlsTask::onLoop() switch (msg->msgType) { - case NtsMessageType::UE_RRC_TO_RLS: { - auto *w = dynamic_cast(msg); + case NtsMessageType::UE_RLS_TO_RLS: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeRrcToRls::PLMN_SEARCH_REQUEST: - plmnSearchRequested(); + case NmUeRlsToRls::SIGNAL_CHANGED: { + auto *m = new NmUeRlsToRrc(NmUeRlsToRrc::SIGNAL_CHANGED); + m->cellId = w->cellId; + m->dbm = w->dbm; + m_base->rrcTask->push(m); + break; + } + case NmUeRlsToRls::DOWNLINK_DATA: { + auto *m = new NmUeRlsToNas(NmUeRlsToNas::DATA_PDU_DELIVERY); + m->psi = w->psi; + m->pdu = std::move(w->data); + m_base->nasTask->push(m); break; - case NwUeRrcToRls::CELL_SELECTION_COMMAND: - handleCellSelectionCommand(w->cellId, w->isSuitableCell); + } + case NmUeRlsToRls::DOWNLINK_RRC: { + auto *m = new NmUeRlsToRrc(NmUeRlsToRrc::DOWNLINK_RRC_DELIVERY); + m->cellId = w->cellId; + m->channel = w->rrcChannel; + m->pdu = std::move(w->data); + m_base->rrcTask->push(m); break; - case NwUeRrcToRls::RRC_PDU_DELIVERY: - deliverUplinkPdu(rls::EPduType::RRC, std::move(w->pdu), - OctetString::FromOctet4(static_cast(w->channel))); + } + case NmUeRlsToRls::RADIO_LINK_FAILURE: { + auto *m = new NmUeRlsToRrc(NmUeRlsToRrc::RADIO_LINK_FAILURE); + m->rlfCause = w->rlfCause; + m_base->rrcTask->push(m); + break; + } + case NmUeRlsToRls::TRANSMISSION_FAILURE: { + m_logger->debug("transmission failure [%d]", w->pduList.size()); break; - case NwUeRrcToRls::RESET_STI: - m_sti = utils::Random64(); + } + default: { + m_logger->unhandledNts(msg); break; } + } break; } - case NtsMessageType::UE_APP_TO_RLS: { - auto *w = dynamic_cast(msg); + case NtsMessageType::UE_RRC_TO_RLS: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeAppToRls::DATA_PDU_DELIVERY: { - deliverUplinkPdu(rls::EPduType::DATA, std::move(w->pdu), OctetString::FromOctet4(static_cast(w->psi))); + case NmUeRrcToRls::ASSIGN_CURRENT_CELL: { + auto *m = new NmUeRlsToRls(NmUeRlsToRls::ASSIGN_CURRENT_CELL); + m->cellId = w->cellId; + m_ctlTask->push(m); break; } + case NmUeRrcToRls::RRC_PDU_DELIVERY: { + auto *m = new NmUeRlsToRls(NmUeRlsToRls::UPLINK_RRC); + m->cellId = w->cellId; + m->rrcChannel = w->channel; + m->pduId = w->pduId; + m->data = std::move(w->pdu); + m_ctlTask->push(m); + break; } - break; - } - case NtsMessageType::TIMER_EXPIRED: { - auto *w = dynamic_cast(msg); - if (w->timerId == TIMER_ID_MEASUREMENT) - { - setTimer(TIMER_ID_MEASUREMENT, m_measurementPeriod); - onMeasurement(); + case NmUeRrcToRls::RESET_STI: { + m_shCtx->sti = utils::Random64(); + break; } - else if (w->timerId == TIMER_ID_RAPID_LAUNCH) - { - slowDownMeasurements(); } break; } - case NtsMessageType::UDP_SERVER_RECEIVE: { - auto *w = dynamic_cast(msg); - auto rlsMsg = rls::DecodeRlsMessage(OctetView{w->packet}); - if (rlsMsg == nullptr) + case NtsMessageType::UE_NAS_TO_RLS: { + auto *w = dynamic_cast(msg); + switch (w->present) { - m_logger->err("Unable to decode RLS message"); + case NmUeNasToRls::DATA_PDU_DELIVERY: { + auto *m = new NmUeRlsToRls(NmUeRlsToRls::UPLINK_DATA); + m->psi = w->psi; + m->data = std::move(w->pdu); + m_ctlTask->push(m); break; } - receiveRlsMessage(w->fromAddress, *rlsMsg); + } break; } default: @@ -122,12 +139,12 @@ void UeRlsTask::onLoop() void UeRlsTask::onQuit() { m_udpTask->quit(); + m_ctlTask->quit(); + delete m_udpTask; -} + delete m_ctlTask; -void UeRlsTask::slowDownMeasurements() -{ - m_measurementPeriod = TIMER_PERIOD_MEASUREMENT_MAX; + delete m_shCtx; } } // namespace nr::ue diff --git a/src/ue/rls/task.hpp b/src/ue/rls/task.hpp index 108d32410..706cc8043 100644 --- a/src/ue/rls/task.hpp +++ b/src/ue/rls/task.hpp @@ -8,18 +8,22 @@ #pragma once -#include -#include -#include +#include "ctl_task.hpp" +#include "udp_task.hpp" + #include #include #include -#include #include +#include + +#include +#include +#include +#include #include #include #include -#include namespace nr::ue { @@ -29,16 +33,10 @@ class UeRlsTask : public NtsTask private: TaskBase *m_base; std::unique_ptr m_logger; - udp::UdpServerTask *m_udpTask; - std::vector m_cellSearchSpace; - std::unordered_map m_pendingMeasurements; - std::unordered_map m_activeMeasurements; - bool m_pendingPlmnResponse; - int64_t m_measurementPeriod; - - uint64_t m_sti; - std::optional m_servingCell; + RlsSharedContext* m_shCtx; + RlsUdpTask *m_udpTask; + RlsControlTask *m_ctlTask; friend class UeCmdHandler; @@ -50,22 +48,6 @@ class UeRlsTask : public NtsTask void onStart() override; void onLoop() override; void onQuit() override; - - private: /* Base */ - void slowDownMeasurements(); - - private: /* Transport */ - void receiveRlsMessage(const InetAddress &address, rls::RlsMessage &msg); - void sendRlsMessage(const InetAddress &address, const rls::RlsMessage &msg); - void deliverUplinkPdu(rls::EPduType pduType, OctetString &&pdu, OctetString &&payload); - void deliverDownlinkPdu(rls::RlsPduDelivery &msg); - - private: /* Measurement */ - void onMeasurement(); - void receiveCellInfoResponse(const rls::RlsCellInfoResponse &msg); - void onCoverageChange(const std::vector &entered, const std::vector &exited); - void plmnSearchRequested(); - void handleCellSelectionCommand(const GlobalNci &cellId, bool isSuitable); }; } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rls/transport.cpp b/src/ue/rls/transport.cpp deleted file mode 100644 index 4fafa335b..000000000 --- a/src/ue/rls/transport.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// This file is a part of UERANSIM open source project. -// Copyright (c) 2021 ALİ GÜNGÖR. -// -// The software and all associated files are licensed under GPL-3.0 -// and subject to the terms and conditions defined in LICENSE file. -// - -#include "task.hpp" -#include -#include -#include -#include - -namespace nr::ue -{ - -void UeRlsTask::receiveRlsMessage(const InetAddress &address, rls::RlsMessage &msg) -{ - switch (msg.msgType) - { - case rls::EMessageType::CELL_INFO_RESPONSE: { - receiveCellInfoResponse((const rls::RlsCellInfoResponse &)msg); - break; - case rls::EMessageType::PDU_DELIVERY: { - deliverDownlinkPdu((rls::RlsPduDelivery &)msg); - break; - } - default: - m_logger->err("Unhandled RLS message type[%d]", static_cast(msg.msgType)); - break; - } - } -} - -void UeRlsTask::sendRlsMessage(const InetAddress &address, const rls::RlsMessage &msg) -{ - OctetString stream{}; - rls::EncodeRlsMessage(msg, stream); - m_udpTask->send(address, stream); -} - -void UeRlsTask::deliverUplinkPdu(rls::EPduType pduType, OctetString &&pdu, OctetString &&payload) -{ - if (!m_servingCell.has_value()) - { - m_logger->warn("RLS uplink delivery requested without a serving cell"); - return; - } - - rls::RlsPduDelivery msg{m_sti}; - msg.pduType = pduType; - msg.pdu = std::move(pdu); - msg.payload = std::move(payload); - sendRlsMessage(InetAddress{m_servingCell->linkIp, cons::PortalPort}, msg); -} - -void UeRlsTask::deliverDownlinkPdu(rls::RlsPduDelivery &msg) -{ - if (msg.pduType == rls::EPduType::RRC) - { - auto *nw = new NwUeRlsToRrc(NwUeRlsToRrc::RRC_PDU_DELIVERY); - nw->channel = static_cast(msg.payload.get4I(0)); - nw->pdu = std::move(msg.pdu); - m_base->rrcTask->push(nw); - } - else if (msg.pduType == rls::EPduType::DATA) - { - auto *nw = new NwUeRlsToApp(NwUeRlsToApp::DATA_PDU_DELIVERY); - nw->psi = msg.payload.get4I(0); - nw->pdu = std::move(msg.pdu); - m_base->appTask->push(nw); - } -} - -} // namespace nr::ue diff --git a/src/ue/rls/udp_task.cpp b/src/ue/rls/udp_task.cpp new file mode 100644 index 000000000..54a2dc0e9 --- /dev/null +++ b/src/ue/rls/udp_task.cpp @@ -0,0 +1,175 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "udp_task.hpp" + +#include +#include +#include + +#include +#include +#include + +static constexpr const int BUFFER_SIZE = 16384; +static constexpr const int LOOP_PERIOD = 1000; +static constexpr const int RECEIVE_TIMEOUT = 200; +static constexpr const int HEARTBEAT_THRESHOLD = 2000; // (LOOP_PERIOD + RECEIVE_TIMEOUT)'dan büyük olmalı + +namespace nr::ue +{ + +RlsUdpTask::RlsUdpTask(TaskBase *base, RlsSharedContext *shCtx, const std::vector &searchSpace) + : m_server{}, m_ctlTask{}, m_shCtx{shCtx}, m_searchSpace{}, m_cells{}, m_cellIdToSti{}, m_lastLoop{}, + m_cellIdCounter{} +{ + m_logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "rls-udp"); + + m_server = new udp::UdpServer(); + + for (auto &ip : searchSpace) + m_searchSpace.emplace_back(ip, cons::PortalPort); + + m_simPos = Vector3{}; +} + +void RlsUdpTask::onStart() +{ +} + +void RlsUdpTask::onLoop() +{ + auto current = utils::CurrentTimeMillis(); + if (current - m_lastLoop > LOOP_PERIOD) + { + m_lastLoop = current; + heartbeatCycle(current, m_simPos); + } + + uint8_t buffer[BUFFER_SIZE]; + InetAddress peerAddress; + + int size = m_server->Receive(buffer, BUFFER_SIZE, RECEIVE_TIMEOUT, peerAddress); + if (size > 0) + { + auto rlsMsg = rls::DecodeRlsMessage(OctetView{buffer, static_cast(size)}); + if (rlsMsg == nullptr) + m_logger->err("Unable to decode RLS message"); + else + receiveRlsPdu(peerAddress, std::move(rlsMsg)); + } +} + +void RlsUdpTask::onQuit() +{ + delete m_server; +} + +void RlsUdpTask::sendRlsPdu(const InetAddress &addr, const rls::RlsMessage &msg) +{ + OctetString stream; + rls::EncodeRlsMessage(msg, stream); + + m_server->Send(addr, stream.data(), static_cast(stream.length())); +} + +void RlsUdpTask::send(int cellId, const rls::RlsMessage &msg) +{ + if (m_cellIdToSti.count(cellId)) + { + auto sti = m_cellIdToSti[cellId]; + sendRlsPdu(m_cells[sti].address, msg); + } +} + +void RlsUdpTask::receiveRlsPdu(const InetAddress &addr, std::unique_ptr &&msg) +{ + if (msg->msgType == rls::EMessageType::HEARTBEAT_ACK) + { + if (!m_cells.count(msg->sti)) + { + m_cells[msg->sti].cellId = ++m_cellIdCounter; + m_cellIdToSti[m_cells[msg->sti].cellId] = msg->sti; + } + + int oldDbm = INT32_MIN; + if (m_cells.count(msg->sti)) + oldDbm = m_cells[msg->sti].dbm; + + m_cells[msg->sti].address = addr; + m_cells[msg->sti].lastSeen = utils::CurrentTimeMillis(); + + int newDbm = ((const rls::RlsHeartBeatAck &)*msg).dbm; + m_cells[msg->sti].dbm = newDbm; + + if (oldDbm != newDbm) + onSignalChangeOrLost(m_cells[msg->sti].cellId); + return; + } + + if (!m_cells.count(msg->sti)) + { + // if no HB-ACK received yet, and the message is not HB-ACK, then ignore the message + return; + } + + auto *w = new NmUeRlsToRls(NmUeRlsToRls::RECEIVE_RLS_MESSAGE); + w->cellId = m_cells[msg->sti].cellId; + w->msg = std::move(msg); + m_ctlTask->push(w); +} + +void RlsUdpTask::onSignalChangeOrLost(int cellId) +{ + int dbm = INT32_MIN; + if (m_cellIdToSti.count(cellId)) + { + auto sti = m_cellIdToSti[cellId]; + dbm = m_cells[sti].dbm; + } + + auto *w = new NmUeRlsToRls(NmUeRlsToRls::SIGNAL_CHANGED); + w->cellId = cellId; + w->dbm = dbm; + m_ctlTask->push(w); +} + +void RlsUdpTask::heartbeatCycle(uint64_t time, const Vector3 &simPos) +{ + std::set> toRemove; + + for (auto &cell : m_cells) + { + auto delta = time - cell.second.lastSeen; + if (delta > HEARTBEAT_THRESHOLD) + toRemove.insert({cell.first, cell.second.cellId}); + } + + for (auto cell : toRemove) + { + m_cells.erase(cell.first); + m_cellIdToSti.erase(cell.second); + } + + for (auto cell : toRemove) + onSignalChangeOrLost(cell.second); + + for (auto &addr : m_searchSpace) + { + rls::RlsHeartBeat msg{m_shCtx->sti}; + msg.simPos = simPos; + sendRlsPdu(addr, msg); + } +} + +void RlsUdpTask::initialize(NtsTask *ctlTask) +{ + m_ctlTask = ctlTask; +} + +} // namespace nr::ue diff --git a/src/ue/rls/udp_task.hpp b/src/ue/rls/udp_task.hpp new file mode 100644 index 000000000..ef093b5d4 --- /dev/null +++ b/src/ue/rls/udp_task.hpp @@ -0,0 +1,68 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace nr::ue +{ + +class RlsUdpTask : public NtsTask +{ + private: + struct CellInfo + { + InetAddress address; + int64_t lastSeen{}; + int dbm{}; + int cellId{}; + }; + + private: + std::unique_ptr m_logger; + udp::UdpServer *m_server; + NtsTask *m_ctlTask; + RlsSharedContext* m_shCtx; + std::vector m_searchSpace; + std::unordered_map m_cells; + std::unordered_map m_cellIdToSti; + int64_t m_lastLoop; + Vector3 m_simPos; + int m_cellIdCounter; + + friend class UeCmdHandler; + + public: + explicit RlsUdpTask(TaskBase *base, RlsSharedContext* shCtx, const std::vector &searchSpace); + ~RlsUdpTask() override = default; + + protected: + void onStart() override; + void onLoop() override; + void onQuit() override; + + private: + void sendRlsPdu(const InetAddress &addr, const rls::RlsMessage &msg); + void receiveRlsPdu(const InetAddress &addr, std::unique_ptr &&msg); + void onSignalChangeOrLost(int cellId); + void heartbeatCycle(uint64_t time, const Vector3 &simPos); + + public: + void initialize(NtsTask *ctlTask); + void send(int cellId, const rls::RlsMessage &msg); +}; + +} // namespace nr::ue diff --git a/src/ue/rrc/cells.cpp b/src/ue/rrc/cells.cpp new file mode 100644 index 000000000..46bb0c32a --- /dev/null +++ b/src/ue/rrc/cells.cpp @@ -0,0 +1,104 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include + +namespace nr::ue +{ + +void UeRrcTask::handleCellSignalChange(int cellId, int dbm) +{ + bool considerLost = dbm < -120; + + if (!m_cellDesc.count(cellId)) + { + if (!considerLost) + notifyCellDetected(cellId, dbm); + } + else + { + if (considerLost) + notifyCellLost(cellId); + else + m_cellDesc[cellId].dbm = dbm; + } +} + +void UeRrcTask::notifyCellDetected(int cellId, int dbm) +{ + m_cellDesc[cellId] = {}; + m_cellDesc[cellId].dbm = dbm; + + m_logger->debug("New signal detected for cell[%d], total [%d] cells in coverage", cellId, + static_cast(m_cellDesc.size())); + + updateAvailablePlmns(); +} + +void UeRrcTask::notifyCellLost(int cellId) +{ + if (!m_cellDesc.count(cellId)) + return; + + bool isActiveCell = false; + ActiveCellInfo lastActiveCell; + m_base->shCtx.currentCell.mutate([&isActiveCell, &lastActiveCell, cellId](auto &value) { + if (value.cellId == cellId) + { + lastActiveCell = value; + value = {}; + isActiveCell = true; + } + }); + + m_cellDesc.erase(cellId); + + m_logger->debug("Signal lost for cell[%d], total [%d] cells in coverage", cellId, + static_cast(m_cellDesc.size())); + + if (isActiveCell) + { + if (m_state != ERrcState::RRC_IDLE) + declareRadioLinkFailure(rls::ERlfCause::SIGNAL_LOST_TO_CONNECTED_CELL); + else + { + auto w2 = new NmUeRrcToNas(NmUeRrcToNas::ACTIVE_CELL_CHANGED); + w2->previousTai = Tai{lastActiveCell.plmn, lastActiveCell.tac}; + m_base->nasTask->push(w2); + } + } + + updateAvailablePlmns(); +} + +bool UeRrcTask::hasSignalToCell(int cellId) +{ + return m_cellDesc.count(cellId); +} + +bool UeRrcTask::isActiveCell(int cellId) +{ + return m_base->shCtx.currentCell.get([](auto &value) { return value.cellId; }) == cellId; +} + +void UeRrcTask::updateAvailablePlmns() +{ + m_base->shCtx.availablePlmns.mutate([this](std::unordered_set &value) { + value.clear(); + for (auto &cellDesc : m_cellDesc) + if (cellDesc.second.sib1.hasSib1) + value.insert(cellDesc.second.sib1.plmn); + }); + + m_base->nasTask->push(new NmUeRrcToNas(NmUeRrcToNas::NAS_NOTIFY)); +} + +} // namespace nr::ue diff --git a/src/ue/rrc/channel.cpp b/src/ue/rrc/channel.cpp index 251f35786..5bd140a83 100644 --- a/src/ue/rrc/channel.cpp +++ b/src/ue/rrc/channel.cpp @@ -19,8 +19,11 @@ namespace nr::ue { -void UeRrcTask::handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &rrcPdu) +void UeRrcTask::handleDownlinkRrc(int cellId, rrc::RrcChannel channel, const OctetString &rrcPdu) { + if (!hasSignalToCell(cellId)) + return; + switch (channel) { case rrc::RrcChannel::BCCH_BCH: { @@ -28,7 +31,7 @@ void UeRrcTask::handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &rr if (pdu == nullptr) m_logger->err("RRC BCCH-BCH PDU decoding failed."); else - receiveRrcMessage(pdu); + receiveRrcMessage(cellId, pdu); asn::Free(asn_DEF_ASN_RRC_BCCH_BCH_Message, pdu); break; } @@ -37,7 +40,7 @@ void UeRrcTask::handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &rr if (pdu == nullptr) m_logger->err("RRC BCCH-DL-SCH PDU decoding failed."); else - receiveRrcMessage(pdu); + receiveRrcMessage(cellId, pdu); asn::Free(asn_DEF_ASN_RRC_BCCH_DL_SCH_Message, pdu); break; } @@ -46,26 +49,32 @@ void UeRrcTask::handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &rr if (pdu == nullptr) m_logger->err("RRC DL-CCCH PDU decoding failed."); else - receiveRrcMessage(pdu); + receiveRrcMessage(cellId, pdu); asn::Free(asn_DEF_ASN_RRC_DL_CCCH_Message, pdu); break; } case rrc::RrcChannel::DL_DCCH: { - auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_DL_DCCH_Message, rrcPdu); - if (pdu == nullptr) - m_logger->err("RRC DL-DCCH PDU decoding failed."); - else - receiveRrcMessage(pdu); - asn::Free(asn_DEF_ASN_RRC_DL_DCCH_Message, pdu); + if (isActiveCell(cellId)) + { + auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_DL_DCCH_Message, rrcPdu); + if (pdu == nullptr) + m_logger->err("RRC DL-DCCH PDU decoding failed."); + else + receiveRrcMessage(pdu); + asn::Free(asn_DEF_ASN_RRC_DL_DCCH_Message, pdu); + } break; }; case rrc::RrcChannel::PCCH: { - auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_PCCH_Message, rrcPdu); - if (pdu == nullptr) - m_logger->err("RRC PCCH PDU decoding failed."); - else - receiveRrcMessage(pdu); - asn::Free(asn_DEF_ASN_RRC_PCCH_Message, pdu); + if (isActiveCell(cellId)) + { + auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_PCCH_Message, rrcPdu); + if (pdu == nullptr) + m_logger->err("RRC PCCH PDU decoding failed."); + else + receiveRrcMessage(pdu); + asn::Free(asn_DEF_ASN_RRC_PCCH_Message, pdu); + } break; } case rrc::RrcChannel::UL_CCCH: @@ -75,22 +84,7 @@ void UeRrcTask::handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &rr } } -void UeRrcTask::sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg) -{ - OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_BCCH_BCH_Message, msg); - if (pdu.length() == 0) - { - m_logger->err("RRC BCCH-BCH encoding failed."); - return; - } - - auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::BCCH_BCH; - nw->pdu = std::move(pdu); - m_base->rlsTask->push(nw); -} - -void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH_Message *msg) +void UeRrcTask::sendRrcMessage(int cellId, ASN_RRC_UL_CCCH_Message *msg) { OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_UL_CCCH_Message, msg); if (pdu.length() == 0) @@ -99,13 +93,14 @@ void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH_Message *msg) return; } - auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::UL_CCCH; - nw->pdu = std::move(pdu); - m_base->rlsTask->push(nw); + auto *m = new NmUeRrcToRls(NmUeRrcToRls::RRC_PDU_DELIVERY); + m->cellId = cellId; + m->channel = rrc::RrcChannel::UL_CCCH; + m->pdu = std::move(pdu); + m_base->rlsTask->push(m); } -void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH1_Message *msg) +void UeRrcTask::sendRrcMessage(int cellId, ASN_RRC_UL_CCCH1_Message *msg) { OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_UL_CCCH1_Message, msg); if (pdu.length() == 0) @@ -114,10 +109,11 @@ void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH1_Message *msg) return; } - auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::UL_CCCH1; - nw->pdu = std::move(pdu); - m_base->rlsTask->push(nw); + auto *m = new NmUeRrcToRls(NmUeRrcToRls::RRC_PDU_DELIVERY); + m->cellId = cellId; + m->channel = rrc::RrcChannel::UL_CCCH1; + m->pdu = std::move(pdu); + m_base->rlsTask->push(m); } void UeRrcTask::sendRrcMessage(ASN_RRC_UL_DCCH_Message *msg) @@ -129,23 +125,36 @@ void UeRrcTask::sendRrcMessage(ASN_RRC_UL_DCCH_Message *msg) return; } - auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::UL_DCCH; - nw->pdu = std::move(pdu); - m_base->rlsTask->push(nw); + auto *m = new NmUeRrcToRls(NmUeRrcToRls::RRC_PDU_DELIVERY); + m->cellId = m_base->shCtx.currentCell.get([](auto &value) { return value.cellId; }); + m->channel = rrc::RrcChannel::UL_DCCH; + m->pdu = std::move(pdu); + m_base->rlsTask->push(m); } -void UeRrcTask::receiveRrcMessage(ASN_RRC_BCCH_BCH_Message *msg) +void UeRrcTask::receiveRrcMessage(int cellId, ASN_RRC_BCCH_BCH_Message *msg) { - // TODO + if (msg->message.present == ASN_RRC_BCCH_BCH_MessageType_PR_mib) + receiveMib(cellId, *msg->message.choice.mib); } -void UeRrcTask::receiveRrcMessage(ASN_RRC_BCCH_DL_SCH_Message *msg) +void UeRrcTask::receiveRrcMessage(int cellId, ASN_RRC_BCCH_DL_SCH_Message *msg) { - // TODO + if (msg->message.present != ASN_RRC_BCCH_DL_SCH_MessageType_PR_c1) + return; + + auto &c1 = msg->message.choice.c1; + switch (c1->present) + { + case ASN_RRC_BCCH_DL_SCH_MessageType__c1_PR_systemInformationBlockType1: + receiveSib1(cellId, *c1->choice.systemInformationBlockType1); + break; + default: + break; + } } -void UeRrcTask::receiveRrcMessage(ASN_RRC_DL_CCCH_Message *msg) +void UeRrcTask::receiveRrcMessage(int cellId, ASN_RRC_DL_CCCH_Message *msg) { if (msg->message.present != ASN_RRC_DL_CCCH_MessageType_PR_c1) return; @@ -154,10 +163,10 @@ void UeRrcTask::receiveRrcMessage(ASN_RRC_DL_CCCH_Message *msg) switch (c1->present) { case ASN_RRC_DL_CCCH_MessageType__c1_PR_rrcReject: - receiveRrcReject(*c1->choice.rrcReject); + receiveRrcReject(cellId, *c1->choice.rrcReject); break; case ASN_RRC_DL_CCCH_MessageType__c1_PR_rrcSetup: - receiveRrcSetup(*c1->choice.rrcSetup); + receiveRrcSetup(cellId, *c1->choice.rrcSetup); break; default: break; diff --git a/src/ue/rrc/connection.cpp b/src/ue/rrc/connection.cpp new file mode 100644 index 000000000..9bc28cc4a --- /dev/null +++ b/src/ue/rrc/connection.cpp @@ -0,0 +1,127 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace nr::ue +{ + +static ASN_RRC_UL_CCCH_Message *ConstructSetupRequest(ASN_RRC_InitialUE_Identity_t initialUeId, + ASN_RRC_EstablishmentCause_t establishmentCause) +{ + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_UL_CCCH_MessageType_PR_c1; + pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); + pdu->message.choice.c1->present = ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcSetupRequest; + + auto &r = pdu->message.choice.c1->choice.rrcSetupRequest = asn::New(); + asn::DeepCopy(asn_DEF_ASN_RRC_InitialUE_Identity, initialUeId, &r->rrcSetupRequest.ue_Identity); + r->rrcSetupRequest.establishmentCause = establishmentCause; + asn::SetSpareBits<1>(r->rrcSetupRequest.spare); + + return pdu; +} + +void UeRrcTask::startConnectionEstablishment(OctetString &&nasPdu) +{ + if (m_state != ERrcState::RRC_IDLE) + { + m_logger->err("RRC establishment could not start, UE not in RRC-IDLE state"); + handleEstablishmentFailure(); + return; + } + + int activeCell = m_base->shCtx.currentCell.get([](auto &item) { return item.cellId; }); + if (activeCell == 0) + { + m_logger->err("RRC establishment could not start, no active cell"); + handleEstablishmentFailure(); + return; + } + + if (m_initialId.present == ASN_RRC_InitialUE_Identity_PR_NOTHING) + { + m_initialId.present = ASN_RRC_InitialUE_Identity_PR_randomValue; + asn::SetBitStringLong<39>(static_cast(utils::Random64()), m_initialId.choice.randomValue); + } + + m_initialNasPdu = std::move(nasPdu); + + m_logger->debug("Sending RRC Setup Request"); + + auto *rrcSetupRequest = ConstructSetupRequest(m_initialId, ASN_RRC_EstablishmentCause_mo_Data); + sendRrcMessage(activeCell, rrcSetupRequest); + asn::Free(asn_DEF_ASN_RRC_UL_CCCH_Message, rrcSetupRequest); +} + +void UeRrcTask::receiveRrcSetup(int cellId, const ASN_RRC_RRCSetup &msg) +{ + if (!isActiveCell(cellId)) + return; + + if (m_lastSetupReq != ERrcLastSetupRequest::SETUP_REQUEST) + { + // TODO + return; + } + + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_UL_DCCH_MessageType_PR_c1; + pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); + pdu->message.choice.c1->present = ASN_RRC_UL_DCCH_MessageType__c1_PR_rrcSetupComplete; + + auto &setupComplete = pdu->message.choice.c1->choice.rrcSetupComplete = asn::New(); + setupComplete->rrc_TransactionIdentifier = msg.rrc_TransactionIdentifier; + setupComplete->criticalExtensions.present = ASN_RRC_RRCSetupComplete__criticalExtensions_PR_rrcSetupComplete; + + auto &ies = setupComplete->criticalExtensions.choice.rrcSetupComplete = asn::New(); + ies->selectedPLMN_Identity = 1; + asn::SetOctetString(ies->dedicatedNAS_Message, m_initialNasPdu); + + m_initialNasPdu = {}; + sendRrcMessage(pdu); + + m_logger->info("RRC connection established"); + switchState(ERrcState::RRC_CONNECTED); + m_base->nasTask->push(new NmUeRrcToNas(NmUeRrcToNas::RRC_CONNECTION_SETUP)); +} + +void UeRrcTask::receiveRrcReject(int cellId, const ASN_RRC_RRCReject &msg) +{ + if (!isActiveCell(cellId)) + return; + + m_logger->err("RRC Reject received"); + + handleEstablishmentFailure(); +} + +void UeRrcTask::receiveRrcRelease(const ASN_RRC_RRCRelease &msg) +{ + m_logger->debug("RRC Release received"); + m_state = ERrcState::RRC_IDLE; + m_base->nasTask->push(new NmUeRrcToNas(NmUeRrcToNas::RRC_CONNECTION_RELEASE)); +} + +void UeRrcTask::handleEstablishmentFailure() +{ + m_base->nasTask->push(new NmUeRrcToNas(NmUeRrcToNas::RRC_ESTABLISHMENT_FAILURE)); +} + +} // namespace nr::ue diff --git a/src/ue/rrc/failures.cpp b/src/ue/rrc/failures.cpp new file mode 100644 index 000000000..3920b2427 --- /dev/null +++ b/src/ue/rrc/failures.cpp @@ -0,0 +1,29 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include +#include + +namespace nr::ue +{ + +void UeRrcTask::declareRadioLinkFailure(rls::ERlfCause cause) +{ + handleRadioLinkFailure(cause); +} + +void UeRrcTask::handleRadioLinkFailure(rls::ERlfCause cause) +{ + m_state = ERrcState::RRC_IDLE; + m_base->nasTask->push(new NmUeRrcToNas(NmUeRrcToNas::RADIO_LINK_FAILURE)); +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/handler.cpp b/src/ue/rrc/handler.cpp index c54a3eb7d..d206f4107 100644 --- a/src/ue/rrc/handler.cpp +++ b/src/ue/rrc/handler.cpp @@ -32,108 +32,6 @@ namespace nr::ue { -void UeRrcTask::deliverInitialNas(OctetString &&nasPdu, long establishmentCause) -{ - if (m_state != ERrcState::RRC_IDLE) - { - m_logger->warn("Initial NAS delivery while not in RRC_IDLE, treating as uplink delivery"); - deliverUplinkNas(std::move(nasPdu)); - return; - } - - m_logger->debug("Sending RRC Setup Request"); - - auto *pdu = asn::New(); - pdu->message.present = ASN_RRC_UL_CCCH_MessageType_PR_c1; - pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); - pdu->message.choice.c1->present = ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcSetupRequest; - - m_initialId.present = ASN_RRC_InitialUE_Identity_PR_randomValue; - asn::SetBitStringLong<39>(utils::Random64(), m_initialId.choice.randomValue); - - auto &r = pdu->message.choice.c1->choice.rrcSetupRequest = asn::New(); - r->rrcSetupRequest.establishmentCause = establishmentCause; - asn::SetSpareBits<1>(r->rrcSetupRequest.spare); - asn::DeepCopy(asn_DEF_ASN_RRC_InitialUE_Identity, m_initialId, &r->rrcSetupRequest.ue_Identity); - - // TODO: Start T300 - - m_initialNasPdu = std::move(nasPdu); - m_lastSetupReq = ERrcLastSetupRequest::SETUP_REQUEST; - sendRrcMessage(pdu); -} - -void UeRrcTask::deliverUplinkNas(OctetString &&nasPdu) -{ - auto *pdu = asn::New(); - pdu->message.present = ASN_RRC_UL_DCCH_MessageType_PR_c1; - pdu->message.choice.c1 = - asn::New(); - pdu->message.choice.c1->present = ASN_RRC_UL_DCCH_MessageType__c1_PR_ulInformationTransfer; - pdu->message.choice.c1->choice.ulInformationTransfer = asn::New(); - - auto &c1 = pdu->message.choice.c1->choice.ulInformationTransfer->criticalExtensions; - c1.present = ASN_RRC_ULInformationTransfer__criticalExtensions_PR_ulInformationTransfer; - c1.choice.ulInformationTransfer = asn::New(); - c1.choice.ulInformationTransfer->dedicatedNAS_Message = asn::New(); - - asn::SetOctetString(*c1.choice.ulInformationTransfer->dedicatedNAS_Message, nasPdu); - - sendRrcMessage(pdu); -} - -void UeRrcTask::receiveRrcSetup(const ASN_RRC_RRCSetup &msg) -{ - if (m_lastSetupReq != ERrcLastSetupRequest::SETUP_REQUEST) - { - // TODO - return; - } - - m_state = ERrcState::RRC_CONNECTED; - - auto *pdu = asn::New(); - pdu->message.present = ASN_RRC_UL_DCCH_MessageType_PR_c1; - pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); - pdu->message.choice.c1->present = ASN_RRC_UL_DCCH_MessageType__c1_PR_rrcSetupComplete; - auto &setupComplete = pdu->message.choice.c1->choice.rrcSetupComplete = asn::New(); - setupComplete->rrc_TransactionIdentifier = msg.rrc_TransactionIdentifier; - setupComplete->criticalExtensions.present = ASN_RRC_RRCSetupComplete__criticalExtensions_PR_rrcSetupComplete; - auto &ies = setupComplete->criticalExtensions.choice.rrcSetupComplete = asn::New(); - ies->selectedPLMN_Identity = 1; - asn::SetOctetString(ies->dedicatedNAS_Message, m_initialNasPdu); - m_initialNasPdu = {}; - - m_logger->info("RRC connection established"); - - m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_SETUP)); - - sendRrcMessage(pdu); -} - -void UeRrcTask::receiveRrcReject(const ASN_RRC_RRCReject &msg) -{ - // TODO - m_logger->err("RRC Reject received"); -} - -void UeRrcTask::receiveDownlinkInformationTransfer(const ASN_RRC_DLInformationTransfer &msg) -{ - OctetString nasPdu = - asn::GetOctetString(*msg.criticalExtensions.choice.dlInformationTransfer->dedicatedNAS_Message); - - auto *nw = new NwUeRrcToNas(NwUeRrcToNas::NAS_DELIVERY); - nw->nasPdu = std::move(nasPdu); - m_base->nasTask->push(nw); -} - -void UeRrcTask::receiveRrcRelease(const ASN_RRC_RRCRelease &msg) -{ - m_logger->debug("RRC Release received"); - m_state = ERrcState::RRC_IDLE; - m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_RELEASE)); -} - void UeRrcTask::receivePaging(const ASN_RRC_Paging &msg) { std::vector tmsiIds{}; @@ -153,7 +51,7 @@ void UeRrcTask::receivePaging(const ASN_RRC_Paging &msg) } }); - auto *w = new NwUeRrcToNas(NwUeRrcToNas::PAGING); + auto *w = new NmUeRrcToNas(NmUeRrcToNas::PAGING); w->pagingTmsi = std::move(tmsiIds); m_base->nasTask->push(w); } diff --git a/src/ue/rrc/idle.cpp b/src/ue/rrc/idle.cpp new file mode 100644 index 000000000..64b5b6085 --- /dev/null +++ b/src/ue/rrc/idle.cpp @@ -0,0 +1,283 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include + +#include +#include +#include + +namespace nr::ue +{ + +void UeRrcTask::performCellSelection() +{ + if (m_state == ERrcState::RRC_CONNECTED) + return; + + int64_t currentTime = utils::CurrentTimeMillis(); + + if (currentTime - m_startedTime <= 1000LL && m_cellDesc.empty()) + return; + + auto lastCell = m_base->shCtx.currentCell.get(); + + bool shouldLogErrors = lastCell.cellId != 0 || (currentTime - m_lastTimePlmnSearchFailureLogged >= 30'000LL); + + ActiveCellInfo cellInfo; + CellSelectionReport report; + + bool cellFound = false; + if (m_base->shCtx.selectedPlmn.get().hasValue()) + { + cellFound = lookForSuitableCell(cellInfo, report); + if (!cellFound) + { + if (shouldLogErrors) + { + if (!m_cellDesc.empty()) + { + m_logger->warn( + "Suitable cell selection failed in [%d] cells. [%d] out of PLMN, [%d] no SI, [%d] reserved, " + "[%d] barred, ftai [%d]", + static_cast(m_cellDesc.size()), report.outOfPlmnCells, report.sib1MissingCells, + report.reservedCells, report.barredCells, report.forbiddenTaiCells); + } + else + { + m_logger->warn("Suitable cell selection failed, no cell is in coverage"); + } + + m_lastTimePlmnSearchFailureLogged = currentTime; + } + } + } + + if (!cellFound) + { + report = {}; + + cellFound = lookForAcceptableCell(cellInfo, report); + + if (!cellFound) + { + if (shouldLogErrors) + { + if (!m_cellDesc.empty()) + { + m_logger->warn("Acceptable cell selection failed in [%d] cells. [%d] no SI, [%d] reserved, [%d] " + "barred, ftai [%d]", + static_cast(m_cellDesc.size()), report.sib1MissingCells, report.reservedCells, + report.barredCells, report.forbiddenTaiCells); + } + else + { + m_logger->warn("Acceptable cell selection failed, no cell is in coverage"); + } + + m_logger->err("Cell selection failure, no suitable or acceptable cell found"); + + m_lastTimePlmnSearchFailureLogged = currentTime; + } + } + } + + int selectedCell = cellInfo.cellId; + m_base->shCtx.currentCell.set(cellInfo); + + if (selectedCell != 0 && selectedCell != lastCell.cellId) + m_logger->info("Selected cell plmn[%s] tac[%d] category[%s]", ToJson(cellInfo.plmn).str().c_str(), cellInfo.tac, + ToJson(cellInfo.category).str().c_str()); + + if (selectedCell != lastCell.cellId) + { + auto *w1 = new NmUeRrcToRls(NmUeRrcToRls::ASSIGN_CURRENT_CELL); + w1->cellId = selectedCell; + m_base->rlsTask->push(w1); + + auto w2 = new NmUeRrcToNas(NmUeRrcToNas::ACTIVE_CELL_CHANGED); + w2->previousTai = Tai{lastCell.plmn, lastCell.tac}; + m_base->nasTask->push(w2); + } +} + +bool UeRrcTask::lookForSuitableCell(ActiveCellInfo &cellInfo, CellSelectionReport &report) +{ + Plmn selectedPlmn = m_base->shCtx.selectedPlmn.get(); + if (!selectedPlmn.hasValue()) + return false; + + std::vector candidates; + + for (auto &item : m_cellDesc) + { + auto &cell = item.second; + + if (!cell.sib1.hasSib1) + { + report.sib1MissingCells++; + continue; + } + + if (cell.sib1.plmn != selectedPlmn) + { + report.outOfPlmnCells++; + continue; + } + + if (cell.mib.hasMib) + { + if (cell.mib.isBarred) + { + report.barredCells++; + continue; + } + } + + if (cell.sib1.isReserved) + { + report.reservedCells++; + continue; + } + + Tai tai{cell.sib1.plmn, cell.sib1.tac}; + + if (m_base->shCtx.forbiddenTaiRoaming.get([&tai](auto &item) { + return std::any_of(item.begin(), item.end(), [&tai](auto &element) { return element == tai; }); + })) + { + report.forbiddenTaiCells++; + continue; + } + + if (m_base->shCtx.forbiddenTaiRps.get([&tai](auto &item) { + return std::any_of(item.begin(), item.end(), [&tai](auto &element) { return element == tai; }); + })) + { + report.forbiddenTaiCells++; + continue; + } + + // It seems suitable + candidates.push_back(item.first); + } + + if (candidates.empty()) + return false; + + // Order candidates by signal strength + std::sort(candidates.begin(), candidates.end(), [this](int a, int b) { + auto &cellA = m_cellDesc[a]; + auto &cellB = m_cellDesc[b]; + return cellB.dbm < cellA.dbm; + }); + + auto &selectedId = candidates[0]; + auto &selectedCell = m_cellDesc[selectedId]; + + cellInfo = {}; + cellInfo.cellId = selectedId; + cellInfo.plmn = selectedCell.sib1.plmn; + cellInfo.tac = selectedCell.sib1.tac; + cellInfo.category = ECellCategory::SUITABLE_CELL; + + return true; +} + +bool UeRrcTask::lookForAcceptableCell(ActiveCellInfo &cellInfo, CellSelectionReport &report) +{ + std::vector candidates; + + for (auto &item : m_cellDesc) + { + auto &cell = item.second; + + if (!cell.sib1.hasSib1) + { + report.sib1MissingCells++; + continue; + } + + if (cell.mib.hasMib) + { + if (cell.mib.isBarred) + { + report.barredCells++; + continue; + } + } + + if (cell.sib1.isReserved) + { + report.reservedCells++; + continue; + } + + Tai tai{cell.sib1.plmn, cell.sib1.tac}; + + if (m_base->shCtx.forbiddenTaiRoaming.get([&tai](auto &item) { + return std::any_of(item.begin(), item.end(), [&tai](auto &element) { return element == tai; }); + })) + { + report.forbiddenTaiCells++; + continue; + } + + if (m_base->shCtx.forbiddenTaiRps.get([&tai](auto &item) { + return std::any_of(item.begin(), item.end(), [&tai](auto &element) { return element == tai; }); + })) + { + report.forbiddenTaiCells++; + continue; + } + + // It seems acceptable + candidates.push_back(item.first); + } + + if (candidates.empty()) + return false; + + // Order candidates by signal strength first + std::sort(candidates.begin(), candidates.end(), [this](int a, int b) { + auto &cellA = m_cellDesc[a]; + auto &cellB = m_cellDesc[b]; + return cellB.dbm < cellA.dbm; + }); + + // Then order candidates by PLMN priority if we have a selected PLMN + Plmn selectedPlmn = m_base->shCtx.selectedPlmn.get(); + if (selectedPlmn.hasValue()) + { + // Using stable-sort here + std::stable_sort(candidates.begin(), candidates.end(), [this, &selectedPlmn](int a, int b) { + auto &cellA = m_cellDesc[a]; + auto &cellB = m_cellDesc[b]; + + bool matchesA = cellA.sib1.hasSib1 && cellA.sib1.plmn == selectedPlmn; + bool matchesB = cellB.sib1.hasSib1 && cellB.sib1.plmn == selectedPlmn; + + return matchesB < matchesA; + }); + } + + auto &selectedId = candidates[0]; + auto &selectedCell = m_cellDesc[selectedId]; + + cellInfo = {}; + cellInfo.cellId = selectedId; + cellInfo.plmn = selectedCell.sib1.plmn; + cellInfo.tac = selectedCell.sib1.tac; + cellInfo.category = ECellCategory::ACCEPTABLE_CELL; + + return true; +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/nas.cpp b/src/ue/rrc/nas.cpp new file mode 100644 index 000000000..c5451095a --- /dev/null +++ b/src/ue/rrc/nas.cpp @@ -0,0 +1,72 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +namespace nr::ue +{ + +void UeRrcTask::deliverUplinkNas(uint32_t pduId, OctetString &&nasPdu) +{ + if (!m_base->shCtx.currentCell.get([](auto &value) { return value.hasValue(); })) + { + m_logger->err("Uplink NAS delivery failed. No active cell"); + return; + } + + if (nasPdu.length() == 0) + return; + + if (m_state == ERrcState::RRC_IDLE) + { + startConnectionEstablishment(std::move(nasPdu)); + return; + } + else if (m_state == ERrcState::RRC_INACTIVE) + { + // TODO + return; + } + + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_UL_DCCH_MessageType_PR_c1; + pdu->message.choice.c1 = + asn::New(); + pdu->message.choice.c1->present = ASN_RRC_UL_DCCH_MessageType__c1_PR_ulInformationTransfer; + pdu->message.choice.c1->choice.ulInformationTransfer = asn::New(); + + auto &c1 = pdu->message.choice.c1->choice.ulInformationTransfer->criticalExtensions; + c1.present = ASN_RRC_ULInformationTransfer__criticalExtensions_PR_ulInformationTransfer; + c1.choice.ulInformationTransfer = asn::New(); + c1.choice.ulInformationTransfer->dedicatedNAS_Message = asn::New(); + + asn::SetOctetString(*c1.choice.ulInformationTransfer->dedicatedNAS_Message, nasPdu); + + sendRrcMessage(pdu); +} + +void UeRrcTask::receiveDownlinkInformationTransfer(const ASN_RRC_DLInformationTransfer &msg) +{ + OctetString nasPdu = + asn::GetOctetString(*msg.criticalExtensions.choice.dlInformationTransfer->dedicatedNAS_Message); + + auto *m = new NmUeRrcToNas(NmUeRrcToNas::NAS_DELIVERY); + m->nasPdu = std::move(nasPdu); + m_base->nasTask->push(m); +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/sap.cpp b/src/ue/rrc/sap.cpp new file mode 100644 index 000000000..2ae198aaa --- /dev/null +++ b/src/ue/rrc/sap.cpp @@ -0,0 +1,59 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include +#include +#include + +namespace nr::ue +{ + +void UeRrcTask::handleRlsSapMessage(NmUeRlsToRrc &msg) +{ + switch (msg.present) + { + case NmUeRlsToRrc::SIGNAL_CHANGED: { + handleCellSignalChange(msg.cellId, msg.dbm); + break; + } + case NmUeRlsToRrc::DOWNLINK_RRC_DELIVERY: { + handleDownlinkRrc(msg.cellId, msg.channel, msg.pdu); + break; + } + case NmUeRlsToRrc::RADIO_LINK_FAILURE: { + handleRadioLinkFailure(msg.rlfCause); + break; + } + } +} + +void UeRrcTask::handleNasSapMessage(NmUeNasToRrc &msg) +{ + switch (msg.present) + { + case NmUeNasToRrc::UPLINK_NAS_DELIVERY: { + deliverUplinkNas(msg.pduId, std::move(msg.nasPdu)); + break; + } + case NmUeNasToRrc::LOCAL_RELEASE_CONNECTION: { + switchState(ERrcState::RRC_IDLE); + m_base->rlsTask->push(new NmUeRrcToRls(NmUeRrcToRls::RESET_STI)); + m_base->nasTask->push(new NmUeRrcToNas(NmUeRrcToNas::RRC_CONNECTION_RELEASE)); + break; + } + case NmUeNasToRrc::RRC_NOTIFY: { + triggerCycle(); + break; + } + } +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/state.cpp b/src/ue/rrc/state.cpp new file mode 100644 index 000000000..4119e30b1 --- /dev/null +++ b/src/ue/rrc/state.cpp @@ -0,0 +1,59 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include +#include +#include + +namespace nr::ue +{ + +void UeRrcTask::triggerCycle() +{ + push(new NmUeRrcToRrc(NmUeRrcToRrc::TRIGGER_CYCLE)); +} + +void UeRrcTask::performCycle() +{ + if (m_state == ERrcState::RRC_CONNECTED) + { + } + else if (m_state == ERrcState::RRC_IDLE) + { + performCellSelection(); + } + else if (m_state == ERrcState::RRC_INACTIVE) + { + performCellSelection(); + } +} + +void UeRrcTask::switchState(ERrcState state) +{ + ERrcState oldState = m_state; + m_state = state; + + m_logger->info("UE switches to state [%s]", ToJson(state).str().c_str()); + + if (m_base->nodeListener) + { + m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::RRC, + ToJson(oldState).str(), ToJson(state).str()); + } + + onSwitchState(oldState, state); +} + +void UeRrcTask::onSwitchState(ERrcState oldState, ERrcState newState) +{ +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/sysinfo.cpp b/src/ue/rrc/sysinfo.cpp new file mode 100644 index 000000000..f90581ea9 --- /dev/null +++ b/src/ue/rrc/sysinfo.cpp @@ -0,0 +1,70 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "task.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace nr::ue +{ + +void UeRrcTask::receiveMib(int cellId, const ASN_RRC_MIB &msg) +{ + auto &desc = m_cellDesc[cellId]; + + desc.mib.isBarred = msg.cellBarred == ASN_RRC_MIB__cellBarred_barred; + desc.mib.isIntraFreqReselectAllowed = msg.intraFreqReselection == ASN_RRC_MIB__intraFreqReselection_allowed; + + desc.mib.hasMib = true; + + updateAvailablePlmns(); +} + +void UeRrcTask::receiveSib1(int cellId, const ASN_RRC_SIB1 &msg) +{ + auto &desc = m_cellDesc[cellId]; + + desc.sib1.isReserved = msg.cellAccessRelatedInfo.cellReservedForOtherUse != nullptr; + + auto *plmnIdentityInfo = msg.cellAccessRelatedInfo.plmn_IdentityList.list.array[0]; + desc.sib1.nci = asn::GetBitStringLong<36>(plmnIdentityInfo->cellIdentity); + + desc.sib1.isReserved &= + plmnIdentityInfo->cellReservedForOperatorUse == ASN_RRC_PLMN_IdentityInfo__cellReservedForOperatorUse_reserved; + + desc.sib1.tac = asn::GetBitStringInt<24>(*plmnIdentityInfo->trackingAreaCode); + + auto plmnIdentity = plmnIdentityInfo->plmn_IdentityList.list.array[0]; + desc.sib1.plmn = asn::rrc::GetPlmnId(*plmnIdentity); + + auto *barringInfo = msg.uac_BarringInfo->uac_BarringInfoSetList.list.array[0]; + + int barringBits = asn::GetBitStringInt<7>(barringInfo->uac_BarringForAccessIdentity); + desc.sib1.aiBarringSet.ai15 = bits::BitAt<0>(barringBits); + desc.sib1.aiBarringSet.ai14 = bits::BitAt<1>(barringBits); + desc.sib1.aiBarringSet.ai13 = bits::BitAt<2>(barringBits); + desc.sib1.aiBarringSet.ai12 = bits::BitAt<3>(barringBits); + desc.sib1.aiBarringSet.ai11 = bits::BitAt<4>(barringBits); + desc.sib1.aiBarringSet.ai2 = bits::BitAt<5>(barringBits); + desc.sib1.aiBarringSet.ai1 = bits::BitAt<6>(barringBits); + + desc.sib1.hasSib1 = true; + + updateAvailablePlmns(); +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/task.cpp b/src/ue/rrc/task.cpp index 1d10adb87..f016dadfc 100644 --- a/src/ue/rrc/task.cpp +++ b/src/ue/rrc/task.cpp @@ -17,18 +17,26 @@ #include #include +static constexpr const int TIMER_ID_MACHINE_CYCLE = 1; +static constexpr const int TIMER_PERIOD_MACHINE_CYCLE = 2500; + namespace nr::ue { -UeRrcTask::UeRrcTask(TaskBase *base) : m_base{base} +UeRrcTask::UeRrcTask(TaskBase *base) : m_base{base}, m_timers{} { m_logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "rrc"); + m_startedTime = utils::CurrentTimeMillis(); + m_state = ERrcState::RRC_IDLE; } void UeRrcTask::onStart() { + triggerCycle(); + + setTimer(TIMER_ID_MACHINE_CYCLE, TIMER_PERIOD_MACHINE_CYCLE); } void UeRrcTask::onQuit() @@ -45,61 +53,29 @@ void UeRrcTask::onLoop() switch (msg->msgType) { case NtsMessageType::UE_NAS_TO_RRC: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwUeNasToRrc::PLMN_SEARCH_REQUEST: { - m_base->rlsTask->push(new NwUeRrcToRls(NwUeRrcToRls::PLMN_SEARCH_REQUEST)); - break; - } - case NwUeNasToRrc::INITIAL_NAS_DELIVERY: { - deliverInitialNas(std::move(w->nasPdu), w->rrcEstablishmentCause); - break; - } - case NwUeNasToRrc::UPLINK_NAS_DELIVERY: { - deliverUplinkNas(std::move(w->nasPdu)); - break; - } - case NwUeNasToRrc::LOCAL_RELEASE_CONNECTION: { - m_state = ERrcState::RRC_IDLE; - m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_RELEASE)); - m_base->rlsTask->push(new NwUeRrcToRls(NwUeRrcToRls::RESET_STI)); - break; - } - case NwUeNasToRrc::CELL_SELECTION_COMMAND: { - auto *wr = new NwUeRrcToRls(NwUeRrcToRls::CELL_SELECTION_COMMAND); - wr->cellId = w->cellId; - wr->isSuitableCell = w->isSuitableCell; - m_base->rlsTask->push(wr); - break; - } - } + handleNasSapMessage(*dynamic_cast(msg)); break; } case NtsMessageType::UE_RLS_TO_RRC: { - auto *w = dynamic_cast(msg); + handleRlsSapMessage(*dynamic_cast(msg)); + break; + } + case NtsMessageType::UE_RRC_TO_RRC: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeRlsToRrc::PLMN_SEARCH_RESPONSE: { - auto *wr = new NwUeRrcToNas(NwUeRrcToNas::PLMN_SEARCH_RESPONSE); - wr->measurements = std::move(w->measurements); - m_base->nasTask->push(wr); - break; - } - case NwUeRlsToRrc::SERVING_CELL_CHANGE: { - auto *wr = new NwUeRrcToNas(NwUeRrcToNas::SERVING_CELL_CHANGE); - wr->servingCell = w->servingCell; - m_base->nasTask->push(wr); - break; - } - case NwUeRlsToRrc::RRC_PDU_DELIVERY: { - handleDownlinkRrc(w->channel, w->pdu); - break; - } - case NwUeRlsToRrc::RADIO_LINK_FAILURE: { - handleRadioLinkFailure(); + case NmUeRrcToRrc::TRIGGER_CYCLE: + performCycle(); break; } + break; + } + case NtsMessageType::TIMER_EXPIRED: { + auto *w = dynamic_cast(msg); + if (w->timerId == TIMER_ID_MACHINE_CYCLE) + { + setTimer(TIMER_ID_MACHINE_CYCLE, TIMER_PERIOD_MACHINE_CYCLE); + performCycle(); } break; } @@ -111,10 +87,4 @@ void UeRrcTask::onLoop() delete msg; } -void UeRrcTask::handleRadioLinkFailure() -{ - m_state = ERrcState::RRC_IDLE; - m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RADIO_LINK_FAILURE)); -} - } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/task.hpp b/src/ue/rrc/task.hpp index 96c669d2a..529292c12 100644 --- a/src/ue/rrc/task.hpp +++ b/src/ue/rrc/task.hpp @@ -8,15 +8,17 @@ #pragma once -#include #include #include +#include +#include + #include #include -#include #include #include -#include + +#include extern "C" { @@ -36,6 +38,8 @@ extern "C" struct ASN_RRC_RRCReject; struct ASN_RRC_RRCRelease; struct ASN_RRC_Paging; + struct ASN_RRC_MIB; + struct ASN_RRC_SIB1; } namespace nr::ue @@ -47,9 +51,18 @@ class UeRrcTask : public NtsTask TaskBase *m_base; std::unique_ptr m_logger; + int64_t m_startedTime; ERrcState m_state; + RrcTimers m_timers; + + /* Cell and PLMN related */ + std::unordered_map m_cellDesc{}; + int64_t m_lastTimePlmnSearchFailureLogged{}; + + /* Procedure related */ ERrcLastSetupRequest m_lastSetupReq{}; + /* Establishment procedure related */ ASN_RRC_InitialUE_Identity_t m_initialId{}; OctetString m_initialNasPdu{}; @@ -66,30 +79,60 @@ class UeRrcTask : public NtsTask private: /* Handlers */ - void handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &pdu); - void deliverInitialNas(OctetString &&nasPdu, long establishmentCause); - void deliverUplinkNas(OctetString &&nasPdu); - - void receiveRrcSetup(const ASN_RRC_RRCSetup &msg); - void receiveRrcReject(const ASN_RRC_RRCReject &msg); - void receiveRrcRelease(const ASN_RRC_RRCRelease &msg); - void receiveDownlinkInformationTransfer(const ASN_RRC_DLInformationTransfer &msg); void receivePaging(const ASN_RRC_Paging &msg); - void handleRadioLinkFailure(); - - /* RRC channel send message */ - void sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg); - void sendRrcMessage(ASN_RRC_UL_CCCH_Message *msg); - void sendRrcMessage(ASN_RRC_UL_CCCH1_Message *msg); + /* RRC Message Transmission and Receive */ + void handleDownlinkRrc(int cellId, rrc::RrcChannel channel, const OctetString &pdu); + void sendRrcMessage(int cellId, ASN_RRC_UL_CCCH_Message *msg); + void sendRrcMessage(int cellId, ASN_RRC_UL_CCCH1_Message *msg); void sendRrcMessage(ASN_RRC_UL_DCCH_Message *msg); - - /* RRC channel receive message */ - void receiveRrcMessage(ASN_RRC_BCCH_BCH_Message *msg); - void receiveRrcMessage(ASN_RRC_BCCH_DL_SCH_Message *msg); - void receiveRrcMessage(ASN_RRC_DL_CCCH_Message *msg); + void receiveRrcMessage(int cellId, ASN_RRC_BCCH_BCH_Message *msg); + void receiveRrcMessage(int cellId, ASN_RRC_BCCH_DL_SCH_Message *msg); + void receiveRrcMessage(int cellId, ASN_RRC_DL_CCCH_Message *msg); void receiveRrcMessage(ASN_RRC_DL_DCCH_Message *msg); void receiveRrcMessage(ASN_RRC_PCCH_Message *msg); + + /* Service Access Point */ + void handleRlsSapMessage(NmUeRlsToRrc &msg); + void handleNasSapMessage(NmUeNasToRrc &msg); + + /* State Management */ + void triggerCycle(); + void performCycle(); + void switchState(ERrcState state); + void onSwitchState(ERrcState oldState, ERrcState newState); + + /* Idle Mode Operations */ + void performCellSelection(); + bool lookForSuitableCell(ActiveCellInfo &cellInfo, CellSelectionReport &report); + bool lookForAcceptableCell(ActiveCellInfo &cellInfo, CellSelectionReport &report); + + /* Cell Management */ + void handleCellSignalChange(int cellId, int dbm); + void notifyCellDetected(int cellId, int dbm); + void notifyCellLost(int cellId); + bool hasSignalToCell(int cellId); + bool isActiveCell(int cellId); + void updateAvailablePlmns(); + + /* System Information and Broadcast */ + void receiveMib(int cellId, const ASN_RRC_MIB &msg); + void receiveSib1(int cellId, const ASN_RRC_SIB1 &msg); + + /* NAS Transport */ + void deliverUplinkNas(uint32_t pduId, OctetString &&nasPdu); + void receiveDownlinkInformationTransfer(const ASN_RRC_DLInformationTransfer &msg); + + /* Connection Control */ + void startConnectionEstablishment(OctetString &&nasPdu); + void handleEstablishmentFailure(); + void receiveRrcSetup(int cellId, const ASN_RRC_RRCSetup &msg); + void receiveRrcReject(int cellId, const ASN_RRC_RRCReject &msg); + void receiveRrcRelease(const ASN_RRC_RRCRelease &msg); + + /* Failures */ + void declareRadioLinkFailure(rls::ERlfCause cause); + void handleRadioLinkFailure(rls::ERlfCause cause); }; } // namespace nr::ue diff --git a/src/lib/nas/timer.cpp b/src/ue/timer.cpp similarity index 81% rename from src/lib/nas/timer.cpp rename to src/ue/timer.cpp index 894873163..ee68d137c 100644 --- a/src/lib/nas/timer.cpp +++ b/src/ue/timer.cpp @@ -12,31 +12,28 @@ #include -namespace nas -{ - -NasTimer::NasTimer(int timerCode, bool isMmTimer, int defaultInterval) +UeTimer::UeTimer(int timerCode, bool isMmTimer, int defaultInterval) : m_code(timerCode), m_isMm(isMmTimer), m_interval(defaultInterval), m_startMillis(0), m_isRunning(false), m_expiryCount(0), m_lastDebugPrintMs(0) { } -bool NasTimer::isRunning() const +bool UeTimer::isRunning() const { return m_isRunning; } -int NasTimer::getCode() const +int UeTimer::getCode() const { return m_code; } -bool NasTimer::isMmTimer() const +bool UeTimer::isMmTimer() const { return m_isMm; } -void NasTimer::start(bool clearExpiryCount) +void UeTimer::start(bool clearExpiryCount) { if (clearExpiryCount) resetExpiryCount(); @@ -44,7 +41,7 @@ void NasTimer::start(bool clearExpiryCount) m_isRunning = true; } -void NasTimer::start(const nas::IEGprsTimer2 &v, bool clearExpiryCount) +void UeTimer::start(const nas::IEGprsTimer2 &v, bool clearExpiryCount) { if (clearExpiryCount) resetExpiryCount(); @@ -53,7 +50,7 @@ void NasTimer::start(const nas::IEGprsTimer2 &v, bool clearExpiryCount) m_isRunning = true; } -void NasTimer::start(const nas::IEGprsTimer3 &v, bool clearExpiryCount) +void UeTimer::start(const nas::IEGprsTimer3 &v, bool clearExpiryCount) { if (clearExpiryCount) resetExpiryCount(); @@ -81,7 +78,7 @@ void NasTimer::start(const nas::IEGprsTimer3 &v, bool clearExpiryCount) m_isRunning = true; } -void NasTimer::stop(bool clearExpiryCount) +void UeTimer::stop(bool clearExpiryCount) { if (clearExpiryCount) resetExpiryCount(); @@ -93,7 +90,7 @@ void NasTimer::stop(bool clearExpiryCount) } } -bool NasTimer::performTick() +bool UeTimer::performTick() { if (m_isRunning) { @@ -114,12 +111,12 @@ bool NasTimer::performTick() return false; } -int NasTimer::getInterval() const +int UeTimer::getInterval() const { return m_interval; } -int NasTimer::getRemaining() const +int UeTimer::getRemaining() const { if (!m_isRunning) return 0; @@ -128,17 +125,17 @@ int NasTimer::getRemaining() const return std::max(m_interval - elapsed, 0); } -void NasTimer::resetExpiryCount() +void UeTimer::resetExpiryCount() { m_expiryCount = 0; } -int NasTimer::getExpiryCount() const +int UeTimer::getExpiryCount() const { return m_expiryCount; } -Json ToJson(const NasTimer &v) +Json ToJson(const UeTimer &v) { std::stringstream ss{}; if (v.isRunning()) @@ -148,5 +145,3 @@ Json ToJson(const NasTimer &v) return ss.str(); } - -} // namespace nas diff --git a/src/lib/nas/timer.hpp b/src/ue/timer.hpp similarity index 74% rename from src/lib/nas/timer.hpp rename to src/ue/timer.hpp index b49cb1727..1454cd4bb 100644 --- a/src/lib/nas/timer.hpp +++ b/src/ue/timer.hpp @@ -8,14 +8,10 @@ #pragma once -#include "ie4.hpp" - +#include #include -namespace nas -{ - -class NasTimer +class UeTimer { private: const int m_code; @@ -29,12 +25,12 @@ class NasTimer long m_lastDebugPrintMs; public: - NasTimer(int timerCode, bool isMmTimer, int defaultInterval); + UeTimer(int timerCode, bool isMmTimer, int defaultInterval); public: void start(bool clearExpiryCount = true); - void start(const IEGprsTimer2 &v, bool clearExpiryCount = true); - void start(const IEGprsTimer3 &v, bool clearExpiryCount = true); + void start(const nas::IEGprsTimer2 &v, bool clearExpiryCount = true); + void start(const nas::IEGprsTimer3 &v, bool clearExpiryCount = true); void stop(bool clearExpiryCount = true); void resetExpiryCount(); bool performTick(); @@ -46,6 +42,4 @@ class NasTimer [[nodiscard]] int getExpiryCount() const; }; -Json ToJson(const NasTimer &v); - -} // namespace nas \ No newline at end of file +Json ToJson(const UeTimer &v); diff --git a/src/ue/tun/task.cpp b/src/ue/tun/task.cpp index 1b2772e5e..e2851e810 100644 --- a/src/ue/tun/task.cpp +++ b/src/ue/tun/task.cpp @@ -15,7 +15,7 @@ #include // TODO: May be reduced to MTU 1500 -#define RECEIVER_BUFFER_SIZE 16000 +#define RECEIVER_BUFFER_SIZE 8000 struct ReceiverArgs { @@ -35,11 +35,11 @@ static std::string GetErrorMessage(const std::string &cause) return what; } -static nr::ue::NwUeTunToApp *NwError(std::string &&error) +static nr::ue::NmUeTunToApp *NmError(std::string &&error) { - auto *nw = new nr::ue::NwUeTunToApp(nr::ue::NwUeTunToApp::TUN_ERROR); - nw->error = std::move(error); - return nw; + auto *m = new nr::ue::NmUeTunToApp(nr::ue::NmUeTunToApp::TUN_ERROR); + m->error = std::move(error); + return m; } static void ReceiverThread(ReceiverArgs *args) @@ -54,19 +54,19 @@ static void ReceiverThread(ReceiverArgs *args) while (true) { - int n = ::read(fd, buffer, RECEIVER_BUFFER_SIZE); + ssize_t n = ::read(fd, buffer, RECEIVER_BUFFER_SIZE); if (n < 0) { - targetTask->push(NwError(GetErrorMessage("TUN device could not read"))); + targetTask->push(NmError(GetErrorMessage("TUN device could not read"))); return; // Abort receiver thread } if (n > 0) { - auto *nw = new nr::ue::NwUeTunToApp(nr::ue::NwUeTunToApp::DATA_PDU_DELIVERY); - nw->psi = psi; - nw->data = OctetString::FromArray(buffer, static_cast(n)); - targetTask->push(nw); + auto *m = new nr::ue::NmUeTunToApp(nr::ue::NmUeTunToApp::DATA_PDU_DELIVERY); + m->psi = psi; + m->data = OctetString::FromArray(buffer, static_cast(n)); + targetTask->push(m); } } } @@ -103,12 +103,12 @@ void TunTask::onLoop() switch (msg->msgType) { case NtsMessageType::UE_APP_TO_TUN: { - auto *w = dynamic_cast(msg); - int res = ::write(m_fd, w->data.data(), w->data.length()); + auto *w = dynamic_cast(msg); + ssize_t res = ::write(m_fd, w->data.data(), w->data.length()); if (res < 0) - push(NwError(GetErrorMessage("TUN device could not write"))); + push(NmError(GetErrorMessage("TUN device could not write"))); else if (res != w->data.length()) - push(NwError(GetErrorMessage("TUN device partially written"))); + push(NmError(GetErrorMessage("TUN device partially written"))); delete w; break; } diff --git a/src/ue/types.cpp b/src/ue/types.cpp index 8e9842da0..caa1497a6 100644 --- a/src/ue/types.cpp +++ b/src/ue/types.cpp @@ -12,7 +12,7 @@ namespace nr::ue { -UeTimers::UeTimers() +NasTimers::NasTimers() : t3346(3346, true, INT32_MAX), t3396(3396, false, INT32_MAX), t3444(3444, true, 12 * 60 * 60), t3445(3445, true, 12 * 60 * 60), t3502(3502, true, 12 * 60), t3510(3510, true, 15), t3511(3511, true, 10), t3512(3512, true, 54 * 60), t3516(3516, true, 30), t3517(3517, true, 15), t3519(3519, true, 60), @@ -68,14 +68,29 @@ Json ToJson(const EMmState &state) } } +Json ToJson(const ERrcState &state) +{ + switch (state) + { + case ERrcState::RRC_IDLE: + return "RRC-IDLE"; + case ERrcState::RRC_CONNECTED: + return "RRC-CONNECTED"; + case ERrcState::RRC_INACTIVE: + return "RRC-INACTIVE"; + default: + return "?"; + } +} + Json ToJson(const EMmSubState &state) { switch (state) { - case EMmSubState::MM_NULL_NA: - return "MM-NULL-NA"; - case EMmSubState::MM_DEREGISTERED_NA: - return "MM-DEREGISTERED/NA"; + case EMmSubState::MM_NULL_PS: + return "MM-NULL"; + case EMmSubState::MM_DEREGISTERED_PS: + return "MM-DEREGISTERED/PS"; case EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE: return "MM-DEREGISTERED/NORMAL-SERVICE"; case EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE: @@ -92,10 +107,10 @@ Json ToJson(const EMmSubState &state) return "MM-DEREGISTERED/ECALL-INACTIVE"; case EMmSubState::MM_DEREGISTERED_INITIAL_REGISTRATION_NEEDED: return "MM-DEREGISTERED/INITIAL-REGISTRATION-NEEDED"; - case EMmSubState::MM_REGISTERED_INITIATED_NA: - return "MM-REGISTER-INITIATED/NA"; - case EMmSubState::MM_REGISTERED_NA: - return "MM-REGISTERED/NA"; + case EMmSubState::MM_REGISTERED_INITIATED_PS: + return "MM-REGISTER-INITIATED"; + case EMmSubState::MM_REGISTERED_PS: + return "MM-REGISTERED/PS"; case EMmSubState::MM_REGISTERED_NORMAL_SERVICE: return "MM-REGISTERED/NORMAL-SERVICE"; case EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE: @@ -110,26 +125,100 @@ Json ToJson(const EMmSubState &state) return "MM-REGISTERED/NO-CELL-AVAILABLE"; case EMmSubState::MM_REGISTERED_UPDATE_NEEDED: return "MM-REGISTERED/UPDATE-NEEDED"; - case EMmSubState::MM_DEREGISTERED_INITIATED_NA: - return "MM-DEREGISTER-INITIATED/NA"; - case EMmSubState::MM_SERVICE_REQUEST_INITIATED_NA: - return "MM-SERVICE-REQUEST-INITIATED/NA"; + case EMmSubState::MM_DEREGISTERED_INITIATED_PS: + return "MM-DEREGISTER-INITIATED"; + case EMmSubState::MM_SERVICE_REQUEST_INITIATED_PS: + return "MM-SERVICE-REQUEST-INITIATED"; default: return "?"; } } -Json ToJson(const UeConfig &v) +Json ToJson(const EServiceReqCause &v) { - return Json::Obj({ - {"supi", ToJson(v.supi)}, - {"hplmn", ToJson(v.hplmn)}, - {"imei", ::ToJson(v.imei)}, - {"imeiSv", ::ToJson(v.imeiSv)}, - }); + switch (v) + { + case EServiceReqCause::IDLE_PAGING: + return "IDLE-PAGING"; + case EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP: + return "CONNECTED-3GPP-NOTIFICATION-N3GPP"; + case EServiceReqCause::IDLE_UPLINK_SIGNAL_PENDING: + return "IDLE-UPLINK-SIGNAL-PENDING"; + case EServiceReqCause::IDLE_UPLINK_DATA_PENDING: + return "IDLE-UPLINK-DATA-PENDING"; + case EServiceReqCause::CONNECTED_UPLINK_DATA_PENDING: + return "CONNECTED-UPLINK-DATA-PENDING"; + case EServiceReqCause::NON_3GPP_AS_ESTABLISHED: + return "NON-3GPP-AS-ESTABLISHED"; + case EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP: + return "IDLE-3GPP-NOTIFICATION-N3GPP"; + case EServiceReqCause::EMERGENCY_FALLBACK: + return "EMERGENCY-FALLBACK"; + case EServiceReqCause::FALLBACK_INDICATION: + return "FALLBACK-INDICATION"; + default: + return "?"; + } +} + +Json ToJson(const ERegUpdateCause &v) +{ + switch (v) + { + case ERegUpdateCause::ENTER_UNLISTED_TRACKING_AREA: + return "ENTER-UNLISTED-TRACKING-AREA"; + case ERegUpdateCause::T3512_EXPIRY: + return "T3512-EXPIRY"; + case ERegUpdateCause::CONFIGURATION_UPDATE: + return "CONFIGURATION-UPDATE"; + case ERegUpdateCause::PAGING_OR_NOTIFICATION: + return "PAGING-OR-NOTIFICATION"; + case ERegUpdateCause::INTER_SYSTEM_CHANGE_S1_TO_N1: + return "INTER-SYSTEM-CHANGE-S1-TO-N1"; + case ERegUpdateCause::CONNECTION_RECOVERY: + return "CONNECTION-RECOVERY"; + case ERegUpdateCause::FALLBACK_INDICATION: + return "FALLBACK-INDICATION"; + case ERegUpdateCause::MM_OR_S1_CAPABILITY_CHANGE: + return "MM-OR-S1-CAPABILITY-CHANGE"; + case ERegUpdateCause::USAGE_SETTING_CHANGE: + return "USAGE-SETTING_CHANGE"; + case ERegUpdateCause::SLICE_CHANGE: + return "SLICE-CHANGE"; + case ERegUpdateCause::DRX_CHANGE: + return "DRX-CHANGE"; + case ERegUpdateCause::EMERGENCY_CASE: + return "EMERGENCY-CASE"; + case ERegUpdateCause::SMS_OVER_NAS_CHANGE: + return "SMS-OVER-NAS-CHANGE"; + case ERegUpdateCause::PS_STATUS_INFORM: + return "PS-STATUS-INFORM"; + case ERegUpdateCause::RADIO_CAP_CHANGE: + return "RADIO-CAP-CHANGE"; + case ERegUpdateCause::NEW_LADN_NEEDED: + return "NEW-LADN-NEEDED"; + case ERegUpdateCause::MICO_MODE_CHANGE: + return "MICO-MODE-CHANGE"; + case ERegUpdateCause::ENTER_EQUIVALENT_PLMN_CELL: + return "ENTER-EQUIVALENT-PLMN-CELL"; + case ERegUpdateCause::RESTRICTED_SERVICE_AREA: + return "RESTRICTED-SERVICE-AREA"; + case ERegUpdateCause::TAI_CHANGE_IN_ATT_UPD: + return "TAI-CHANGE-IN-ATT-UPD"; + case ERegUpdateCause::PLMN_CHANGE_IN_ATT_UPD: + return "PLMN-CHANGE-IN-ATT-UPD"; + case ERegUpdateCause::T3346_EXPIRY_IN_ATT_UPD: + return "T3346-EXPIRY-IN-ATT-UPD"; + case ERegUpdateCause::T3502_EXPIRY_IN_ATT_UPD: + return "T3502-EXPIRY-IN-ATT-UPD"; + case ERegUpdateCause::T3511_EXPIRY_IN_ATT_UPD: + return "T3511-EXPIRY-IN-ATT-UPD"; + default: + return "?"; + } } -Json ToJson(const UeTimers &v) +Json ToJson(const NasTimers &v) { return Json::Obj({ {"T3346", ToJson(v.t3346)}, @@ -167,55 +256,6 @@ Json ToJson(const E5UState &state) } } -Json ToJson(const ERegUpdateCause &v) -{ - switch (v) - { - case ERegUpdateCause::UNSPECIFIED: - return "UNSPECIFIED"; - case ERegUpdateCause::ENTER_UNLISTED_TRACKING_AREA: - return "ENTER_UNLISTED_TRACKING_AREA"; - case ERegUpdateCause::T3512_EXPIRY: - return "T3512_EXPIRY"; - case ERegUpdateCause::CONFIGURATION_UPDATE: - return "CONFIGURATION_UPDATE"; - case ERegUpdateCause::PAGING_OR_NOTIFICATION: - return "PAGING_OR_NOTIFICATION"; - case ERegUpdateCause::INTER_SYSTEM_CHANGE_S1_TO_N1: - return "INTER_SYSTEM_CHANGE_S1_TO_N1"; - case ERegUpdateCause::CONNECTION_RECOVERY: - return "CONNECTION_RECOVERY"; - case ERegUpdateCause::FALLBACK_INDICATION: - return "FALLBACK_INDICATION"; - case ERegUpdateCause::MM_OR_S1_CAPABILITY_CHANGE: - return "MM_OR_S1_CAPABILITY_CHANGE"; - case ERegUpdateCause::USAGE_SETTING_CHANGE: - return "USAGE_SETTING_CHANGE"; - case ERegUpdateCause::SLICE_CHANGE: - return "SLICE_CHANGE"; - case ERegUpdateCause::DRX_CHANGE: - return "DRX_CHANGE"; - case ERegUpdateCause::EMERGENCY_CASE: - return "EMERGENCY_CASE"; - case ERegUpdateCause::SMS_OVER_NAS_CHANGE: - return "SMS_OVER_NAS_CHANGE"; - case ERegUpdateCause::PS_STATUS_INFORM: - return "PS_STATUS_INFORM"; - case ERegUpdateCause::RADIO_CAP_CHANGE: - return "RADIO_CAP_CHANGE"; - case ERegUpdateCause::NEW_LADN_NEEDED: - return "NEW_LADN_NEEDED"; - case ERegUpdateCause::MICO_MODE_CHANGE: - return "MICO_MODE_CHANGE"; - case ERegUpdateCause::ENTER_EQUIVALENT_PLMN_CELL: - return "ENTER_EQUIVALENT_PLMN_CELL"; - case ERegUpdateCause::RESTRICTED_SERVICE_AREA: - return "RESTRICTED_SERVICE_AREA"; - default: - return "?"; - } -} - Json ToJson(const EPsState &state) { switch (state) @@ -235,43 +275,33 @@ Json ToJson(const EPsState &state) } } -Json ToJson(const UePduSessionInfo &v) +bool ActiveCellInfo::hasValue() const { - return Json::Obj({{"id", v.psi}, - {"type", v.type}, - {"address", v.address}, - {"emergency", v.isEmergency}, - {"apn", ::ToJson(v.apn)}, - {"s-nssai", ToJson(v.sNssai)}}); + return cellId != 0; } -Json ToJson(const EServiceReqCause &v) +Plmn UeSharedContext::getCurrentPlmn() +{ + return currentCell.get([](auto &value) { return value.plmn; }); +} + +Tai UeSharedContext::getCurrentTai() +{ + Tai tai; + currentCell.access([&tai](auto &value) { + tai.plmn = value.plmn; + tai.tac = value.tac; + }); + return tai; +} + +bool UeSharedContext::hasActiveCell() +{ + return getCurrentTai().hasValue(); +} + +RrcTimers::RrcTimers() : t300(300, false, 1) { - switch (v) - { - case EServiceReqCause::UNSPECIFIED: - return "UNSPECIFIED"; - case EServiceReqCause::IDLE_PAGING: - return "IDLE_PAGING"; - case EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP: - return "CONNECTED_3GPP_NOTIFICATION_N3GPP"; - case EServiceReqCause::IDLE_UPLINK_SIGNAL_PENDING: - return "IDLE_UPLINK_SIGNAL_PENDING"; - case EServiceReqCause::IDLE_UPLINK_DATA_PENDING: - return "IDLE_UPLINK_DATA_PENDING"; - case EServiceReqCause::CONNECTED_UPLINK_DATA_PENDING: - return "CONNECTED_UPLINK_DATA_PENDING"; - case EServiceReqCause::NON_3GPP_AS_ESTABLISHED: - return "NON_3GPP_AS_ESTABLISHED"; - case EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP: - return "IDLE_3GPP_NOTIFICATION_N3GPP"; - case EServiceReqCause::EMERGENCY_FALLBACK: - return "EMERGENCY_FALLBACK"; - case EServiceReqCause::FALLBACK_INDICATION: - return "FALLBACK_INDICATION"; - default: - return "?"; - } } } // namespace nr::ue diff --git a/src/ue/types.hpp b/src/ue/types.hpp index 5ed808b90..12bc72113 100644 --- a/src/ue/types.hpp +++ b/src/ue/types.hpp @@ -8,14 +8,20 @@ #pragma once +#include "timer.hpp" + #include +#include +#include +#include +#include + #include #include #include -#include -#include #include #include +#include #include #include #include @@ -29,6 +35,28 @@ class UeRrcTask; class UeRlsTask; class UserEquipment; +struct UeCellDesc +{ + int dbm{}; + + struct + { + bool hasMib = false; + bool isBarred = true; + bool isIntraFreqReselectAllowed = true; + } mib{}; + + struct + { + bool hasSib1 = false; + bool isReserved = false; + int64_t nci = 0; + int tac = 0; + Plmn plmn; + UacAiBarringSet aiBarringSet; + } sib1{}; +}; + struct SupportedAlgs { bool nia1 = true; @@ -72,16 +100,10 @@ struct UeConfig std::optional imeiSv{}; SupportedAlgs supportedAlgs{}; std::vector gnbSearchList{}; - std::vector initSessions{}; + std::vector defaultSessions{}; IntegrityMaxDataRateConfig integrityMaxRate{}; - - /* Read from config file as well, but should be stored in non-volatile - * mobile storage and subject to change in runtime */ - struct Initials - { - NetworkSlice defaultConfiguredNssai{}; - NetworkSlice configuredNssai{}; - } initials{}; + NetworkSlice defaultConfiguredNssai{}; + NetworkSlice configuredNssai{}; /* Assigned by program */ bool configureRouting{}; @@ -112,6 +134,45 @@ struct UeConfig } }; +struct CellSelectionReport +{ + int outOfPlmnCells{}; + int sib1MissingCells{}; + int reservedCells{}; + int barredCells{}; + int forbiddenTaiCells{}; +}; + +struct ActiveCellInfo +{ + int cellId{}; + ECellCategory category{}; + Plmn plmn{}; + int tac{}; + + [[nodiscard]] bool hasValue() const; +}; + +struct UeSharedContext +{ + Locked> availablePlmns; + Locked selectedPlmn; + Locked currentCell; + Locked> forbiddenTaiRoaming; + Locked> forbiddenTaiRps; + Locked providedGuti; + Locked providedTmsi; + + Plmn getCurrentPlmn(); + Tai getCurrentTai(); + bool hasActiveCell(); +}; + +struct RlsSharedContext +{ + std::atomic sti{}; +}; + struct TaskBase { UserEquipment *ue{}; @@ -121,36 +182,45 @@ struct TaskBase app::INodeListener *nodeListener{}; NtsTask *cliCallbackTask{}; + UeSharedContext shCtx{}; + UeAppTask *appTask{}; NasTask *nasTask{}; UeRrcTask *rrcTask{}; UeRlsTask *rlsTask{}; }; -struct UeTimers +struct RrcTimers { - nas::NasTimer t3346; /* MM - ... */ - nas::NasTimer t3396; /* SM - ... */ - - nas::NasTimer t3444; /* MM - ... */ - nas::NasTimer t3445; /* MM - ... */ - - nas::NasTimer t3502; /* MM - Initiation of the registration procedure, if still required */ - nas::NasTimer t3510; /* MM - Registration Request transmission timer */ - nas::NasTimer t3511; /* MM - Retransmission of the REGISTRATION REQUEST, if still required */ - nas::NasTimer t3512; /* MM - Periodic registration update timer */ - nas::NasTimer t3516; /* MM - 5G AKA - RAND and RES* storing timer */ - nas::NasTimer t3517; /* MM - Service Request transmission timer */ - nas::NasTimer t3519; /* MM - Transmission with fresh SUCI timer */ - nas::NasTimer t3520; /* MM - ... */ - nas::NasTimer t3521; /* MM - De-registration transmission timer for not switch off */ - nas::NasTimer t3525; /* MM - ... */ - nas::NasTimer t3540; /* MM - ... */ + UeTimer t300; - nas::NasTimer t3584; /* SM - ... */ - nas::NasTimer t3585; /* SM - ... */ + RrcTimers(); +}; - UeTimers(); +struct NasTimers +{ + UeTimer t3346; /* MM - ... */ + UeTimer t3396; /* SM - ... */ + + UeTimer t3444; /* MM - ... */ + UeTimer t3445; /* MM - ... */ + + UeTimer t3502; /* MM - Initiation of the registration procedure, if still required */ + UeTimer t3510; /* MM - Registration Request transmission timer */ + UeTimer t3511; /* MM - Retransmission of the REGISTRATION REQUEST, if still required */ + UeTimer t3512; /* MM - Periodic registration update timer */ + UeTimer t3516; /* MM - 5G AKA - RAND and RES* storing timer */ + UeTimer t3517; /* MM - Service Request transmission timer */ + UeTimer t3519; /* MM - Transmission with fresh SUCI timer */ + UeTimer t3520; /* MM - ... */ + UeTimer t3521; /* MM - De-registration transmission timer for not switch off */ + UeTimer t3525; /* MM - ... */ + UeTimer t3540; /* MM - ... */ + + UeTimer t3584; /* SM - ... */ + UeTimer t3585; /* SM - ... */ + + NasTimers(); }; enum class ERmState @@ -167,7 +237,7 @@ enum class ECmState enum class E5UState { - U1_UPDATED, + U1_UPDATED = 0, U2_NOT_UPDATED, U3_ROAMING_NOT_ALLOWED }; @@ -199,9 +269,9 @@ enum class EMmState enum class EMmSubState { - MM_NULL_NA, + MM_NULL_PS, - MM_DEREGISTERED_NA, + MM_DEREGISTERED_PS, MM_DEREGISTERED_NORMAL_SERVICE, MM_DEREGISTERED_LIMITED_SERVICE, MM_DEREGISTERED_ATTEMPTING_REGISTRATION, @@ -211,9 +281,9 @@ enum class EMmSubState MM_DEREGISTERED_ECALL_INACTIVE, MM_DEREGISTERED_INITIAL_REGISTRATION_NEEDED, - MM_REGISTERED_INITIATED_NA, + MM_REGISTERED_INITIATED_PS, - MM_REGISTERED_NA, + MM_REGISTERED_PS, MM_REGISTERED_NORMAL_SERVICE, MM_REGISTERED_NON_ALLOWED_SERVICE, MM_REGISTERED_ATTEMPTING_REGISTRATION_UPDATE, @@ -222,9 +292,9 @@ enum class EMmSubState MM_REGISTERED_NO_CELL_AVAILABLE, MM_REGISTERED_UPDATE_NEEDED, - MM_DEREGISTERED_INITIATED_NA, + MM_DEREGISTERED_INITIATED_PS, - MM_SERVICE_REQUEST_INITIATED_NA + MM_SERVICE_REQUEST_INITIATED_PS }; enum class EPsState @@ -273,7 +343,7 @@ struct ProcedureTransaction static constexpr const int MAX_ID = 254; EPtState state{}; - std::unique_ptr timer{}; + std::unique_ptr timer{}; std::unique_ptr message{}; int psi{}; }; @@ -383,21 +453,8 @@ enum class EAutnValidationRes SYNCHRONISATION_FAILURE, }; -struct UePduSessionInfo -{ - int psi{}; - std::string type{}; - std::string address{}; - bool isEmergency{}; - bool uplinkPending{}; - std::optional apn{}; - std::optional sNssai{}; -}; - enum class ERegUpdateCause { - // unspecified cause - UNSPECIFIED, // when the UE detects entering a tracking area that is not in the list of tracking areas that the UE previously // registered in the AMF ENTER_UNLISTED_TRACKING_AREA, @@ -446,13 +503,17 @@ enum class ERegUpdateCause // belonging to an equivalent PLMN of the registered PLMN and not belonging to the registered PLMN; ENTER_EQUIVALENT_PLMN_CELL, // when the UE receives a SERVICE REJECT message with the 5GMM cause value set to #28 "Restricted service area". - RESTRICTED_SERVICE_AREA + RESTRICTED_SERVICE_AREA, + // ------ following are not specified by 24.501 ------ + TAI_CHANGE_IN_ATT_UPD, + PLMN_CHANGE_IN_ATT_UPD, + T3346_EXPIRY_IN_ATT_UPD, + T3502_EXPIRY_IN_ATT_UPD, + T3511_EXPIRY_IN_ATT_UPD, }; enum class EServiceReqCause { - // unspecified cause - UNSPECIFIED, // a) the UE, in 5GMM-IDLE mode over 3GPP access, receives a paging request from the network IDLE_PAGING, // b) the UE, in 5GMM-CONNECTED mode over 3GPP access, receives a notification from the network with access type @@ -481,16 +542,30 @@ enum class EServiceReqCause FALLBACK_INDICATION }; +enum class EProcRc +{ + OK, + CANCEL, + STAY, +}; + +struct ProcControl +{ + std::optional initialRegistration{}; + std::optional mobilityRegistration{}; + std::optional serviceRequest{}; + std::optional deregistration{}; +}; + Json ToJson(const ECmState &state); Json ToJson(const ERmState &state); Json ToJson(const EMmState &state); Json ToJson(const EMmSubState &state); Json ToJson(const E5UState &state); -Json ToJson(const UeConfig &v); -Json ToJson(const UeTimers &v); +Json ToJson(const NasTimers &v); Json ToJson(const ERegUpdateCause &v); Json ToJson(const EPsState &v); -Json ToJson(const UePduSessionInfo &v); Json ToJson(const EServiceReqCause &v); +Json ToJson(const ERrcState &v); } // namespace nr::ue diff --git a/src/ue/ue.cpp b/src/ue/ue.cpp index 47fb14d53..ea0d2d3f9 100644 --- a/src/ue/ue.cpp +++ b/src/ue/ue.cpp @@ -10,8 +10,8 @@ #include "app/task.hpp" #include "nas/task.hpp" -#include "rrc/task.hpp" #include "rls/task.hpp" +#include "rrc/task.hpp" namespace nr::ue { @@ -62,7 +62,7 @@ void UserEquipment::start() void UserEquipment::pushCommand(std::unique_ptr cmd, const InetAddress &address) { - taskBase->appTask->push(new NwUeCliCommand(std::move(cmd), address)); + taskBase->appTask->push(new NmUeCliCommand(std::move(cmd), address)); } } // namespace nr::ue diff --git a/src/utils/common_types.cpp b/src/utils/common_types.cpp index 3cfdace28..0cddc21a5 100644 --- a/src/utils/common_types.cpp +++ b/src/utils/common_types.cpp @@ -36,6 +36,9 @@ Json ToJson(const Supi &v) Json ToJson(const Plmn &v) { + if (!v.hasValue()) + return nullptr; + std::stringstream ss{}; ss << std::setfill('0') << std::setw(3) << v.mcc << "/"; ss << std::setfill('0') << std::setw(v.isLongMnc ? 3 : 2) << v.mnc; @@ -57,20 +60,18 @@ Json ToJson(const PlmnSupport &v) return Json::Obj({{"plmn", ToJson(v.plmn)}, {"nssai", ToJson(v.sliceSupportList)}}); } -Json ToJson(const EDeregCause &v) +Json ToJson(const ECellCategory &v) { switch (v) { - case EDeregCause::UNSPECIFIED: - return "NORMAL"; - case EDeregCause::SWITCH_OFF: - return "SWITCH-OFF"; - case EDeregCause::USIM_REMOVAL: - return "USIM-REMOVAL"; - case EDeregCause::DISABLE_5G: - return "DISABLE-5G"; - case EDeregCause::ECALL_INACTIVITY: - return "ECALL-INACTIVITY"; + case ECellCategory::BARRED_CELL: + return "BARRED"; + case ECellCategory::RESERVED_CELL: + return "RESERVED"; + case ECellCategory::ACCEPTABLE_CELL: + return "ACCEPTABLE"; + case ECellCategory::SUITABLE_CELL: + return "SUITABLE"; default: return "?"; } @@ -87,6 +88,11 @@ bool operator==(const SingleSlice &lhs, const SingleSlice &rhs) return ((int)*lhs.sd) == ((int)*rhs.sd); } +bool operator!=(const SingleSlice &lhs, const SingleSlice &rhs) +{ + return !(lhs == rhs); +} + bool operator==(const Plmn &lhs, const Plmn &rhs) { if (lhs.mcc != rhs.mcc) @@ -96,11 +102,86 @@ bool operator==(const Plmn &lhs, const Plmn &rhs) return lhs.isLongMnc == rhs.isLongMnc; } +bool operator!=(const Plmn &lhs, const Plmn &rhs) +{ + return !(lhs == rhs); +} + bool operator==(const GlobalNci &lhs, const GlobalNci &rhs) { return lhs.plmn == rhs.plmn && lhs.nci == rhs.nci; } +bool operator!=(const GlobalNci &lhs, const GlobalNci &rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const Tai &lhs, const Tai &rhs) +{ + return lhs.plmn == rhs.plmn && lhs.tac == rhs.tac; +} + +bool operator!=(const Tai &lhs, const Tai &rhs) +{ + return !(lhs == rhs); +} + +Json ToJson(const EDeregCause &v) +{ + switch (v) + { + case EDeregCause::NORMAL: + return "NORMAL"; + case EDeregCause::SWITCH_OFF: + return "SWITCH-OFF"; + case EDeregCause::USIM_REMOVAL: + return "USIM-REMOVAL"; + case EDeregCause::DISABLE_5G: + return "DISABLE-5G"; + case EDeregCause::ECALL_INACTIVITY: + return "ECALL-INACTIVITY"; + default: + return "?"; + } +} + +Json ToJson(const EInitialRegCause &v) +{ + switch (v) + { + case EInitialRegCause::EMERGENCY_SERVICES: + return "EMERGENCY-SERVICES"; + case EInitialRegCause::MM_DEREG_NORMAL_SERVICE: + return "MM-DEREG-NORMAL-SERVICE"; + case EInitialRegCause::T3346_EXPIRY: + return "T3346-EXPIRY"; + case EInitialRegCause::DUE_TO_DEREGISTRATION: + return "DUE-TO-DEREGISTRATION"; + case EInitialRegCause::DUE_TO_SERVICE_REJECT: + return "DUE-TO-SERVICE_REJECT"; + case EInitialRegCause::TAI_CHANGE_IN_ATT_REG: + return "TAI-CHANGE-IN-ATT-REG"; + case EInitialRegCause::PLMN_CHANGE_IN_ATT_REG: + return "PLMN-CHANGE-IN-ATT-REG"; + case EInitialRegCause::T3346_EXPIRY_IN_ATT_REG: + return "T3346-EXPIRY-IN-ATT-REG"; + case EInitialRegCause::T3502_EXPIRY_IN_ATT_REG: + return "T3502-EXPIRY-IN-ATT-REG"; + case EInitialRegCause::T3511_EXPIRY_IN_ATT_REG: + return "T3511-EXPIRY-IN-ATT-REG"; + default: + return "?"; + } +} + +Json ToJson(const Tai &v) +{ + if (!v.hasValue()) + return nullptr; + return "PLMN[" + ToJson(v.plmn).str() + "] TAC[" + std::to_string(v.tac) + "]"; +} + void NetworkSlice::addIfNotExists(const SingleSlice &slice) { if (!std::any_of(slices.begin(), slices.end(), [&slice](auto &s) { return s == slice; })) @@ -123,3 +204,33 @@ std::size_t std::hash::operator()(const GlobalNci &v) const noexcept utils::HashCombine(h, v.nci); return h; } + +std::size_t std::hash::operator()(const Tai &v) const noexcept +{ + std::size_t h = 0; + utils::HashCombine(h, v.plmn); + utils::HashCombine(h, v.tac); + return h; +} + +bool Plmn::hasValue() const +{ + return this->mcc != 0; +} + +Tai::Tai() : plmn{}, tac{} +{ +} + +Tai::Tai(const Plmn &plmn, int tac) : plmn{plmn}, tac{tac} +{ +} + +Tai::Tai(int mcc, int mnc, bool longMnc, int tac) : plmn{mcc, mnc, longMnc}, tac{tac} +{ +} + +bool Tai::hasValue() const +{ + return plmn.hasValue(); +} diff --git a/src/utils/common_types.hpp b/src/utils/common_types.hpp index f710f417b..642d43d98 100644 --- a/src/utils/common_types.hpp +++ b/src/utils/common_types.hpp @@ -30,6 +30,20 @@ struct Plmn int mcc{}; int mnc{}; bool isLongMnc{}; + + [[nodiscard]] bool hasValue() const; +}; + +struct Tai +{ + Plmn plmn; + int tac; + + Tai(); + Tai(const Plmn &plmn, int tac); + Tai(int mcc, int mnc, bool longMnc, int tac); + + [[nodiscard]] bool hasValue() const; }; struct SingleSlice @@ -113,7 +127,7 @@ struct Supi enum class EDeregCause { - UNSPECIFIED, + NORMAL, SWITCH_OFF, USIM_REMOVAL, DISABLE_5G, @@ -122,12 +136,16 @@ enum class EDeregCause enum class EInitialRegCause { - UNSPECIFIED, EMERGENCY_SERVICES, MM_DEREG_NORMAL_SERVICE, T3346_EXPIRY, DUE_TO_DEREGISTRATION, DUE_TO_SERVICE_REJECT, + TAI_CHANGE_IN_ATT_REG, + PLMN_CHANGE_IN_ATT_REG, + T3346_EXPIRY_IN_ATT_REG, + T3502_EXPIRY_IN_ATT_REG, + T3511_EXPIRY_IN_ATT_REG, }; struct GlobalNci @@ -144,31 +162,10 @@ struct GlobalNci enum class ECellCategory { - UNDEFINED, - ACCEPTABLE_CELL, - SUITABLE_CELL, BARRED_CELL, RESERVED_CELL, -}; - -struct UeCellMeasurement -{ - uint64_t sti{}; - GlobalNci cellId{}; - int tac{}; - int dbm{}; - std::string gnbName{}; - std::string linkIp{}; -}; - -struct UeCellInfo -{ - uint64_t sti{}; - GlobalNci cellId{}; - int tac{}; - ECellCategory cellCategory{}; - std::string gnbName{}; - std::string linkIp{}; + ACCEPTABLE_CELL, + SUITABLE_CELL, }; struct Vector3 @@ -184,16 +181,38 @@ struct Vector3 } }; -bool operator==(const SingleSlice &lhs, const SingleSlice &rhs); +struct UacAiBarringSet +{ + bool ai1 = false; + bool ai2 = false; + bool ai11 = false; + bool ai12 = false; + bool ai13 = false; + bool ai14 = false; + bool ai15 = false; +}; + bool operator==(const Plmn &lhs, const Plmn &rhs); +bool operator!=(const Plmn &lhs, const Plmn &rhs); + +bool operator==(const Tai &lhs, const Tai &rhs); +bool operator!=(const Tai &lhs, const Tai &rhs); + +bool operator==(const SingleSlice &lhs, const SingleSlice &rhs); +bool operator!=(const SingleSlice &lhs, const SingleSlice &rhs); + bool operator==(const GlobalNci &lhs, const GlobalNci &rhs); +bool operator!=(const GlobalNci &lhs, const GlobalNci &rhs); Json ToJson(const Supi &v); Json ToJson(const Plmn &v); +Json ToJson(const Tai &v); Json ToJson(const SingleSlice &v); Json ToJson(const NetworkSlice &v); Json ToJson(const PlmnSupport &v); Json ToJson(const EDeregCause &v); +Json ToJson(const ECellCategory &v); +Json ToJson(const EInitialRegCause &v); namespace std { @@ -204,6 +223,12 @@ struct hash std::size_t operator()(const Plmn &v) const noexcept; }; +template <> +struct hash +{ + std::size_t operator()(const Tai &v) const noexcept; +}; + template <> struct hash { diff --git a/src/utils/constants.hpp b/src/utils/constants.hpp index 380c29c96..94fb8d27a 100644 --- a/src/utils/constants.hpp +++ b/src/utils/constants.hpp @@ -14,11 +14,11 @@ struct cons { // Version information static constexpr const uint8_t Major = 3; - static constexpr const uint8_t Minor = 1; - static constexpr const uint8_t Patch = 9; + static constexpr const uint8_t Minor = 2; + static constexpr const uint8_t Patch = 0; static constexpr const char *Project = "UERANSIM"; - static constexpr const char *Tag = "v3.1.9"; - static constexpr const char *Name = "UERANSIM v3.1.9"; + static constexpr const char *Tag = "v3.2.0"; + static constexpr const char *Name = "UERANSIM v3.2.0"; static constexpr const char *Owner = "ALİ GÜNGÖR"; // Some port values diff --git a/src/utils/json.cpp b/src/utils/json.cpp index 30de77887..33560916c 100644 --- a/src/utils/json.cpp +++ b/src/utils/json.cpp @@ -161,7 +161,7 @@ Json::Json() : m_type{Type::NULL_TYPE} { } -Json::Json(nullptr_t) : m_type{Type::NULL_TYPE} +Json::Json(std::nullptr_t) : m_type{Type::NULL_TYPE} { } @@ -362,7 +362,7 @@ void Json::put(std::string key, Json value) m_children.emplace_back(std::move(key), std::move(value)); } -Json ToJson(nullptr_t) +Json ToJson(std::nullptr_t) { return nullptr; } diff --git a/src/utils/json.hpp b/src/utils/json.hpp index 4667ea1f0..cff6aa7f3 100644 --- a/src/utils/json.hpp +++ b/src/utils/json.hpp @@ -47,7 +47,7 @@ class Json public: Json(); - /* no-explicit */ Json(nullptr_t v); + /* no-explicit */ Json(std::nullptr_t v); /* no-explicit */ Json(std::string str); /* no-explicit */ Json(bool v); /* no-explicit */ Json(uint8_t v); @@ -105,7 +105,7 @@ class Json [[nodiscard]] std::string dumpYaml() const; }; -Json ToJson(nullptr_t); +Json ToJson(std::nullptr_t); Json ToJson(bool v); Json ToJson(const std::string &v); Json ToJson(uint8_t v); diff --git a/src/utils/light_sync.cpp b/src/utils/light_sync.cpp new file mode 100644 index 000000000..da2972944 --- /dev/null +++ b/src/utils/light_sync.cpp @@ -0,0 +1,9 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "light_sync.hpp" diff --git a/src/utils/light_sync.hpp b/src/utils/light_sync.hpp new file mode 100644 index 000000000..837df20e1 --- /dev/null +++ b/src/utils/light_sync.hpp @@ -0,0 +1,81 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "common.hpp" + +#include +#include +#include + +template +class LightSync +{ + private: + const int64_t m_validUntilForProducer; + const int64_t m_validUntilForConsumer; + const std::unique_ptr m_input; + std::atomic_bool m_processed; + std::shared_ptr m_output; + + public: + LightSync(int validForMs, int estimatedProcessMs, std::unique_ptr &&input) + : m_validUntilForProducer{utils::CurrentTimeMillis() + static_cast(validForMs)}, + m_validUntilForConsumer{utils::CurrentTimeMillis() + static_cast(validForMs + estimatedProcessMs)}, + m_input{std::move(input)}, m_processed{}, m_output{} + { + if (validForMs >= 2500) + throw std::runtime_error("LightSync timeout is too large"); + if (estimatedProcessMs >= 50) + throw std::runtime_error("LightSync estimated process time is too large"); + } + + private: + bool isExpiredForConsumer() + { + return utils::CurrentTimeMillis() > m_validUntilForConsumer; + } + + public: + const TInput &input() + { + return *m_input; + } + + bool isExpiredForProducer() + { + return utils::CurrentTimeMillis() > m_validUntilForProducer; + } + + // Only producer can call at most once + void notifyProcessed(std::unique_ptr &&output) + { + if (m_processed) + throw std::runtime_error("LightSync processed more than once"); + m_output = std::move(output); + m_processed = true; + } + + // Only consumer can call at most once + std::shared_ptr waitForProcess() + { + while (true) + { + if (isExpiredForConsumer()) + return nullptr; + + if (m_processed) + return m_output; + } + } + + static std::shared_ptr> MakeShared(int validForMs, int estimatedProcessMs, + std::unique_ptr &&input) + { + return std::make_shared>(validForMs, estimatedProcessMs, std::move(input)); + } +}; \ No newline at end of file diff --git a/src/utils/locked.cpp b/src/utils/locked.cpp new file mode 100644 index 000000000..5d6ec14fa --- /dev/null +++ b/src/utils/locked.cpp @@ -0,0 +1,9 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "locked.hpp" \ No newline at end of file diff --git a/src/utils/locked.hpp b/src/utils/locked.hpp new file mode 100644 index 000000000..b3bc93bd4 --- /dev/null +++ b/src/utils/locked.hpp @@ -0,0 +1,77 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include + +template +class Locked +{ + private: + T m_value; + std::recursive_mutex m_mutex; + + public: + Locked() : m_value{}, m_mutex{} + { + } + + explicit Locked(T value) : m_value{value}, m_mutex{} + { + } + + static_assert(!std::is_reference::value); + + Locked(const T &) = delete; + Locked(T &&) = delete; + + Locked &operator=(const Locked &) = delete; + Locked &operator=(Locked &&) = delete; + + template + inline void access(Func &&fun) + { + // Şimdilik access ve mutate aynı, optimizasyon adına read-write lock kullanılabilir + + std::lock_guard lk(m_mutex); + fun((const T &)m_value); + } + + template + inline void mutate(Func &&fun) + { + // Şimdilik access ve mutate aynı, optimizasyon adına read-write lock kullanılabilir + + std::lock_guard lk(m_mutex); + fun(m_value); + } + + inline T get() + { + T copy{}; + access([©](const auto &value) { copy = value; }); + return copy; + } + + template + inline U get(Func &&fun) + { + U copy{}; + access([©, &fun](const auto &value) { copy = fun(value); }); + return copy; + } + + inline void set(const T &value) + { + mutate([&value](auto &v) { v = value; }); + } + + inline void set(T &&value) + { + mutate([&value](auto &v) { v = std::move(value); }); + } +}; \ No newline at end of file diff --git a/src/utils/network.cpp b/src/utils/network.cpp index 8b9cd0c5e..efdcfb5d9 100644 --- a/src/utils/network.cpp +++ b/src/utils/network.cpp @@ -174,12 +174,12 @@ int Socket::receive(uint8_t *buffer, size_t bufferSize, int timeoutMs, InetAddre sockaddr_storage peerAddr{}; socklen_t peerAddrLen = sizeof(struct sockaddr_storage); - rc = recvfrom(fd, buffer, bufferSize, 0, (struct sockaddr *)&peerAddr, &peerAddrLen); - if (rc == -1) + auto r = recvfrom(fd, buffer, bufferSize, 0, (struct sockaddr *)&peerAddr, &peerAddrLen); + if (r == -1) throw LibError("recvfrom recv failed: ", errno); outAddress = InetAddress{peerAddr, peerAddrLen}; - return rc; + return static_cast(r); } return 0; diff --git a/src/utils/nts.cpp b/src/utils/nts.cpp index 98c6c3e23..498379df0 100644 --- a/src/utils/nts.cpp +++ b/src/utils/nts.cpp @@ -16,7 +16,7 @@ static NtsMessage *TimerExpiredMessage(TimerInfo *timerInfo) { - return timerInfo ? new NwTimerExpired(timerInfo->timerId) : nullptr; + return timerInfo ? new NmTimerExpired(timerInfo->timerId) : nullptr; } void TimerBase::setTimerAbsolute(int timerId, int64_t timeMs) diff --git a/src/utils/nts.hpp b/src/utils/nts.hpp index 85bbbc211..405306239 100644 --- a/src/utils/nts.hpp +++ b/src/utils/nts.hpp @@ -39,22 +39,25 @@ enum class NtsMessageType GNB_RLS_TO_GTP, GNB_GTP_TO_RLS, GNB_RRC_TO_RLS, + GNB_RLS_TO_RLS, GNB_NGAP_TO_RRC, GNB_RRC_TO_NGAP, GNB_NGAP_TO_GTP, GNB_SCTP, - UE_APP_TO_RLS, UE_APP_TO_TUN, UE_APP_TO_NAS, UE_TUN_TO_APP, UE_RRC_TO_NAS, UE_NAS_TO_RRC, - UE_RRC_TO_RLS, + UE_RRC_TO_RLS, + UE_RRC_TO_RRC, UE_NAS_TO_NAS, UE_RLS_TO_RRC, - UE_RLS_TO_APP, - UE_NAS_TO_APP, + UE_RLS_TO_NAS, + UE_RLS_TO_RLS, + UE_NAS_TO_APP, + UE_NAS_TO_RLS, }; struct NtsMessage @@ -68,11 +71,11 @@ struct NtsMessage virtual ~NtsMessage() = default; }; -struct NwTimerExpired : NtsMessage +struct NmTimerExpired : NtsMessage { int timerId; - explicit NwTimerExpired(int timerId) : NtsMessage(NtsMessageType::TIMER_EXPIRED), timerId(timerId) + explicit NmTimerExpired(int timerId) : NtsMessage(NtsMessageType::TIMER_EXPIRED), timerId(timerId) { } }; diff --git a/src/utils/octet_string.hpp b/src/utils/octet_string.hpp index b40179cf4..f1ff225b1 100644 --- a/src/utils/octet_string.hpp +++ b/src/utils/octet_string.hpp @@ -15,7 +15,6 @@ #include #include -// TODO: implement MemoryBlock and use it for decoders etc. instead of this. class OctetString { private: diff --git a/tools/rls-wireshark-dissector.lua b/tools/rls-wireshark-dissector.lua index 9b1109db1..2f2f8560b 100644 --- a/tools/rls-wireshark-dissector.lua +++ b/tools/rls-wireshark-dissector.lua @@ -1,138 +1,114 @@ --[[ +-- -- Dissector for Radio Link Simulation Protocol --- (used by UERANSIM ). +-- (UERANSIM project ). -- -- CC0-1.0 2021 - Louis Royer () +-- --]] ---[[ --- ProtoFields ---]] -local rls_protocol = Proto("RLS", "Radio Link Simulation Protocol") -local version_major = ProtoField.uint8("rls.version_major", "RLS Version Major", base.DEC) -local version_minor = ProtoField.uint8("rls.version_minor", "RLS Version Minor", base.DEC) -local version_patch = ProtoField.uint8("rls.version_patch", "RLS Version Patch", base.DEC) +local rlsProtocol = Proto("RLS", "UERANSIM Radio Link Simulation (RLS) Protocol") +local fields = rlsProtocol.fields -local message_type_name = { - [0] = "Reserved", - [1] = "Cell Info Request", - [2] = "Cell Info Response", - [3] = "PDU Delivery" +local msgTypeNames = { + [0] = "[Reserved]", + [1] = "[Reserved]", + [2] = "[Reserved]", + [3] = "[Reserved]", + [4] = "Heartbeat", + [5] = "Heartbeat ACK", + [6] = "PDU Transmission", + [7] = "PDU Transmission ACK", } -local message_type = ProtoField.uint8("rls.message_type", "RLS Message Type", base.DEC, message_type_name) -local sti = ProtoField.uint64("rls.sti", "RLS Temporary Identifier", base.HEX) - --- For cell Info Request -local sim_pos_x = ProtoField.uint32("rls.sim_pos_x", "RLS Position X", base.DEC) -local sim_pos_y = ProtoField.uint32("rls.sim_pos_y", "RLS Position Y", base.DEC) -local sim_pos_z = ProtoField.uint32("rls.sim_pos_z", "RLS Position Z", base.DEC) +local pduTypeNames = { + [0] = "[Reserved]", + [1] = "RRC", + [2] = "Data" +} --- For cell Info Response -local mcc = ProtoField.uint16("rls.mcc", "MCC", base.DEC) -local mnc = ProtoField.uint16("rls.mnc", "MNC", base.DEC) -local long_mnc = ProtoField.bool("rls.long_mnc", "MNC is 3-digit", base.BOOL) -local nci = ProtoField.uint64("rls.nci", "NR Cell Identity", base.HEX) -local tac = ProtoField.uint32("rls.tac", "Tracking Area Code", base.DEC) -local dbm = ProtoField.int32("rls.dbm", "RLS Signal Strength (dBm)", base.DEC) -local gnb_name = ProtoField.string("rls.gnb_name", "gNB Name") -local link_ip = ProtoField.string("rls.link_ip", "gNB Link IP") +local rrcMsgTypeNames = { + [0] = "BCCH-BCH", + [1] = "BCCH-DL-SCH", + [2] = "DL-CCCH", + [3] = "DL-DCCH", + [4] = "PCCH", + [5] = "UL-CCCH", + [6] = "UL-CCCH1", + [7] = "UL-DCCH", +} --- For PDU Delivery -local pdu_type_name = { - [0] = "Reserved", - [1] = "RRC", - [2] = "Data" +local nrRrcDissectors = { + [0] = "nr-rrc.bcch.bch", + [1] = "nr-rrc.bcch.dl.sch", + [2] = "nr-rrc.dl.ccch", + [3] = "nr-rrc.dl.dcch", + [4] = "nr-rrc.pcch", + [5] = "nr-rrc.ul.ccch", + [6] = "nr-rrc.ul.ccch1", + [7] = "nr-rrc.ul.dcch", } -local pdu_type = ProtoField.uint8("rls.pdu_type", "RLS PDU Type", base.DEC, pdu_type_name) +fields.Version = ProtoField.string("rls.version", "Version") +fields.MsgType = ProtoField.uint8("rls.message_type", "Message Type", base.DEC, msgTypeNames) +fields.Sti = ProtoField.uint64("rls.sti", "Sender Node Temporary ID", base.DEC) +fields.PduType = ProtoField.uint8("rls.pdu_type", "PDU Type", base.DEC, pduTypeNames) +fields.PduId = ProtoField.uint32("rls.pdu_id", "PDU ID", base.DEC) +fields.RrcMsgType = ProtoField.uint32("rls.rrc_message_type", "RRC Message Type", base.DEC, rrcMsgTypeNames) +fields.PduLength = ProtoField.uint32("rls.pdu_length", "PDU Length", base.DEC) +fields.PduSessionId = ProtoField.uint32("rls.pdu_session_id", "PDU Session ID", base.DEC) +fields.AcknowledgeItem = ProtoField.uint32("rls.ack_item", "PDU ID") +fields.Dbm = ProtoField.int32("rls.dbm", "RLS Signal Strength (dBm)", base.DEC) +fields.PosX = ProtoField.uint32("rls.pos_x", "RLS Position X", base.DEC) +fields.PosY = ProtoField.uint32("rls.pos_y", "RLS Position Y", base.DEC) +fields.PosZ = ProtoField.uint32("rls.pos_z", "RLS Position Z", base.DEC) -local rrc_channel_name = { - [0] = "BCCH-BCH", - [1] = "BCCH-DL-SCH", - [2] = "DL-CCCH", - [3] = "DL-DCCH", - [4] = "PCCH", - [5] = "UL-CCCH", - [6] = "UL-CCCH1", - [7] = "UL-DCCH", -} +function rlsProtocol.dissector(buffer, pinfo, tree) + if buffer:len() == 0 then return end + if buffer(0, 1):uint() ~= 0x03 then return end -local rrc_channel_dissector = { - [0] = "nr-rrc.bcch.bch", - [1] = "nr-rrc.bcch.dl.sch", - [2] = "nr-rrc.dl.ccch", - [3] = "nr-rrc.dl.dcch", - [4] = "nr-rrc.pcch", - [5] = "nr-rrc.ul.ccch", - [6] = "nr-rrc.ul.ccch1", - [7] = "nr-rrc.ul.dcch", -} + pinfo.cols.protocol = rlsProtocol.name -local rrc_channel = ProtoField.uint32("rls.rrc_channel", "RRC Channel", base.DEC, rrc_channel_name) -local session_id = ProtoField.uint32("rls.session_id", "PDU Session ID", base.DEC) + local versionNumber = buffer(1, 1):uint() .. "." .. buffer(2, 1):uint() .. "." .. buffer(3, 1):uint() + local subtree = tree:add(rlsProtocol, buffer(), "UERANSIM Radio Link Simulation (RLS) protocol") ---[[ --- Dissector definition ---]] -rls_protocol.fields = { - version_major, version_minor, version_patch, message_type, sti, - sim_pos_x, sim_pos_y, sim_pos_z, - mcc, mnc, long_mnc, nci, tac, dbm, gnb_name, link_ip, - pdu_type, rrc_channel, session_id, -} + subtree:add(fields.Version, buffer(1, 3), versionNumber) + subtree:add(fields.MsgType, buffer(4, 1)) + local msgType = buffer(4, 1):uint() -function rls_protocol.dissector(buffer, pinfo, tree) - local length = buffer:len() - if length == 0 then return end - if buffer(0,1):uint() ~= 0x03 then return end + pinfo.cols.info = msgTypeNames[msgType] + subtree:add(fields.Sti, buffer(5, 8)) - pinfo.cols.protocol = rls_protocol.name - local version_number = buffer(1,1):uint().."." - ..buffer(2,1):uint().."." - ..buffer(3,1):uint() - local subtree = tree:add(rls_protocol, buffer(), "RLS Protocol Version "..version_number) - local version = subtree:add(rls_protocol, buffer(2,3), "Version: "..version_number) - version:add(version_major, buffer(1,1)) - version:add(version_minor, buffer(2,1)) - version:add(version_patch, buffer(3,1)) - subtree:add(message_type, buffer(4,1)) - local msg_type = buffer(4,1):uint() - if msg_type <=0 or msg_type > 3 then return end - pinfo.cols.info = message_type_name[msg_type] - subtree:append_text(" - "..message_type_name[msg_type]) - subtree:add(sti, buffer(5,8)) - if msg_type == 1 then -- Cell Info Request - subtree:add(sim_pos_x, buffer(13,4)) - subtree:add(sim_pos_y, buffer(17,4)) - subtree:add(sim_pos_z, buffer(21,4)) - elseif msg_type == 2 then -- Cell Info Response - subtree:add(mcc, buffer(13,2)) - local mnc_tree = subtree:add(rls_protocol, buffer(15,3), "MNC: "..tostring(buffer(15,2):uint())) - mnc_tree:add(mnc, buffer(15,2)) - mnc_tree:add(long_mnc, buffer(17,1)) - subtree:add(nci, buffer(18,8)) - subtree:add(tac, buffer(26,4)) - subtree:add(dbm, buffer(30,4)) - local gnb_name_len = buffer(34,4):uint() - subtree:add(gnb_name, buffer(38,gnb_name_len)) - local link_ip_size = buffer(38+gnb_name_len,4):uint() - subtree:add(link_ip, buffer(42+gnb_name_len,link_ip_size)) - elseif msg_type == 3 then -- PDU Delivery - subtree:add(pdu_type, buffer(13,1)) - local pdu_type_value = buffer(13,1):uint() - local pdu_len = buffer(14,4):uint() - local payload_len = buffer(18+pdu_len,4):uint() - if pdu_type_value == 1 then -- RRC - subtree:add(rrc_channel, buffer(22+pdu_len,payload_len)) - local channel = buffer(22+pdu_len,payload_len):uint() - Dissector.get(rrc_channel_dissector[channel]):call(buffer(18,pdu_len):tvb(), pinfo, tree) - elseif pdu_type_value == 2 then -- DATA - subtree:add(session_id, buffer(22+pdu_len,payload_len)) - Dissector.get("ip"):call(buffer(18,pdu_len):tvb(), pinfo, tree) - end - end + if msgType == 4 then -- Heartbeat + subtree:add(fields.PosX, buffer(13,4)) + subtree:add(fields.PosY, buffer(17,4)) + subtree:add(fields.PosZ, buffer(21,4)) + elseif msgType == 5 then -- Heartbeat ACK + subtree:add(fields.Dbm, buffer(13,4)) + elseif msgType == 6 then -- PDU Transmission + local pduType = buffer(13, 1):uint() + subtree:add(fields.PduType, buffer(13, 1)) + subtree:add(fields.PduId, buffer(14, 4)) + if pduType == 1 then -- RRC PDU + local rrcMsgType = buffer(18, 4):uint() + local pduLength = buffer(22, 4):uint() + subtree:add(fields.RrcMsgType, buffer(18, 4)) + subtree:add(fields.PduLength, buffer(22, 4)) + Dissector.get(nrRrcDissectors[rrcMsgType]):call(buffer(26, pduLength):tvb(), pinfo, tree) + elseif (pduType == 2) then -- Data PDU + subtree:add(fields.PduSessionId, buffer(18, 4)) + local pduLength = buffer(22, 4):uint() + subtree:add(fields.PduLength, buffer(22, 4)) + Dissector.get("ip"):call(buffer(26, pduLength):tvb(), pinfo, tree) + end + elseif msgType == 7 then -- PDU Transmission ACK + local ackCount = buffer(13, 4):uint() + local ackArray = subtree:add(rlsProtocol, buffer(13, 4), "Acknowledge List (" .. ackCount .. ")") + for i = 1,ackCount,1 do + ackArray:add(fields.AcknowledgeItem, buffer(17 + (i - 1) * 4, 4)) + end + end end local udp_port = DissectorTable.get("udp.port") -udp_port:add(4997, rls_protocol) +udp_port:add(4997, rlsProtocol)