diff --git a/README.md b/README.md index 997f6a63b..29e44673c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- +

diff --git a/config/open5gs-gnb.yaml b/config/open5gs-gnb.yaml index 88e210dfe..d825a80c6 100644 --- a/config/open5gs-gnb.yaml +++ b/config/open5gs-gnb.yaml @@ -17,7 +17,6 @@ amfConfigs: # List of supported S-NSSAIs by this gNB slices: - sst: 1 - sd: 1 # Indicates whether or not SCTP stream number errors should be ignored. ignoreStreamIds: true diff --git a/config/open5gs-ue.yaml b/config/open5gs-ue.yaml index 7c1761793..d6779ed65 100644 --- a/config/open5gs-ue.yaml +++ b/config/open5gs-ue.yaml @@ -34,7 +34,6 @@ sessions: # Configured NSSAI for this UE by HPLMN configured-nssai: - sst: 1 - sd: 1 # Default Configured NSSAI for this UE default-nssai: diff --git a/src/app/cli_cmd.cpp b/src/app/cli_cmd.cpp index 5a1213c4b..f8ec97502 100644 --- a/src/app/cli_cmd.cpp +++ b/src/app/cli_cmd.cpp @@ -150,6 +150,7 @@ static OrderedMap g_gnbCmdEntries = { {"amf-info", {"Show some status information about the given AMF", "", DefaultDesc, true}}, {"ue-list", {"List all UEs associated with the gNB", "", DefaultDesc, false}}, {"ue-count", {"Print the total number of UEs connected the this gNB", "", DefaultDesc, false}}, + {"ue-release", {"Request a UE context release for the given UE", "", DefaultDesc, false}}, }; static OrderedMap g_ueCmdEntries = { @@ -162,6 +163,7 @@ static OrderedMap g_ueCmdEntries = { {"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, @@ -199,6 +201,18 @@ static std::unique_ptr GnbCliParseImpl(const std::string &subCmd, { return std::make_unique(GnbCliCommand::UE_COUNT); } + else if (subCmd == "ue-release") + { + auto cmd = std::make_unique(GnbCliCommand::UE_RELEASE_REQ); + if (options.positionalCount() == 0) + CMD_ERR("UE ID is expected") + if (options.positionalCount() > 1) + CMD_ERR("Only one UE ID is expected") + cmd->ueId = utils::ParseInt(options.getPositional(0)); + if (cmd->ueId <= 0) + CMD_ERR("Invalid UE ID") + return cmd; + } return nullptr; } @@ -301,6 +315,10 @@ static std::unique_ptr UeCliParseImpl(const std::string &subCmd, c } return cmd; } + else if (subCmd == "coverage") + { + return std::make_unique(UeCliCommand::COVERAGE); + } return nullptr; } diff --git a/src/app/cli_cmd.hpp b/src/app/cli_cmd.hpp index 45b7d2e96..b980c675a 100644 --- a/src/app/cli_cmd.hpp +++ b/src/app/cli_cmd.hpp @@ -26,12 +26,16 @@ struct GnbCliCommand AMF_LIST, AMF_INFO, UE_LIST, - UE_COUNT + UE_COUNT, + UE_RELEASE_REQ, } present; // AMF_INFO int amfId{}; + // UE_RELEASE_REQ + int ueId{}; + explicit GnbCliCommand(PR present) : present(present) { } @@ -48,6 +52,7 @@ struct UeCliCommand PS_RELEASE, PS_RELEASE_ALL, DE_REGISTER, + COVERAGE, } present; // DE_REGISTER diff --git a/src/asn/utils/utils.hpp b/src/asn/utils/utils.hpp index 8bd4c23c4..22cef52ba 100644 --- a/src/asn/utils/utils.hpp +++ b/src/asn/utils/utils.hpp @@ -198,4 +198,12 @@ inline Unique WrapUnique(T *ptr, asn_TYPE_descriptor_t &desc) return asn::Unique(ptr, asn::Deleter{desc}); } +template +inline Unique UniqueCopy(const T &value, asn_TYPE_descriptor_t &desc) +{ + auto *ptr = New(); + DeepCopy(desc, value, ptr); + return WrapUnique(ptr, desc); +} + } // namespace asn \ No newline at end of file diff --git a/src/gnb/app/cmd_handler.cpp b/src/gnb/app/cmd_handler.cpp index 995f8576e..a3b94ccde 100644 --- a/src/gnb/app/cmd_handler.cpp +++ b/src/gnb/app/cmd_handler.cpp @@ -10,8 +10,8 @@ #include #include -#include #include +#include #include #include #include @@ -36,7 +36,7 @@ void GnbCmdHandler::sendError(const InetAddress &address, const std::string &out void GnbCmdHandler::pauseTasks() { m_base->gtpTask->requestPause(); - m_base->mrTask->requestPause(); + m_base->rlsTask->requestPause(); m_base->ngapTask->requestPause(); m_base->rrcTask->requestPause(); m_base->sctpTask->requestPause(); @@ -45,7 +45,7 @@ void GnbCmdHandler::pauseTasks() void GnbCmdHandler::unpauseTasks() { m_base->gtpTask->requestUnpause(); - m_base->mrTask->requestUnpause(); + m_base->rlsTask->requestUnpause(); m_base->ngapTask->requestUnpause(); m_base->rrcTask->requestUnpause(); m_base->sctpTask->requestUnpause(); @@ -55,7 +55,7 @@ bool GnbCmdHandler::isAllPaused() { if (!m_base->gtpTask->isPauseConfirmed()) return false; - if (!m_base->mrTask->isPauseConfirmed()) + if (!m_base->rlsTask->isPauseConfirmed()) return false; if (!m_base->ngapTask->isPauseConfirmed()) return false; @@ -131,7 +131,7 @@ void GnbCmdHandler::handleCmdImpl(NwGnbCliCommand &msg) for (auto &ue : m_base->ngapTask->m_ueCtx) { json.push(Json::Obj({ - {"ue-name", m_base->mrTask->m_ueMap[ue.first].name}, + {"ue-id", ue.first}, {"ran-ngap-id", ue.second->ranUeNgapId}, {"amf-ngap-id", ue.second->amfUeNgapId}, })); @@ -143,6 +143,17 @@ void GnbCmdHandler::handleCmdImpl(NwGnbCliCommand &msg) sendResult(msg.address, std::to_string(m_base->ngapTask->m_ueCtx.size())); break; } + case app::GnbCliCommand::UE_RELEASE_REQ: { + if (m_base->ngapTask->m_ueCtx.count(msg.cmd->ueId) == 0) + sendError(msg.address, "UE not found with given ID"); + else + { + auto ue = m_base->ngapTask->m_ueCtx[msg.cmd->ueId]; + m_base->ngapTask->sendContextRelease(ue->ctxId, NgapCause::RadioNetwork_unspecified); + sendResult(msg.address, "Requesting UE context release"); + } + break; + } } } diff --git a/src/gnb/gnb.cpp b/src/gnb/gnb.cpp index a277bc437..d63330db0 100644 --- a/src/gnb/gnb.cpp +++ b/src/gnb/gnb.cpp @@ -9,13 +9,12 @@ #include "gnb.hpp" #include "app/task.hpp" #include "gtp/task.hpp" -#include "mr/task.hpp" +#include "rls/task.hpp" #include "ngap/task.hpp" #include "rrc/task.hpp" #include "sctp/task.hpp" #include -#include namespace nr::gnb { @@ -33,7 +32,7 @@ GNodeB::GNodeB(GnbConfig *config, app::INodeListener *nodeListener, NtsTask *cli base->ngapTask = new NgapTask(base); base->rrcTask = new GnbRrcTask(base); base->gtpTask = new GtpTask(base); - base->mrTask = new GnbMrTask(base); + base->rlsTask = new GnbRlsTask(base); taskBase = base; } @@ -45,14 +44,14 @@ GNodeB::~GNodeB() taskBase->ngapTask->quit(); taskBase->rrcTask->quit(); taskBase->gtpTask->quit(); - taskBase->mrTask->quit(); + taskBase->rlsTask->quit(); delete taskBase->appTask; delete taskBase->sctpTask; delete taskBase->ngapTask; delete taskBase->rrcTask; delete taskBase->gtpTask; - delete taskBase->mrTask; + delete taskBase->rlsTask; delete taskBase->logBase; @@ -65,7 +64,7 @@ void GNodeB::start() taskBase->sctpTask->start(); taskBase->ngapTask->start(); taskBase->rrcTask->start(); - taskBase->mrTask->start(); + taskBase->rlsTask->start(); taskBase->gtpTask->start(); } diff --git a/src/gnb/gtp/task.cpp b/src/gnb/gtp/task.cpp index e6557e3d0..a061b14f1 100644 --- a/src/gnb/gtp/task.cpp +++ b/src/gnb/gtp/task.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include @@ -76,12 +76,12 @@ void GtpTask::onLoop() } break; } - case NtsMessageType::GNB_MR_TO_GTP: { - auto *w = dynamic_cast(msg); + case NtsMessageType::GNB_RLS_TO_GTP: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbMrToGtp::UPLINK_DELIVERY: { - handleUplinkData(w->ueId, w->pduSessionId, std::move(w->data)); + case NwGnbRlsToGtp::DATA_PDU_DELIVERY: { + handleUplinkData(w->ueId, w->psi, std::move(w->pdu)); break; } } @@ -239,11 +239,11 @@ void GtpTask::handleUdpReceive(const udp::NwUdpServerReceive &msg) if (m_rateLimiter->allowDownlinkPacket(sessionInd, gtp->payload.length())) { - auto *w = new NwGnbGtpToMr(NwGnbGtpToMr::DATA_PDU_DELIVERY); + auto *w = new NwGnbGtpToRls(NwGnbGtpToRls::DATA_PDU_DELIVERY); w->ueId = GetUeId(sessionInd); - w->pduSessionId = GetPsi(sessionInd); - w->data = std::move(gtp->payload); - m_base->mrTask->push(w); + w->psi = GetPsi(sessionInd); + w->pdu = std::move(gtp->payload); + m_base->rlsTask->push(w); } delete gtp; diff --git a/src/gnb/mr/rls.cpp b/src/gnb/mr/rls.cpp deleted file mode 100644 index 36aa37aa4..000000000 --- a/src/gnb/mr/rls.cpp +++ /dev/null @@ -1,63 +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 "rls.hpp" -#include - -namespace nr::gnb -{ - -GnbRls::GnbRls(std::string nodeName, std::unique_ptr logger, NtsTask *targetTask) - : RlsGnbEntity(std::move(nodeName)), m_logger(std::move(logger)), m_targetTask(targetTask) -{ -} - -void GnbRls::logWarn(const std::string &msg) -{ - m_logger->warn(msg); -} - -void GnbRls::logError(const std::string &msg) -{ - m_logger->err(msg); -} - -void GnbRls::onUeConnected(int ue, std::string name) -{ - auto *w = new NwGnbMrToMr(NwGnbMrToMr::UE_CONNECTED); - w->ue = ue; - w->name = std::move(name); - m_targetTask->push(w); -} - -void GnbRls::onUeReleased(int ue, rls::ECause cause) -{ - auto *w = new NwGnbMrToMr(NwGnbMrToMr::UE_RELEASED); - w->ue = ue; - w->cause = cause; - m_targetTask->push(w); -} - -void GnbRls::deliverUplinkPayload(int ue, rls::EPayloadType type, OctetString &&payload) -{ - auto *w = new NwGnbMrToMr(NwGnbMrToMr::RECEIVE_OVER_UDP); - w->ue = ue; - w->type = type; - w->pdu = std::move(payload); - m_targetTask->push(w); -} - -void GnbRls::sendRlsPdu(const InetAddress &address, OctetString &&pdu) -{ - auto *w = new NwGnbMrToMr(NwGnbMrToMr::SEND_OVER_UDP); - w->address = address; - w->pdu = std::move(pdu); - m_targetTask->push(w); -} - -} // namespace nr::gnb diff --git a/src/gnb/mr/rls.hpp b/src/gnb/mr/rls.hpp deleted file mode 100644 index 823d3451c..000000000 --- a/src/gnb/mr/rls.hpp +++ /dev/null @@ -1,36 +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. -// - -#pragma once - -#include -#include -#include - -namespace nr::gnb -{ - -class GnbRls : public rls::RlsGnbEntity -{ - private: - std::unique_ptr m_logger; - NtsTask *m_targetTask; - - public: - explicit GnbRls(std::string nodeName, std::unique_ptr logger, NtsTask *targetTask); - - protected: - void logWarn(const std::string &msg) override; - void logError(const std::string &msg) override; - void onUeConnected(int ue, std::string name) override; - void onUeReleased(int ue, rls::ECause cause) override; - void sendRlsPdu(const InetAddress &address, OctetString &&pdu) override; - void deliverUplinkPayload(int ue, rls::EPayloadType type, OctetString &&payload) override; -}; - -} // namespace nr::gnb diff --git a/src/gnb/mr/task.cpp b/src/gnb/mr/task.cpp deleted file mode 100644 index 4e7d54f56..000000000 --- a/src/gnb/mr/task.cpp +++ /dev/null @@ -1,193 +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 "rls.hpp" -#include -#include -#include -#include -#include - -static const int TIMER_ID_RLS_HEARTBEAT = 1; - -namespace nr::gnb -{ - -GnbMrTask::GnbMrTask(TaskBase *base) : m_base{base}, m_udpTask{}, m_rlsEntity{}, m_ueMap{} -{ - m_logger = m_base->logBase->makeUniqueLogger("mr"); -} - -void GnbMrTask::onStart() -{ - m_rlsEntity = new GnbRls(m_base->config->name, m_base->logBase->makeUniqueLogger("rls"), this); - - try - { - m_udpTask = new udp::UdpServerTask(m_base->config->portalIp, cons::PortalPort, this); - m_udpTask->start(); - } - catch (const LibError &e) - { - m_logger->err("MR failure [%s]", e.what()); - quit(); - return; - } - - setTimer(TIMER_ID_RLS_HEARTBEAT, rls::Constants::HB_PERIOD_UE_TO_GNB); -} - -void GnbMrTask::onLoop() -{ - NtsMessage *msg = take(); - if (!msg) - return; - - switch (msg->msgType) - { - case NtsMessageType::GNB_MR_TO_MR: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwGnbMrToMr::UE_CONNECTED: { - onUeConnected(w->ue, w->name); - break; - } - case NwGnbMrToMr::UE_RELEASED: { - onUeReleased(w->ue, w->cause); - break; - } - case NwGnbMrToMr::SEND_OVER_UDP: { - m_udpTask->send(w->address, w->pdu); - break; - } - case NwGnbMrToMr::RECEIVE_OVER_UDP: { - receiveUplinkPayload(w->ue, w->type, std::move(w->pdu)); - break; - } - } - break; - } - case NtsMessageType::GNB_GTP_TO_MR: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwGnbGtpToMr::DATA_PDU_DELIVERY: { - OctetString stream{}; - stream.appendOctet4(static_cast(w->pduSessionId)); - stream.append(w->data); - - m_rlsEntity->downlinkPayloadDelivery(w->ueId, rls::EPayloadType::DATA, std::move(stream)); - break; - } - } - break; - } - case NtsMessageType::GNB_RRC_TO_MR: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwGnbRrcToMr::RRC_PDU_DELIVERY: { - OctetString stream{}; - stream.appendOctet(static_cast(w->channel)); - stream.append(w->pdu); - - m_rlsEntity->downlinkPayloadDelivery(w->ueId, rls::EPayloadType::RRC, std::move(stream)); - break; - } - case NwGnbRrcToMr::NGAP_LAYER_INITIALIZED: { - m_rlsEntity->setAcceptConnections(true); - break; - } - case NwGnbRrcToMr::AN_RELEASE: { - m_rlsEntity->localReleaseConnection(w->ueId, rls::ECause::RRC_NORMAL_RELEASE); - break; - } - } - break; - } - case NtsMessageType::TIMER_EXPIRED: { - auto *w = dynamic_cast(msg); - if (w->timerId == TIMER_ID_RLS_HEARTBEAT) - { - setTimer(TIMER_ID_RLS_HEARTBEAT, rls::Constants::HB_PERIOD_GNB_TO_UE); - m_rlsEntity->onHeartbeat(); - } - break; - } - case NtsMessageType::UDP_SERVER_RECEIVE: { - auto *w = dynamic_cast(msg); - m_rlsEntity->onReceive(w->fromAddress, w->packet); - break; - } - default: - m_logger->unhandledNts(msg); - break; - } - - delete msg; -} - -void GnbMrTask::onQuit() -{ - delete m_rlsEntity; - - if (m_udpTask != nullptr) - m_udpTask->quit(); - delete m_udpTask; -} - -void GnbMrTask::onUeConnected(int ue, const std::string &name) -{ - m_ueMap[ue] = {}; - m_ueMap[ue].ueId = ue; - m_ueMap[ue].name = name; - - m_logger->info("New UE connected to gNB. Total number of UEs [%d]", m_ueMap.size()); -} - -void GnbMrTask::onUeReleased(int ue, rls::ECause cause) -{ - if (rls::IsRlf(cause)) - { - m_logger->err("Radio link failure for UE[%d] with cause[%s]", ue, rls::CauseToString(cause)); - - auto *w = new NwGnbMrToRrc(NwGnbMrToRrc::RADIO_LINK_FAILURE); - w->ueId = ue; - m_base->rrcTask->push(w); - } - - m_ueMap.erase(ue); - m_logger->info("A UE disconnected from gNB. Total number of UEs [%d]", m_ueMap.size()); -} - -void GnbMrTask::receiveUplinkPayload(int ue, rls::EPayloadType type, OctetString &&payload) -{ - if (type == rls::EPayloadType::RRC) - { - auto *nw = new NwGnbMrToRrc(NwGnbMrToRrc::RRC_PDU_DELIVERY); - nw->ueId = ue; - nw->channel = static_cast(payload.getI(0)); - nw->pdu = payload.subCopy(1); - m_base->rrcTask->push(nw); - } - else if (type == rls::EPayloadType::DATA) - { - int psi = payload.get4I(0); - OctetString dataPayload = payload.subCopy(4); - - auto *w = new NwGnbMrToGtp(NwGnbMrToGtp::UPLINK_DELIVERY); - w->ueId = ue; - w->pduSessionId = psi; - w->data = std::move(dataPayload); - m_base->gtpTask->push(w); - } -} - -} // namespace nr::gnb diff --git a/src/gnb/mr/task.hpp b/src/gnb/mr/task.hpp deleted file mode 100644 index 184fcef86..000000000 --- a/src/gnb/mr/task.hpp +++ /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. -// - -#pragma once - -#include "rls.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace nr::gnb -{ - -class GnbMrTask : public NtsTask -{ - private: - TaskBase *m_base; - std::unique_ptr m_logger; - - udp::UdpServerTask *m_udpTask; - GnbRls *m_rlsEntity; - std::unordered_map m_ueMap; - - friend class GnbCmdHandler; - - public: - explicit GnbMrTask(TaskBase *base); - ~GnbMrTask() override = default; - - protected: - void onStart() override; - void onLoop() override; - void onQuit() override; - - private: - void onUeConnected(int ue, const std::string &name); - void onUeReleased(int ue, rls::ECause cause); - void receiveUplinkPayload(int ue, rls::EPayloadType type, OctetString &&payload); -}; - -} // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/ngap/interface.cpp b/src/gnb/ngap/interface.cpp index 6b7ebab77..0f3002dc2 100644 --- a/src/gnb/ngap/interface.cpp +++ b/src/gnb/ngap/interface.cpp @@ -195,7 +195,7 @@ void NgapTask::receiveNgSetupResponse(int amfId, ASN_NGAP_NGSetupResponse *msg) update->isNgapUp = true; m_base->appTask->push(update); - m_base->rrcTask->push(new NwGnbNgapToRrc(NwGnbNgapToRrc::NGAP_LAYER_INITIALIZED)); + m_base->rrcTask->push(new NwGnbNgapToRrc(NwGnbNgapToRrc::RADIO_POWER_ON)); } } @@ -238,8 +238,8 @@ void NgapTask::sendErrorIndication(int amfId, NgapCause cause, int ueId) ieCause->value.present = ASN_NGAP_ErrorIndicationIEs__value_PR_Cause; ngap_utils::ToCauseAsn_Ref(cause, ieCause->value.choice.Cause); - m_logger->debug("Sending an error indication with cause: %s", - ngap_utils::CauseToString(ieCause->value.choice.Cause).c_str()); + m_logger->warn("Sending an error indication with cause: %s", + ngap_utils::CauseToString(ieCause->value.choice.Cause).c_str()); auto *pdu = asn::ngap::NewMessagePdu({ieCause}); diff --git a/src/gnb/ngap/radio.cpp b/src/gnb/ngap/radio.cpp index 5c2acf1f9..52fecfd3c 100644 --- a/src/gnb/ngap/radio.cpp +++ b/src/gnb/ngap/radio.cpp @@ -13,6 +13,9 @@ #include #include +#include +#include + namespace nr::gnb { @@ -27,4 +30,30 @@ void NgapTask::handleRadioLinkFailure(int ueId) sendContextRelease(ueId, NgapCause::RadioNetwork_radio_connection_with_ue_lost); } +void NgapTask::receivePaging(int amfId, ASN_NGAP_Paging *msg) +{ + m_logger->debug("Paging received"); + + auto *amf = findAmfContext(amfId); + if (amf == nullptr) + return; + + auto *ieUePagingIdentity = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_UEPagingIdentity); + auto *ieTaiListForPaging = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_TAIListForPaging); + + if (ieUePagingIdentity == nullptr || ieTaiListForPaging == nullptr || + ieUePagingIdentity->UEPagingIdentity.present != ASN_NGAP_UEPagingIdentity_PR_fiveG_S_TMSI) + { + m_logger->err("Invalid parameters received in Paging message"); + return; + } + + auto *w = new NwGnbNgapToRrc(NwGnbNgapToRrc::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); + + m_base->rrcTask->push(w); +} + } // namespace nr::gnb diff --git a/src/gnb/ngap/task.hpp b/src/gnb/ngap/task.hpp index dbcdc7fe6..4b52378d4 100644 --- a/src/gnb/ngap/task.hpp +++ b/src/gnb/ngap/task.hpp @@ -31,6 +31,7 @@ extern "C" struct ASN_NGAP_AMFConfigurationUpdate; extern "C" struct ASN_NGAP_OverloadStart; extern "C" struct ASN_NGAP_OverloadStop; extern "C" struct ASN_NGAP_PDUSessionResourceReleaseCommand; +extern "C" struct ASN_NGAP_Paging; namespace nr::gnb { @@ -118,6 +119,7 @@ class NgapTask : public NtsTask /* Radio resource control */ void handleRadioLinkFailure(int ueId); + void receivePaging(int amfId, ASN_NGAP_Paging *msg); }; } // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/ngap/transport.cpp b/src/gnb/ngap/transport.cpp index 788b5d9d3..36f7c1c6c 100644 --- a/src/gnb/ngap/transport.cpp +++ b/src/gnb/ngap/transport.cpp @@ -288,6 +288,9 @@ void NgapTask::handleSctpMessage(int amfId, uint16_t stream, const UniqueBuffer case ASN_NGAP_InitiatingMessage__value_PR_PDUSessionResourceReleaseCommand: receiveSessionResourceReleaseCommand(amf->ctxId, &value.choice.PDUSessionResourceReleaseCommand); break; + case ASN_NGAP_InitiatingMessage__value_PR_Paging: + receivePaging(amf->ctxId, &value.choice.Paging); + break; default: m_logger->err("Unhandled NGAP initiating-message received (%d)", value.present); break; diff --git a/src/gnb/nts.hpp b/src/gnb/nts.hpp index 4d8904fa0..64248e637 100644 --- a/src/gnb/nts.hpp +++ b/src/gnb/nts.hpp @@ -8,61 +8,93 @@ #pragma once +#include "types.hpp" #include #include +#include #include #include -#include #include #include #include #include #include -#include "types.hpp" +extern "C" struct ASN_NGAP_FiveG_S_TMSI; +extern "C" struct ASN_NGAP_TAIListForPaging; namespace nr::gnb { -struct NwGnbMrToRrc : NtsMessage +struct NwGnbRlsToRrc : NtsMessage { enum PR { RRC_PDU_DELIVERY, - RADIO_LINK_FAILURE, + SIGNAL_LOST } present; // RRC_PDU_DELIVERY - // RADIO_LINK_FAILURE + // SIGNAL_LOST int ueId{}; // RRC_PDU_DELIVERY rrc::RrcChannel channel{}; OctetString pdu{}; - explicit NwGnbMrToRrc(PR present) : NtsMessage(NtsMessageType::GNB_MR_TO_RRC), present(present) + explicit NwGnbRlsToRrc(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_RRC), present(present) { } }; -struct NwGnbRrcToMr : NtsMessage +struct NwGnbRlsToGtp : NtsMessage { enum PR { - NGAP_LAYER_INITIALIZED, - RRC_PDU_DELIVERY, - AN_RELEASE, + DATA_PDU_DELIVERY, } present; - // RRC_PDU_DELIVERY - // AN_RELEASE + // DATA_PDU_DELIVERY int ueId{}; + int psi{}; + OctetString pdu{}; + + explicit NwGnbRlsToGtp(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_GTP), present(present) + { + } +}; + +struct NwGnbGtpToRls : NtsMessage +{ + enum PR + { + DATA_PDU_DELIVERY, + } present; + + // DATA_PDU_DELIVERY + int ueId{}; + int psi{}; + OctetString pdu{}; + + explicit NwGnbGtpToRls(PR present) : NtsMessage(NtsMessageType::GNB_GTP_TO_RLS), present(present) + { + } +}; + +struct NwGnbRrcToRls : NtsMessage +{ + enum PR + { + RADIO_POWER_ON, + RRC_PDU_DELIVERY, + } present; // RRC_PDU_DELIVERY + int ueId{}; rrc::RrcChannel channel{}; OctetString pdu{}; - explicit NwGnbRrcToMr(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_MR), present(present) + explicit NwGnbRrcToRls(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_RLS), present(present) { } }; @@ -71,9 +103,10 @@ struct NwGnbNgapToRrc : NtsMessage { enum PR { - NGAP_LAYER_INITIALIZED, + RADIO_POWER_ON, NAS_DELIVERY, AN_RELEASE, + PAGING, } present; // NAS_DELIVERY @@ -83,6 +116,10 @@ struct NwGnbNgapToRrc : NtsMessage // NAS_DELIVERY OctetString pdu{}; + // PAGING + asn::Unique uePagingTmsi{}; + asn::Unique taiListForPaging{}; + explicit NwGnbNgapToRrc(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_RRC), present(present) { } @@ -142,73 +179,6 @@ struct NwGnbNgapToGtp : NtsMessage } }; -struct NwGnbMrToGtp : NtsMessage -{ - enum PR - { - UPLINK_DELIVERY, - } present; - - // UPLINK_DELIVERY - int ueId{}; - int pduSessionId{}; - OctetString data{}; - - explicit NwGnbMrToGtp(PR present) : NtsMessage(NtsMessageType::GNB_MR_TO_GTP), present(present) - { - } -}; - -struct NwGnbGtpToMr : NtsMessage -{ - enum PR - { - DATA_PDU_DELIVERY, - } present; - - // DATA_PDU_DELIVERY - int ueId{}; - int pduSessionId{}; - OctetString data{}; - - explicit NwGnbGtpToMr(PR present) : NtsMessage(NtsMessageType::GNB_GTP_TO_MR), present(present) - { - } -}; - -struct NwGnbMrToMr : NtsMessage -{ - enum PR - { - UE_CONNECTED, - UE_RELEASED, - SEND_OVER_UDP, - RECEIVE_OVER_UDP, - } present; - - // UE_CONNECTED - // UE_RELEASED - // RECEIVE_OVER_UDP - int ue{}; - - // UE_CONNECTED - std::string name{}; - - // UE_RELEASED - rls::ECause cause{}; - - // SEND_OVER_RLS - InetAddress address{}; - OctetString pdu{}; - - // RECEIVE_OVER_UDP - rls::EPayloadType type{}; - - explicit NwGnbMrToMr(PR present) : NtsMessage(NtsMessageType::GNB_MR_TO_MR), present(present) - { - } -}; - struct NwGnbSctp : NtsMessage { enum PR diff --git a/src/gnb/rls/handler.cpp b/src/gnb/rls/handler.cpp new file mode 100644 index 000000000..477c2927a --- /dev/null +++ b/src/gnb/rls/handler.cpp @@ -0,0 +1,89 @@ +// +// 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 new file mode 100644 index 000000000..193dda79b --- /dev/null +++ b/src/gnb/rls/management.cpp @@ -0,0 +1,73 @@ +// +// 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 new file mode 100644 index 000000000..1ac19793f --- /dev/null +++ b/src/gnb/rls/task.cpp @@ -0,0 +1,118 @@ +// +// 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 + +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{} +{ + m_logger = m_base->logBase->makeUniqueLogger("rls"); + m_sti = utils::Random64(); +} + +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); +} + +void GnbRlsTask::onLoop() +{ + NtsMessage *msg = take(); + if (!msg) + return; + + switch (msg->msgType) + { + case NtsMessageType::GNB_RRC_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))); + break; + } + case NwGnbRrcToRls::RADIO_POWER_ON: { + m_powerOn = true; + break; + } + } + break; + } + case NtsMessageType::GNB_GTP_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))); + break; + } + } + break; + } + case NtsMessageType::UDP_SERVER_RECEIVE: { + auto *w = dynamic_cast(msg); + auto rlsMsg = rls::DecodeRlsMessage(OctetView{w->packet}); + if (rlsMsg == nullptr) + { + m_logger->err("Unable to decode RLS message"); + 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; + } + default: + m_logger->unhandledNts(msg); + break; + } + + delete msg; +} + +void GnbRlsTask::onQuit() +{ + if (m_udpTask != nullptr) + m_udpTask->quit(); + delete m_udpTask; +} + +} // namespace nr::gnb diff --git a/src/gnb/rls/task.hpp b/src/gnb/rls/task.hpp new file mode 100644 index 000000000..f5b378095 --- /dev/null +++ b/src/gnb/rls/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 +#include +#include +#include + +namespace nr::gnb +{ + +class GnbRlsTask : public NtsTask +{ + private: + TaskBase *m_base; + std::unique_ptr m_logger; + udp::UdpServerTask *m_udpTask; + + bool m_powerOn; + uint64_t m_sti; + std::unordered_map> m_ueCtx; + std::unordered_map m_stiToUeId; + int m_ueIdCounter; + + friend class GnbCmdHandler; + + public: + explicit GnbRlsTask(TaskBase *base); + ~GnbRlsTask() override = default; + + protected: + 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 diff --git a/src/gnb/rls/transport.cpp b/src/gnb/rls/transport.cpp new file mode 100644 index 000000000..43eeaaa97 --- /dev/null +++ b/src/gnb/rls/transport.cpp @@ -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 "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/rrc/channel.cpp b/src/gnb/rrc/channel.cpp index b689310ef..9880e7a64 100644 --- a/src/gnb/rrc/channel.cpp +++ b/src/gnb/rrc/channel.cpp @@ -9,8 +9,8 @@ #include "task.hpp" #include #include +#include #include -#include namespace nr::gnb { @@ -28,42 +28,6 @@ void GnbRrcTask::handleUplinkRrc(int ueId, rrc::RrcChannel channel, const OctetS asn::Free(asn_DEF_ASN_RRC_BCCH_BCH_Message, pdu); break; } - case rrc::RrcChannel::BCCH_DL_SCH: { - auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_BCCH_DL_SCH_Message, rrcPdu); - if (pdu == nullptr) - m_logger->err("RRC BCCH-DL-SCH PDU decoding failed."); - else - receiveRrcMessage(ueId, pdu); - asn::Free(asn_DEF_ASN_RRC_BCCH_DL_SCH_Message, pdu); - break; - } - case rrc::RrcChannel::DL_CCCH: { - auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_DL_CCCH_Message, rrcPdu); - if (pdu == nullptr) - m_logger->err("RRC DL-CCCH PDU decoding failed."); - else - receiveRrcMessage(ueId, 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(ueId, 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(ueId, pdu); - asn::Free(asn_DEF_ASN_RRC_PCCH_Message, pdu); - break; - } case rrc::RrcChannel::UL_CCCH: { auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_UL_CCCH_Message, rrcPdu); if (pdu == nullptr) @@ -91,6 +55,11 @@ void GnbRrcTask::handleUplinkRrc(int ueId, rrc::RrcChannel channel, const OctetS asn::Free(asn_DEF_ASN_RRC_UL_DCCH_Message, pdu); break; } + case rrc::RrcChannel::PCCH: + case rrc::RrcChannel::BCCH_DL_SCH: + case rrc::RrcChannel::DL_CCCH: + case rrc::RrcChannel::DL_DCCH: + break; } } @@ -103,11 +72,11 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg) return; } - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); + auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); w->ueId = ueId; w->channel = rrc::RrcChannel::BCCH_BCH; w->pdu = std::move(pdu); - m_base->mrTask->push(w); + m_base->rlsTask->push(w); } void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_BCCH_DL_SCH_Message *msg) @@ -119,11 +88,11 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_BCCH_DL_SCH_Message *msg) return; } - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); + auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); w->ueId = ueId; w->channel = rrc::RrcChannel::BCCH_DL_SCH; w->pdu = std::move(pdu); - m_base->mrTask->push(w); + m_base->rlsTask->push(w); } void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg) @@ -135,11 +104,11 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg) return; } - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); + auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); w->ueId = ueId; w->channel = rrc::RrcChannel::DL_CCCH; w->pdu = std::move(pdu); - m_base->mrTask->push(w); + m_base->rlsTask->push(w); } void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg) @@ -151,14 +120,14 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg) return; } - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); + auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); w->ueId = ueId; w->channel = rrc::RrcChannel::DL_DCCH; w->pdu = std::move(pdu); - m_base->mrTask->push(w); + m_base->rlsTask->push(w); } -void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_PCCH_Message *msg) +void GnbRrcTask::sendRrcMessage(ASN_RRC_PCCH_Message *msg) { OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_PCCH_Message, msg); if (pdu.length() == 0) @@ -167,59 +136,11 @@ void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_PCCH_Message *msg) return; } - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); - w->ueId = ueId; + auto *w = new NwGnbRrcToRls(NwGnbRrcToRls::RRC_PDU_DELIVERY); + w->ueId = 0; w->channel = rrc::RrcChannel::PCCH; w->pdu = std::move(pdu); - m_base->mrTask->push(w); -} - -void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_UL_CCCH_Message *msg) -{ - OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_UL_CCCH_Message, msg); - if (pdu.length() == 0) - { - m_logger->err("RRC UL-CCCH encoding failed."); - return; - } - - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); - w->ueId = ueId; - w->channel = rrc::RrcChannel::UL_CCCH; - w->pdu = std::move(pdu); - m_base->mrTask->push(w); -} - -void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_UL_CCCH1_Message *msg) -{ - OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_UL_CCCH1_Message, msg); - if (pdu.length() == 0) - { - m_logger->err("RRC UL-CCCH1 encoding failed."); - return; - } - - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); - w->ueId = ueId; - w->channel = rrc::RrcChannel::UL_CCCH1; - w->pdu = std::move(pdu); - m_base->mrTask->push(w); -} - -void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_UL_DCCH_Message *msg) -{ - OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_UL_DCCH_Message, msg); - if (pdu.length() == 0) - { - m_logger->err("RRC UL-DCCH encoding failed."); - return; - } - - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::RRC_PDU_DELIVERY); - w->ueId = ueId; - w->channel = rrc::RrcChannel::UL_DCCH; - w->pdu = std::move(pdu); - m_base->mrTask->push(w); + m_base->rlsTask->push(w); } void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg) @@ -227,26 +148,6 @@ void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg) // TODO } -void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_BCCH_DL_SCH_Message *msg) -{ - // TODO -} - -void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg) -{ - // TODO -} - -void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg) -{ - // TODO -} - -void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_PCCH_Message *msg) -{ - // TODO -} - void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_UL_CCCH_Message *msg) { if (msg->message.present != ASN_RRC_UL_CCCH_MessageType_PR_c1) diff --git a/src/gnb/rrc/handler.cpp b/src/gnb/rrc/handler.cpp index 3e921ea42..c257058e9 100644 --- a/src/gnb/rrc/handler.cpp +++ b/src/gnb/rrc/handler.cpp @@ -8,10 +8,10 @@ #include "task.hpp" -#include #include #include +#include #include #include #include @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -117,7 +120,7 @@ void GnbRrcTask::receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest asn::SetOctetString(rrcSetupIEs->masterCellGroup, rrc::encode::EncodeS(asn_DEF_ASN_RRC_CellGroupConfig, &masterCellGroup)); - m_logger->debug("Sending RRC Setup for UE[%d]", ueId); + m_logger->info("RRC Setup for UE[%d]", ueId); sendRrcMessage(ueId, pdu); } @@ -141,7 +144,7 @@ void GnbRrcTask::receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplet void GnbRrcTask::releaseConnection(int ueId) { - m_logger->debug("Releasing RRC connection for UE[%d]", ueId); + m_logger->info("Releasing RRC connection for UE[%d]", ueId); // Send RRC Release message auto *pdu = asn::New(); @@ -155,11 +158,6 @@ void GnbRrcTask::releaseConnection(int ueId) sendRrcMessage(ueId, pdu); - // Notify MR task - auto *w = new NwGnbRrcToMr(NwGnbRrcToMr::AN_RELEASE); - w->ueId = ueId; - m_base->mrTask->push(w); - // Delete UE RRC context m_ueCtx.erase(ueId); } @@ -175,4 +173,32 @@ void GnbRrcTask::handleRadioLinkFailure(int ueId) m_ueCtx.erase(ueId); } +void GnbRrcTask::handlePaging(const asn::Unique &tmsi, + const asn::Unique &taiList) +{ + // Construct and send a Paging message + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_PCCH_MessageType_PR_c1; + pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); + pdu->message.choice.c1->present = ASN_RRC_PCCH_MessageType__c1_PR_paging; + auto &paging = pdu->message.choice.c1->choice.paging = asn::New(); + + auto *record = asn::New(); + record->ue_Identity.present = ASN_RRC_PagingUE_Identity_PR_ng_5G_S_TMSI; + + OctetString tmsiOctets{}; + tmsiOctets.appendOctet2(bits::Ranged16({ + {10, asn::GetBitStringInt<10>(tmsi->aMFSetID)}, + {6, asn::GetBitStringInt<10>(tmsi->aMFPointer)}, + })); + tmsiOctets.append(asn::GetOctetString(tmsi->fiveG_TMSI)); + + asn::SetBitString(record->ue_Identity.choice.ng_5G_S_TMSI, tmsiOctets); + + paging->pagingRecordList = asn::NewFor(paging->pagingRecordList); + asn::SequenceAdd(*paging->pagingRecordList, record); + + sendRrcMessage(pdu); +} + } // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/rrc/task.cpp b/src/gnb/rrc/task.cpp index cd9b6f7ab..a3e58647b 100644 --- a/src/gnb/rrc/task.cpp +++ b/src/gnb/rrc/task.cpp @@ -9,8 +9,8 @@ #include "task.hpp" #include #include -#include #include +#include #include namespace nr::gnb @@ -38,15 +38,15 @@ void GnbRrcTask::onLoop() switch (msg->msgType) { - case NtsMessageType::GNB_MR_TO_RRC: { - auto *w = dynamic_cast(msg); + case NtsMessageType::GNB_RLS_TO_RRC: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbMrToRrc::RRC_PDU_DELIVERY: { + case NwGnbRlsToRrc::RRC_PDU_DELIVERY: { handleUplinkRrc(w->ueId, w->channel, w->pdu); break; } - case NwGnbMrToRrc::RADIO_LINK_FAILURE: { + case NwGnbRlsToRrc::SIGNAL_LOST: { handleRadioLinkFailure(w->ueId); break; } @@ -57,8 +57,8 @@ void GnbRrcTask::onLoop() auto *w = dynamic_cast(msg); switch (w->present) { - case NwGnbNgapToRrc::NGAP_LAYER_INITIALIZED: { - m_base->mrTask->push(new NwGnbRrcToMr(NwGnbRrcToMr::NGAP_LAYER_INITIALIZED)); + case NwGnbNgapToRrc::RADIO_POWER_ON: { + m_base->rlsTask->push(new NwGnbRrcToRls(NwGnbRrcToRls::RADIO_POWER_ON)); break; } case NwGnbNgapToRrc::NAS_DELIVERY: { @@ -69,6 +69,9 @@ void GnbRrcTask::onLoop() releaseConnection(w->ueId); break; } + case NwGnbNgapToRrc::PAGING: + handlePaging(w->uePagingTmsi, w->taiListForPaging); + break; } break; } diff --git a/src/gnb/rrc/task.hpp b/src/gnb/rrc/task.hpp index 9cefe6dae..d8cb15319 100644 --- a/src/gnb/rrc/task.hpp +++ b/src/gnb/rrc/task.hpp @@ -36,7 +36,6 @@ namespace nr::gnb { class NgapTask; -class GnbMrTask; class GnbRrcTask : public NtsTask { @@ -71,6 +70,8 @@ class GnbRrcTask : public NtsTask void deliverUplinkNas(int ueId, OctetString &&nasPdu); void releaseConnection(int ueId); void handleRadioLinkFailure(int ueId); + void handlePaging(const asn::Unique &tmsi, + const asn::Unique &taiList); void receiveUplinkInformationTransfer(int ueId, const ASN_RRC_ULInformationTransfer &msg); void receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg); @@ -81,17 +82,10 @@ class GnbRrcTask : public NtsTask void sendRrcMessage(int ueId, 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(int ueId, ASN_RRC_PCCH_Message *msg); - void sendRrcMessage(int ueId, ASN_RRC_UL_CCCH_Message *msg); - void sendRrcMessage(int ueId, ASN_RRC_UL_CCCH1_Message *msg); - void sendRrcMessage(int ueId, ASN_RRC_UL_DCCH_Message *msg); + void sendRrcMessage(ASN_RRC_PCCH_Message *msg); /* RRC channel receive message */ void receiveRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg); - void receiveRrcMessage(int ueId, ASN_RRC_BCCH_DL_SCH_Message *msg); - void receiveRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg); - void receiveRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg); - void receiveRrcMessage(int ueId, ASN_RRC_PCCH_Message *msg); 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); diff --git a/src/gnb/types.hpp b/src/gnb/types.hpp index 0afef9e4d..bad4bd96b 100644 --- a/src/gnb/types.hpp +++ b/src/gnb/types.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -23,9 +24,9 @@ namespace nr::gnb class GnbAppTask; class GtpTask; -class GnbMrTask; class NgapTask; class GnbRrcTask; +class GnbRlsTask; class SctpTask; enum class EAmfState @@ -103,6 +104,18 @@ struct NgapAmfContext std::vector plmnSupportList{}; }; +struct RlsUeContext +{ + const int ueId; + uint64_t sti{}; + InetAddress addr{}; + int64_t lastSeen{}; + + explicit RlsUeContext(int ueId) : ueId(ueId) + { + } +}; + struct AggregateMaximumBitRate { uint64_t dlAmbr{}; @@ -299,6 +312,7 @@ struct GnbConfig /* Assigned by program */ std::string name{}; EPagingDrx pagingDrx{}; + Vector3 phyLocation{}; [[nodiscard]] inline uint32_t getGnbId() const { @@ -320,16 +334,10 @@ struct TaskBase GnbAppTask *appTask{}; GtpTask *gtpTask{}; - GnbMrTask *mrTask{}; NgapTask *ngapTask{}; GnbRrcTask *rrcTask{}; SctpTask *sctpTask{}; -}; - -struct MrUeContext -{ - int ueId{}; - std::string name{}; + GnbRlsTask *rlsTask{}; }; Json ToJson(const GnbStatusInfo &v); diff --git a/src/nas/enums.hpp b/src/nas/enums.hpp index 3c42fc1e6..d810aa58a 100644 --- a/src/nas/enums.hpp +++ b/src/nas/enums.hpp @@ -137,7 +137,7 @@ enum class EMmCause SEC_MODE_REJECTED_UNSPECIFIED = 0b00011000, NON_5G_AUTHENTICATION_UNACCEPTABLE = 0b00011010, N1_MODE_NOT_ALLOWED = 0b00011011, - RESTRICTED_NOT_SERVICE_AREA = 0b00011100, + RESTRICTED_SERVICE_AREA = 0b00011100, LADN_NOT_AVAILABLE = 0b00101011, MAX_PDU_SESSIONS_REACHED = 0b01000001, INSUFFICIENT_RESOURCES_FOR_SLICE_AND_DNN = 0b01000011, @@ -646,6 +646,7 @@ enum class EServiceType EMERGENCY_SERVICES = 0b0011, EMERGENCY_SERVICES_FALLBACK = 0b0100, HIGH_PRIORITY_ACCESS = 0b0101, + ELEVATED_SIGNALLING = 0b0110, UNUSED_SIGNALLING_1 = 0b0110, UNUSED_SIGNALLING_2 = 0b0111, UNUSED_SIGNALLING_3 = 0b1000, diff --git a/src/nas/utils.cpp b/src/nas/utils.cpp index 0d8bcebd0..d69eca31b 100644 --- a/src/nas/utils.cpp +++ b/src/nas/utils.cpp @@ -8,6 +8,7 @@ #include "utils.hpp" +#include #include namespace nas::utils @@ -95,8 +96,8 @@ const char *EnumToString(EMmCause v) return "NON_5G_AUTHENTICATION_UNACCEPTABLE"; case EMmCause::N1_MODE_NOT_ALLOWED: return "N1_MODE_NOT_ALLOWED"; - case EMmCause::RESTRICTED_NOT_SERVICE_AREA: - return "RESTRICTED_NOT_SERVICE_AREA"; + case EMmCause::RESTRICTED_SERVICE_AREA: + return "RESTRICTED_SERVICE_AREA"; case EMmCause::LADN_NOT_AVAILABLE: return "LADN_NOT_AVAILABLE"; case EMmCause::MAX_PDU_SESSIONS_REACHED: @@ -262,7 +263,7 @@ IEDnn DnnFromApn(const std::string &apn) return dnn; } -void AddToPlmnList(IEPlmnList &list, VPlmn item) +void AddToPlmnList(IEPlmnList &list, const VPlmn &item) { if (!std::any_of(list.plmns.begin(), list.plmns.end(), [&item](auto &i) { return DeepEqualsV(i, item); })) list.plmns.push_back(item); @@ -289,4 +290,148 @@ SingleSlice SNssaiTo(const IESNssai &v) return sNssai; } +bool PlmnListContains(const IEPlmnList &list, Plmn item) +{ + return PlmnListContains(list, PlmnFrom(item)); +} + +bool PlmnListContains(const IEPlmnList &list, VPlmn item) +{ + return std::any_of(list.plmns.begin(), list.plmns.end(), [&item](auto &i) { return DeepEqualsV(i, item); }); +} + +bool TaiListContains(const IE5gsTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai) +{ + return std::any_of(list.list.begin(), list.list.end(), [&tai](auto &i) { return TaiListContains(i, tai); }); +} + +bool TaiListContains(const VPartialTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai) +{ + if (list.present == 0) + { + auto &list0 = list.list00; + if (DeepEqualsV(list0->plmn, tai.plmn)) + { + if (std::any_of(list0->tacs.begin(), list0->tacs.end(), [&tai](auto &i) { return (int)i == (int)tai.tac; })) + return true; + } + } + else if (list.present == 1) + { + auto &list1 = list.list01; + if (DeepEqualsV(list1->plmn, tai.plmn) && (int)list1->tac == (int)tai.tac) + return true; + } + else if (list.present == 2) + { + auto &list2 = list.list10; + if (std::any_of(list2->tais.begin(), list2->tais.end(), [&tai](auto &i) { return DeepEqualsV(i, tai); })) + return true; + } + + return false; +} + +bool ServiceAreaListForbidsPlmn(const IEServiceAreaList &list, const VPlmn &plmn) +{ + return std::any_of(list.list.begin(), list.list.end(), + [&plmn](auto &i) { return ServiceAreaListForbidsPlmn(i, plmn); }); +} + +bool ServiceAreaListForbidsTai(const IEServiceAreaList &list, const VTrackingAreaIdentity &tai) +{ + return std::any_of(list.list.begin(), list.list.end(), + [&tai](auto &i) { return ServiceAreaListForbidsTai(i, tai); }); +} + +bool ServiceAreaListForbidsPlmn(const VPartialServiceAreaList &list, const VPlmn &plmn) +{ + if (list.present == 3) + { + if (list.list11->allowedType == EAllowedType::IN_THE_NON_ALLOWED_AREA && DeepEqualsV(list.list11->plmn, plmn)) + return true; + } + return false; +} + +bool ServiceAreaListForbidsTai(const VPartialServiceAreaList &list, const VTrackingAreaIdentity &tai) +{ + if (ServiceAreaListForbidsPlmn(list, tai.plmn)) + return true; + if (list.present == 0) + { + if (list.list00->allowedType == EAllowedType::IN_THE_NON_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_NON_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_NON_ALLOWED_AREA && + std::any_of(list.list10->tais.begin(), list.list10->tais.end(), + [tai](auto &i) { return DeepEqualsV(i, tai); })) + return true; + } + return false; +} + +void AddToTaiList(IE5gsTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai) +{ + if (!TaiListContains(list, tai)) + { + VPartialTrackingAreaIdentityList ls{}; + ls.list01 = VPartialTrackingAreaIdentityList01{tai.plmn, tai.tac}; + ls.present = 1; + list.list.push_back(ls); + } +} + +void RemoveFromTaiList(IE5gsTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai) +{ + list.list.erase(std::remove_if(list.list.begin(), list.list.end(), + [&tai](auto &itemList) { + return itemList.present == 1 && DeepEqualsV(itemList.list01->plmn, tai.plmn) && + (int)itemList.list01->tac == (int)tai.tac; + }), + list.list.end()); + + for (auto &itemList : list.list) + { + if (itemList.present == 0) + { + auto &list0 = itemList.list00; + if (DeepEqualsV(list0->plmn, tai.plmn)) + { + list0->tacs.erase(std::remove_if(list0->tacs.begin(), list0->tacs.end(), + [&tai](auto tac) { return (int)tac == (int)tai.tac; }), + list0->tacs.end()); + } + } + else if (itemList.present == 2) + { + auto &list2 = itemList.list10; + list2->tais.erase( + std::remove_if(list2->tais.begin(), list2->tais.end(), [&tai](auto &i) { return DeepEqualsV(i, tai); }), + list2->tais.end()); + } + } + + list.list.erase( + std::remove_if(list.list.begin(), list.list.end(), + [](auto &itemList) { return itemList.present == 0 && itemList.list00->tacs.empty(); }), + list.list.end()); + + list.list.erase( + std::remove_if(list.list.begin(), list.list.end(), + [](auto &itemList) { return itemList.present == 2 && itemList.list10->tais.empty(); }), + list.list.end()); +} + } // namespace nas::utils diff --git a/src/nas/utils.hpp b/src/nas/utils.hpp index b122f8c5c..7a62e86dd 100644 --- a/src/nas/utils.hpp +++ b/src/nas/utils.hpp @@ -24,7 +24,17 @@ SingleSlice SNssaiTo(const IESNssai &v); bool HasValue(const IEGprsTimer3 &v); bool HasValue(const IEGprsTimer2 &v); -void AddToPlmnList(IEPlmnList &list, VPlmn item); +void AddToPlmnList(IEPlmnList &list, const VPlmn &item); +void AddToTaiList(nas::IE5gsTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai); +void RemoveFromTaiList(nas::IE5gsTrackingAreaIdentityList &list, const VTrackingAreaIdentity &tai); +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); +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); const char *EnumToString(ERegistrationType v); const char *EnumToString(EMmCause v); diff --git a/src/ue/app/cmd_handler.cpp b/src/ue/app/cmd_handler.cpp index 1e98f2547..5863d4fdd 100644 --- a/src/ue/app/cmd_handler.cpp +++ b/src/ue/app/cmd_handler.cpp @@ -9,9 +9,9 @@ #include "cmd_handler.hpp" #include -#include #include #include +#include #include #include #include @@ -19,6 +19,17 @@ #define PAUSE_CONFIRM_TIMEOUT 3000 #define PAUSE_POLLING 10 +static std::string SignalDescription(int dbm) +{ + if (dbm > -90) + return "Excellent"; + if (dbm > -105) + return "Good"; + if (dbm > -120) + return "Fair"; + return "Poor"; +} + namespace nr::ue { @@ -34,26 +45,26 @@ void UeCmdHandler::sendError(const InetAddress &address, const std::string &outp void UeCmdHandler::pauseTasks() { - m_base->mrTask->requestPause(); m_base->nasTask->requestPause(); m_base->rrcTask->requestPause(); + m_base->rlsTask->requestPause(); } void UeCmdHandler::unpauseTasks() { - m_base->mrTask->requestUnpause(); m_base->nasTask->requestUnpause(); m_base->rrcTask->requestUnpause(); + m_base->rlsTask->requestUnpause(); } bool UeCmdHandler::isAllPaused() { - if (!m_base->mrTask->isPauseConfirmed()) - return false; if (!m_base->nasTask->isPauseConfirmed()) return false; if (!m_base->rrcTask->isPauseConfirmed()) return false; + if (!m_base->rlsTask->isPauseConfirmed()) + return false; return true; } @@ -103,6 +114,8 @@ void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg) {"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 : "")}, {"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)}, @@ -150,6 +163,30 @@ 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()) + { + sendResult(msg.address, "No cell exists in the range"); + break; + } + + std::vector cellInfo{}; + for (auto &entry : map) + { + 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) + "]"}, + })); + } + + sendResult(msg.address, Json::Arr(cellInfo).dumpYaml()); + break; + } } } diff --git a/src/ue/app/task.cpp b/src/ue/app/task.cpp index 22a7c339f..61febf220 100644 --- a/src/ue/app/task.cpp +++ b/src/ue/app/task.cpp @@ -9,7 +9,8 @@ #include "task.hpp" #include "cmd_handler.hpp" #include -#include +#include +#include #include #include #include @@ -50,17 +51,17 @@ void UeAppTask::onLoop() switch (msg->msgType) { - case NtsMessageType::UE_MR_TO_APP: { - auto *w = dynamic_cast(msg); + case NtsMessageType::UE_RLS_TO_APP: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeMrToApp::DATA_PDU_DELIVERY: { + 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->data); + nw->data = std::move(w->pdu); tunTask->push(nw); } break; @@ -73,10 +74,7 @@ void UeAppTask::onLoop() switch (w->present) { case NwUeTunToApp::DATA_PDU_DELIVERY: { - auto *nw = new NwAppToMr(NwAppToMr::DATA_PDU_DELIVERY); - nw->psi = w->psi; - nw->data = std::move(w->data); - m_base->mrTask->push(nw); + handleUplinkDataRequest(w->psi, std::move(w->data)); break; } case NwUeTunToApp::TUN_ERROR: { @@ -135,6 +133,7 @@ void UeAppTask::receiveStatusUpdate(NwUeStatusUpdate &msg) 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); @@ -158,6 +157,12 @@ void UeAppTask::receiveStatusUpdate(NwUeStatusUpdate &msg) } return; } + + if (msg.what == NwUeStatusUpdate::CM_STATE) + { + m_cmState = msg.cmState; + return; + } } void UeAppTask::setupTunInterface(const PduSession *pduSession) @@ -219,4 +224,40 @@ 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 75a528b21..ceab73bf8 100644 --- a/src/ue/app/task.hpp +++ b/src/ue/app/task.hpp @@ -29,6 +29,7 @@ class UeAppTask : public NtsTask std::array, 16> m_pduSessions{}; std::array m_tunTasks{}; + ECmState m_cmState{}; friend class UeCmdHandler; @@ -44,6 +45,7 @@ class UeAppTask : public NtsTask private: void receiveStatusUpdate(NwUeStatusUpdate &msg); void setupTunInterface(const PduSession *pduSession); + void handleUplinkDataRequest(int psi, OctetString &&data); }; } // namespace nr::ue diff --git a/src/ue/mm/radio.cpp b/src/ue/mm/radio.cpp deleted file mode 100644 index 90179caba..000000000 --- a/src/ue/mm/radio.cpp +++ /dev/null @@ -1,67 +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 -#include -#include -#include - -namespace nr::ue -{ - -void NasMm::handlePlmnSearchResponse(const std::string &gnbName) -{ - if (m_base->nodeListener) - m_base->nodeListener->onConnected(app::NodeType::UE, m_base->config->getNodeName(), app::NodeType::GNB, - gnbName); - - m_logger->info("UE connected to gNB"); - - if (m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH || - m_mmSubState == EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE) - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE); - else if (m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH || - m_mmSubState == EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); - - resetRegAttemptCounter(); -} - -void NasMm::handlePlmnSearchFailure() -{ - if (m_mmSubState == EMmSubState::MM_REGISTERED_PLMN_SEARCH) - switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NO_CELL_AVAILABLE); - else if (m_mmSubState == EMmSubState::MM_DEREGISTERED_PLMN_SEARCH) - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE); -} - -void NasMm::handleRrcConnectionSetup() -{ - switchCmState(ECmState::CM_CONNECTED); -} - -void NasMm::handleRrcConnectionRelease() -{ - switchCmState(ECmState::CM_IDLE); -} - -void NasMm::handleRadioLinkFailure() -{ - m_logger->debug("Radio link failure detected"); - handleRrcConnectionRelease(); -} - -void NasMm::localReleaseConnection() -{ - m_logger->info("Performing local release of NAS connection"); - - m_base->rrcTask->push(new NwUeNasToRrc(NwUeNasToRrc::LOCAL_RELEASE_CONNECTION)); -} - -} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/mm/service.cpp b/src/ue/mm/service.cpp deleted file mode 100644 index 594c2412d..000000000 --- a/src/ue/mm/service.cpp +++ /dev/null @@ -1,40 +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" - -namespace nr::ue -{ - -void NasMm::receiveServiceAccept(const nas::ServiceAccept &msg) -{ - if (msg.eapMessage.has_value()) - { - if (msg.eapMessage->eap->code == eap::ECode::FAILURE) - receiveEapFailureMessage(*msg.eapMessage->eap); - else - m_logger->warn("Network sent EAP with inconvenient type in ServiceAccept, ignoring EAP IE."); - } - - // TODO -} - -void NasMm::receiveServiceReject(const nas::ServiceReject &msg) -{ - if (msg.eapMessage.has_value()) - { - if (msg.eapMessage->eap->code == eap::ECode::FAILURE) - receiveEapFailureMessage(*msg.eapMessage->eap); - else - m_logger->warn("Network sent EAP with inconvenient type in ServiceAccept, ignoring EAP IE."); - } - - // TODO -} - -} // namespace nr::ue diff --git a/src/ue/mr/rls.cpp b/src/ue/mr/rls.cpp deleted file mode 100644 index 69682859e..000000000 --- a/src/ue/mr/rls.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 "rls.hpp" -#include -#include - -namespace nr::ue -{ - -UeRls::UeRls(const std::string &nodeName, const std::vector &gnbSearchList, std::unique_ptr logger, - NtsTask *targetTask) - : RlsUeEntity(nodeName, gnbSearchList), m_logger(std::move(logger)), m_targetTask(targetTask) -{ -} - -void UeRls::logWarn(const std::string &msg) -{ - m_logger->warn(msg); -} - -void UeRls::logError(const std::string &msg) -{ - m_logger->err(msg); -} - -void UeRls::startWaitingTimer(int period) -{ - auto *w = new NwUeMrToMr(NwUeMrToMr::RLS_START_WAITING_TIMER); - w->period = period; - m_targetTask->push(w); -} - -void UeRls::searchFailure(rls::ECause cause) -{ - auto *w = new NwUeMrToMr(NwUeMrToMr::RLS_SEARCH_FAILURE); - w->cause = cause; - m_targetTask->push(w); -} - -void UeRls::onRelease(rls::ECause cause) -{ - auto *w = new NwUeMrToMr(NwUeMrToMr::RLS_RELEASED); - w->cause = cause; - m_targetTask->push(w); -} - -void UeRls::onConnect(const std::string &gnbName) -{ - auto *w = new NwUeMrToMr(NwUeMrToMr::RLS_CONNECTED); - w->gnbName = gnbName; - m_targetTask->push(w); -} - -void UeRls::sendRlsPdu(const InetAddress &address, OctetString &&pdu) -{ - auto *w = new NwUeMrToMr(NwUeMrToMr::RLS_SEND_OVER_UDP); - w->address = address; - w->pdu = std::move(pdu); - m_targetTask->push(w); -} - -void UeRls::deliverPayload(rls::EPayloadType type, OctetString &&payload) -{ - auto *w = new NwUeMrToMr(NwUeMrToMr::RLS_RECEIVE_OVER_UDP); - w->type = type; - w->pdu = std::move(payload); - m_targetTask->push(w); -} - -} // namespace nr::ue diff --git a/src/ue/mr/rls.hpp b/src/ue/mr/rls.hpp deleted file mode 100644 index 3ba47016e..000000000 --- a/src/ue/mr/rls.hpp +++ /dev/null @@ -1,39 +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. -// - -#pragma once - -#include -#include -#include - -namespace nr::ue -{ - -class UeRls : public rls::RlsUeEntity -{ - private: - std::unique_ptr m_logger; - NtsTask *m_targetTask; - - public: - UeRls(const std::string &nodeName, const std::vector &gnbSearchList, std::unique_ptr logger, - NtsTask *targetTask); - - protected: - void logWarn(const std::string &msg) override; - void logError(const std::string &msg) override; - void startWaitingTimer(int period) override; - void searchFailure(rls::ECause cause) override; - void onRelease(rls::ECause cause) override; - void onConnect(const std::string &gnbName) override; - void sendRlsPdu(const InetAddress &address, OctetString &&pdu) override; - void deliverPayload(rls::EPayloadType type, OctetString &&payload) override; -}; - -} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/mr/task.cpp b/src/ue/mr/task.cpp deleted file mode 100644 index d2a1a1233..000000000 --- a/src/ue/mr/task.cpp +++ /dev/null @@ -1,192 +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 -#include - -static const int TIMER_ID_RLS_WAITING_TIMER = 1; -static const int TIMER_ID_RLS_HEARTBEAT = 2; -static const int PLMN_SEARCH_FAILED_PRINT_THRESHOLD = 10000; - -namespace nr::ue -{ - -ue::UeMrTask::UeMrTask(TaskBase *base) : m_base{base}, m_udpTask{}, m_rlsEntity{}, m_lastPlmnSearchFailurePrinted{} -{ - m_logger = m_base->logBase->makeUniqueLogger(m_base->config->getLoggerPrefix() + "mr"); -} - -void UeMrTask::onStart() -{ - m_udpTask = new udp::UdpServerTask(this); - - std::vector gnbSearchList{}; - for (auto &ip : m_base->config->gnbSearchList) - gnbSearchList.emplace_back(ip, cons::PortalPort); - - m_rlsEntity = new UeRls(m_base->config->getNodeName(), gnbSearchList, - m_base->logBase->makeUniqueLogger(m_base->config->getLoggerPrefix() + "rls"), this); - - m_udpTask->start(); - - setTimer(TIMER_ID_RLS_HEARTBEAT, rls::Constants::HB_PERIOD_UE_TO_GNB); -} - -void UeMrTask::onQuit() -{ - delete m_rlsEntity; - - m_udpTask->quit(); - delete m_udpTask; -} - -void UeMrTask::onLoop() -{ - NtsMessage *msg = take(); - if (!msg) - return; - - switch (msg->msgType) - { - case NtsMessageType::UE_MR_TO_MR: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwUeMrToMr::RLS_CONNECTED: { - auto tw = new NwUeMrToRrc(NwUeMrToRrc::PLMN_SEARCH_RESPONSE); - tw->gnbName = std::move(w->gnbName); - m_base->rrcTask->push(tw); - break; - } - case NwUeMrToMr::RLS_RELEASED: { - if (rls::IsRlf(w->cause)) - { - m_logger->err("Radio link failure with cause[%s]", rls::CauseToString(w->cause)); - m_base->rrcTask->push(new NwUeMrToRrc(NwUeMrToRrc::RADIO_LINK_FAILURE)); - } - else - { - m_logger->debug("UE disconnected from gNB [%s]", rls::CauseToString(w->cause)); - } - break; - } - case NwUeMrToMr::RLS_SEARCH_FAILURE: { - long current = utils::CurrentTimeMillis(); - if (current - m_lastPlmnSearchFailurePrinted > PLMN_SEARCH_FAILED_PRINT_THRESHOLD) - { - m_logger->err("PLMN search failed [%s]", rls::CauseToString(w->cause)); - m_lastPlmnSearchFailurePrinted = current; - m_base->rrcTask->push(new NwUeMrToRrc(NwUeMrToRrc::PLMN_SEARCH_FAILURE)); - } - break; - } - case NwUeMrToMr::RLS_START_WAITING_TIMER: { - setTimer(TIMER_ID_RLS_WAITING_TIMER, w->period); - break; - } - case NwUeMrToMr::RLS_SEND_OVER_UDP: { - m_udpTask->send(w->address, w->pdu); - break; - } - case NwUeMrToMr::RLS_RECEIVE_OVER_UDP: { - receiveDownlinkPayload(w->type, std::move(w->pdu)); - break; - } - } - break; - } - case NtsMessageType::UE_RRC_TO_MR: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwUeRrcToMr::PLMN_SEARCH_REQUEST: { - m_rlsEntity->startGnbSearch(); - break; - } - case NwUeRrcToMr::RRC_PDU_DELIVERY: { - // Append channel information - OctetString stream{}; - stream.appendOctet(static_cast(w->channel)); - stream.append(w->pdu); - - m_rlsEntity->onUplinkDelivery(rls::EPayloadType::RRC, std::move(stream)); - break; - } - case NwUeRrcToMr::RRC_CONNECTION_RELEASE: { - m_rlsEntity->localReleaseConnection(w->cause); - m_rlsEntity->resetEntity(); - break; - } - } - break; - } - case NtsMessageType::UE_APP_TO_MR: { - auto *w = dynamic_cast(msg); - switch (w->present) - { - case NwAppToMr::DATA_PDU_DELIVERY: { - // Append PDU session information - OctetString stream{}; - stream.appendOctet4(w->psi); - stream.append(w->data); - - m_rlsEntity->onUplinkDelivery(rls::EPayloadType::DATA, std::move(stream)); - break; - } - } - break; - } - case NtsMessageType::TIMER_EXPIRED: { - auto *w = dynamic_cast(msg); - if (w->timerId == TIMER_ID_RLS_WAITING_TIMER) - { - m_rlsEntity->onWaitingTimerExpire(); - } - else if (w->timerId == TIMER_ID_RLS_HEARTBEAT) - { - setTimer(TIMER_ID_RLS_HEARTBEAT, rls::Constants::HB_PERIOD_UE_TO_GNB); - m_rlsEntity->onHeartbeat(); - } - break; - } - case NtsMessageType::UDP_SERVER_RECEIVE: { - auto *w = dynamic_cast(msg); - m_rlsEntity->onReceive(w->fromAddress, w->packet); - break; - } - default: - m_logger->unhandledNts(msg); - break; - } - - delete msg; -} - -void UeMrTask::receiveDownlinkPayload(rls::EPayloadType type, OctetString &&payload) -{ - if (type == rls::EPayloadType::RRC) - { - auto *nw = new NwUeMrToRrc(NwUeMrToRrc::RRC_PDU_DELIVERY); - nw->channel = static_cast(payload.getI(0)); - nw->pdu = payload.subCopy(1); - m_base->rrcTask->push(nw); - } - else if (type == rls::EPayloadType::DATA) - { - auto *nw = new NwUeMrToApp(NwUeMrToApp::DATA_PDU_DELIVERY); - nw->psi = payload.get4I(0); - nw->data = payload.subCopy(4); - m_base->appTask->push(nw); - } -} - -} // namespace nr::ue diff --git a/src/ue/mr/task.hpp b/src/ue/mr/task.hpp deleted file mode 100644 index e5d780831..000000000 --- a/src/ue/mr/task.hpp +++ /dev/null @@ -1,50 +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. -// - -#pragma once - -#include "rls.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace nr::ue -{ - -class UeMrTask : public NtsTask -{ - private: - TaskBase *m_base; - std::unique_ptr m_logger; - - udp::UdpServerTask *m_udpTask; - UeRls *m_rlsEntity; - - long m_lastPlmnSearchFailurePrinted; - - friend class UeCmdHandler; - - public: - explicit UeMrTask(TaskBase *base); - ~UeMrTask() override = default; - - protected: - void onStart() override; - void onLoop() override; - void onQuit() override; - - private: - void receiveDownlinkPayload(rls::EPayloadType type, OctetString &&payload); -}; - -} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/nas/enc.cpp b/src/ue/nas/enc.cpp index 4439f01f8..8f8db4a1c 100644 --- a/src/ue/nas/enc.cpp +++ b/src/ue/nas/enc.cpp @@ -14,12 +14,13 @@ namespace nr::ue::nas_enc { -static nas::ESecurityHeaderType MakeSecurityHeaderType(const NasSecurityContext &ctx, nas::EMessageType msgType) +static nas::ESecurityHeaderType MakeSecurityHeaderType(const NasSecurityContext &ctx, nas::EMessageType msgType, + bool bypassCiphering) { auto &encKey = ctx.keys.kNasEnc; auto &intKey = ctx.keys.kNasInt; - bool ciphered = encKey.length() > 0; + bool ciphered = !bypassCiphering && encKey.length() > 0; bool integrityProtected = intKey.length() > 0; if (!ciphered && !integrityProtected) @@ -64,7 +65,7 @@ static OctetString EncryptData(nas::ETypeOfCipheringAlgorithm alg, const NasCoun } static std::unique_ptr Encrypt(NasSecurityContext &ctx, OctetString &&plainNasMessage, - nas::EMessageType msgType) + nas::EMessageType msgType, bool bypassCiphering) { auto count = ctx.uplinkCount; auto is3gppAccess = ctx.is3gppAccess; @@ -73,12 +74,13 @@ static std::unique_ptr Encrypt(NasSecurityContext &ctx, O auto intAlg = ctx.integrity; auto encAlg = ctx.ciphering; - auto encryptedData = EncryptData(encAlg, count, is3gppAccess, plainNasMessage, encKey); + auto encryptedData = + bypassCiphering ? plainNasMessage.copy() : EncryptData(encAlg, count, is3gppAccess, plainNasMessage, encKey); auto mac = ComputeMac(intAlg, count, is3gppAccess, true, intKey, encryptedData); auto secured = std::make_unique(); secured->epd = nas::EExtendedProtocolDiscriminator::MOBILITY_MANAGEMENT_MESSAGES; - secured->sht = MakeSecurityHeaderType(ctx, msgType); + secured->sht = MakeSecurityHeaderType(ctx, msgType, bypassCiphering); secured->messageAuthenticationCode = octet4{mac}; secured->sequenceNumber = count.sqn; secured->plainNasMessage = std::move(encryptedData); @@ -120,24 +122,15 @@ static OctetString DecryptData(nas::ETypeOfCipheringAlgorithm alg, const NasCoun return msg; } -std::unique_ptr Encrypt(NasSecurityContext &ctx, const nas::PlainMmMessage &msg) +std::unique_ptr Encrypt(NasSecurityContext &ctx, const nas::PlainMmMessage &msg, + bool bypassCiphering) { nas::EMessageType msgType = msg.messageType; OctetString stream; nas::EncodeNasMessage(msg, stream); - return Encrypt(ctx, std::move(stream), msgType); -} - -std::unique_ptr Encrypt(NasSecurityContext &ctx, const nas::SmMessage &msg) -{ - nas::EMessageType msgType = msg.messageType; - - OctetString stream; - nas::EncodeNasMessage(msg, stream); - - return Encrypt(ctx, std::move(stream), msgType); + return Encrypt(ctx, std::move(stream), msgType, bypassCiphering); } std::unique_ptr Decrypt(NasSecurityContext &ctx, const nas::SecuredMmMessage &msg) diff --git a/src/ue/nas/enc.hpp b/src/ue/nas/enc.hpp index 2973585d1..e20b86830 100644 --- a/src/ue/nas/enc.hpp +++ b/src/ue/nas/enc.hpp @@ -14,7 +14,7 @@ namespace nr::ue::nas_enc { -std::unique_ptr Encrypt(NasSecurityContext &ctx, const nas::PlainMmMessage &msg); +std::unique_ptr Encrypt(NasSecurityContext &ctx, const nas::PlainMmMessage &msg, bool bypassCiphering); std::unique_ptr Decrypt(NasSecurityContext &ctx, const nas::SecuredMmMessage &msg); uint32_t ComputeMac(nas::ETypeOfIntegrityProtectionAlgorithm alg, NasCount count, bool is3gppAccess, diff --git a/src/ue/mm/access.cpp b/src/ue/nas/mm/access.cpp similarity index 59% rename from src/ue/mm/access.cpp rename to src/ue/nas/mm/access.cpp index b8ec3baca..7378072af 100644 --- a/src/ue/mm/access.cpp +++ b/src/ue/nas/mm/access.cpp @@ -9,7 +9,7 @@ #include "mm.hpp" #include -#include +#include namespace nr::ue { @@ -25,7 +25,7 @@ bool NasMm::hasEmergency() m_lastRegistrationRequest->registrationType.registrationType == nas::ERegistrationType::EMERGENCY_REGISTRATION) return true; - // TODO: Other case which is an emergency PDU session is established, or need to be established (and wanted to be + // TODO: Other case which is an emergency PDU session need to be established (and wanted to be // established soon) if (m_sm->anyEmergencySession()) return true; @@ -43,4 +43,27 @@ void NasMm::setN1Capability(bool enabled) // TODO } +bool NasMm::isInNonAllowedArea() +{ + if (!m_usim->isValid()) + return false; + if (!m_usim->m_currentPlmn.has_value()) + return false; + + auto &plmn = *m_usim->m_currentPlmn; + + if (nas::utils::ServiceAreaListForbidsPlmn(m_usim->m_serviceAreaList, nas::utils::PlmnFrom(plmn))) + return true; + + if (m_usim->m_servingCell.has_value()) + { + if (nas::utils::ServiceAreaListForbidsTai( + m_usim->m_serviceAreaList, + nas::VTrackingAreaIdentity{nas::utils::PlmnFrom(plmn), octet3{m_usim->m_servingCell->tac}})) + return true; + } + + return false; +} + } // namespace nr::ue diff --git a/src/ue/mm/auth.cpp b/src/ue/nas/mm/auth.cpp similarity index 98% rename from src/ue/mm/auth.cpp rename to src/ue/nas/mm/auth.cpp index 20f22cd76..ceaf89c2e 100644 --- a/src/ue/mm/auth.cpp +++ b/src/ue/nas/mm/auth.cpp @@ -80,7 +80,7 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms auto sqnXorAk = OctetString::Xor(m_usim->m_sqn, milenageAk); auto ckPrimeIkPrime = - keys::CalculateCkPrimeIkPrime(ck, ik, keys::ConstructServingNetworkName(m_usim->m_currentPlmn), sqnXorAk); + keys::CalculateCkPrimeIkPrime(ck, ik, keys::ConstructServingNetworkName(*m_usim->m_currentPlmn), sqnXorAk); auto &ckPrime = ckPrimeIkPrime.first; auto &ikPrime = ckPrimeIkPrime.second; @@ -198,7 +198,7 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms m_usim->m_nonCurrentNsCtx->keys.kAusf = std::move(kAusf); 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, *m_usim->m_currentPlmn, *m_usim->m_nonCurrentNsCtx); // m_logger->debug("kSeaf: %s", m_usim->m_nonCurrentNsCtx->keys.kSeaf.toHexString().c_str()); // m_logger->debug("kAmf: %s", m_usim->m_nonCurrentNsCtx->keys.kAmf.toHexString().c_str()); @@ -266,7 +266,7 @@ void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest & auto &milenageAk = milenage.ak; auto &milenageMac = milenage.mac_a; auto sqnXorAk = OctetString::Xor(m_usim->m_sqn, milenageAk); - auto snn = keys::ConstructServingNetworkName(m_usim->m_currentPlmn); + auto snn = keys::ConstructServingNetworkName(*m_usim->m_currentPlmn); // m_logger->debug("Calculated res[%s] ck[%s] ik[%s] ak[%s] mac_a[%s]", res.toHexString().c_str(), // ck.toHexString().c_str(), ik.toHexString().c_str(), milenageAk.toHexString().c_str(), @@ -287,7 +287,7 @@ void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest & m_usim->m_nonCurrentNsCtx->keys.kAusf = keys::CalculateKAusfFor5gAka(ck, 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, *m_usim->m_currentPlmn, *m_usim->m_nonCurrentNsCtx); // m_logger->debug("Derived kSeaf[%s] kAusf[%s] kAmf[%s]", // m_usim->m_nonCurrentNsCtx->keys.kSeaf.toHexString().c_str(), diff --git a/src/ue/mm/base.cpp b/src/ue/nas/mm/base.cpp similarity index 72% rename from src/ue/mm/base.cpp rename to src/ue/nas/mm/base.cpp index aaeedd39f..f9ba16c3d 100644 --- a/src/ue/mm/base.cpp +++ b/src/ue/nas/mm/base.cpp @@ -60,7 +60,23 @@ void NasMm::performMmCycle() if (m_cmState == ECmState::CM_IDLE) switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); else - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE); + { + if (m_usim->m_servingCell.has_value()) + { + 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); + } + else + { + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + } + } } else { @@ -70,11 +86,16 @@ void NasMm::performMmCycle() 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) { - long current = utils::CurrentTimeMillis(); - long elapsedMs = current - m_lastPlmnSearchTrigger; - if (elapsedMs > 50) + 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) { m_base->rrcTask->push(new NwUeNasToRrc(NwUeNasToRrc::PLMN_SEARCH_REQUEST)); m_lastPlmnSearchTrigger = current; @@ -85,7 +106,7 @@ void NasMm::performMmCycle() if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE) { if (!m_timers->t3346.isRunning()) - sendInitialRegistration(false, false); + sendInitialRegistration(EInitialRegCause::MM_DEREG_NORMAL_SERVICE); return; } @@ -94,10 +115,44 @@ void NasMm::performMmCycle() if (startECallInactivityIfNeeded()) return; } + + if (m_mmSubState == EMmSubState::MM_REGISTERED_NA) + { + if (m_usim->m_servingCell.has_value()) + { + auto cellCategory = m_usim->m_servingCell->cellCategory; + + 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); + } + } } void NasMm::switchMmState(EMmState state, EMmSubState subState) { + ERmState oldRmState = m_rmState; + if (state == EMmState::MM_DEREGISTERED || state == EMmState::MM_REGISTERED_INITIATED) + m_rmState = ERmState::RM_DEREGISTERED; + else if (state == EMmState::MM_REGISTERED || state == EMmState::MM_SERVICE_REQUEST_INITIATED || + state == EMmState::MM_DEREGISTERED_INITIATED) + m_rmState = ERmState::RM_REGISTERED; + + onSwitchRmState(oldRmState, m_rmState); + + if (m_base->nodeListener) + { + m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::RM, + ToJson(oldRmState).str(), ToJson(m_rmState).str()); + } + EMmState oldState = m_mmState; EMmSubState oldSubState = m_mmSubState; @@ -120,41 +175,26 @@ void NasMm::switchMmState(EMmState state, EMmSubState subState) triggerMmCycle(); } -void NasMm::switchRmState(ERmState state) -{ - ERmState oldState = m_rmState; - m_rmState = state; - - onSwitchRmState(oldState, m_rmState); - - if (m_base->nodeListener) - { - m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::RM, - ToJson(oldState).str(), ToJson(m_rmState).str()); - } - - // No need to log it - // m_logger->info("UE switches to state [%s]", RmStateName(state)); - - triggerMmCycle(); -} - void NasMm::switchCmState(ECmState state) { ECmState oldState = m_cmState; m_cmState = state; + if (state != oldState) + m_logger->info("UE switches to state [%s]", ToJson(state).str().c_str()); + onSwitchCmState(oldState, m_cmState); + auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::CM_STATE); + statusUpdate->cmState = m_cmState; + m_base->appTask->push(statusUpdate); + if (m_base->nodeListener) { m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::CM, ToJson(oldState).str(), ToJson(m_cmState).str()); } - if (state != oldState) - m_logger->info("UE switches to state [%s]", ToJson(state).str().c_str()); - triggerMmCycle(); } @@ -214,7 +254,6 @@ void NasMm::onSwitchCmState(ECmState oldState, ECmState newState) if (regType == nas::ERegistrationType::INITIAL_REGISTRATION || regType == nas::ERegistrationType::EMERGENCY_REGISTRATION) { - switchRmState(ERmState::RM_DEREGISTERED); switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); switchUState(E5UState::U2_NOT_UPDATED); @@ -238,8 +277,6 @@ void NasMm::onSwitchCmState(ECmState oldState, ECmState newState) else if (m_lastDeregistrationRequest->deRegistrationType.switchOff == nas::ESwitchOff::NORMAL_DE_REGISTRATION) switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); - - switchRmState(ERmState::RM_DEREGISTERED); } } } diff --git a/src/ue/mm/config.cpp b/src/ue/nas/mm/config.cpp similarity index 100% rename from src/ue/mm/config.cpp rename to src/ue/nas/mm/config.cpp diff --git a/src/ue/mm/dereg.cpp b/src/ue/nas/mm/dereg.cpp similarity index 96% rename from src/ue/mm/dereg.cpp rename to src/ue/nas/mm/dereg.cpp index 2fcc7f0b5..d40c72ff8 100644 --- a/src/ue/mm/dereg.cpp +++ b/src/ue/nas/mm/dereg.cpp @@ -9,7 +9,7 @@ #include "mm.hpp" #include #include -#include +#include namespace nr::ue { @@ -77,7 +77,6 @@ void NasMm::sendDeregistration(EDeregCause deregCause) switchMmState(EMmState::MM_DEREGISTERED_INITIATED, EMmSubState::MM_DEREGISTERED_INITIATED_NA); else { - switchRmState(ERmState::RM_DEREGISTERED); switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); } } @@ -100,8 +99,6 @@ void NasMm::receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOrigina m_sm->localReleaseAllSessions(); - switchRmState(ERmState::RM_DEREGISTERED); - if (m_lastDeregCause == EDeregCause::DISABLE_5G) switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); else @@ -158,6 +155,13 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi forceIgnoreReregistration = true; } } + // 5.6.1.7 Abnormal cases in the UE (de-registration collision) + else if (m_mmState == EMmState::MM_SERVICE_REQUEST_INITIATED) + { + // "UE shall progress the DEREGISTRATION REQUEST message and the service request procedure shall be aborted." + // (no specific action is required herein to abort service request procedure) + (void)0; + } bool reRegistrationRequired = msg.deRegistrationType.reRegistrationRequired == nas::EReRegistrationRequired::REQUIRED && @@ -174,7 +178,6 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi } sendNasMessage(nas::DeRegistrationAcceptUeTerminated{}); - switchRmState(ERmState::RM_DEREGISTERED); // "Upon sending a DEREGISTRATION ACCEPT message, the UE shall delete the rejected NSSAI as specified in // subclause 4.6.2.2." diff --git a/src/ue/mm/ecall.cpp b/src/ue/nas/mm/ecall.cpp similarity index 100% rename from src/ue/mm/ecall.cpp rename to src/ue/nas/mm/ecall.cpp diff --git a/src/ue/mm/identity.cpp b/src/ue/nas/mm/identity.cpp similarity index 83% rename from src/ue/mm/identity.cpp rename to src/ue/nas/mm/identity.cpp index 154c64a57..7e470d6b9 100644 --- a/src/ue/mm/identity.cpp +++ b/src/ue/nas/mm/identity.cpp @@ -31,6 +31,21 @@ void NasMm::receiveIdentityRequest(const nas::IdentityRequest &msg) resp.mobileIdentity.type = nas::EIdentityType::IMEISV; resp.mobileIdentity.value = *m_base->config->imeiSv; } + else if (msg.identityType.value == nas::EIdentityType::GUTI) + { + resp.mobileIdentity = m_usim->m_storedGuti; + } + else if (msg.identityType.value == nas::EIdentityType::TMSI) + { + // TMSI is already a part of GUTI + resp.mobileIdentity = m_usim->m_storedGuti; + if (resp.mobileIdentity.type != nas::EIdentityType::NO_IDENTITY) + { + resp.mobileIdentity.type = nas::EIdentityType::TMSI; + resp.mobileIdentity.gutiOrTmsi.plmn = {}; + resp.mobileIdentity.gutiOrTmsi.amfRegionId = {}; + } + } else { resp.mobileIdentity.type = nas::EIdentityType::NO_IDENTITY; @@ -56,7 +71,7 @@ nas::IE5gsMobileIdentity NasMm::getOrGenerateSuci() nas::IE5gsMobileIdentity NasMm::generateSuci() { auto &supi = m_base->config->supi; - auto &plmn = m_usim->m_currentPlmn; + auto &plmn = m_base->config->hplmn; if (!supi.has_value()) return {}; diff --git a/src/ue/mm/interface.cpp b/src/ue/nas/mm/interface.cpp similarity index 66% rename from src/ue/mm/interface.cpp rename to src/ue/nas/mm/interface.cpp index 4843f2822..d1ff872fa 100644 --- a/src/ue/mm/interface.cpp +++ b/src/ue/nas/mm/interface.cpp @@ -10,6 +10,8 @@ #include +static constexpr const int64_t SERVICE_REQUEST_NEEDED_FOR_DATA_THRESHOLD = 1000; + namespace nr::ue { @@ -22,11 +24,7 @@ void NasMm::handleRrcEvent(const NwUeRrcToNas &msg) break; } case NwUeRrcToNas::PLMN_SEARCH_RESPONSE: { - handlePlmnSearchResponse(msg.gnbName); - break; - } - case NwUeRrcToNas::PLMN_SEARCH_FAILURE: { - handlePlmnSearchFailure(); + handlePlmnSearchResponse(msg.measurements); break; } case NwUeRrcToNas::NAS_DELIVERY: { @@ -44,6 +42,14 @@ void NasMm::handleRrcEvent(const NwUeRrcToNas &msg) handleRadioLinkFailure(); break; } + case NwUeRrcToNas::SERVING_CELL_CHANGE: { + handleServingCellChange(msg.servingCell); + break; + } + case NwUeRrcToNas::PAGING: { + handlePaging(msg.pagingTmsi); + break; + } } } @@ -77,4 +83,16 @@ 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/mm/mm.hpp b/src/ue/nas/mm/mm.hpp similarity index 89% rename from src/ue/mm/mm.hpp rename to src/ue/nas/mm/mm.hpp index 73dd01a31..bb5f5a160 100644 --- a/src/ue/mm/mm.hpp +++ b/src/ue/nas/mm/mm.hpp @@ -40,8 +40,12 @@ class NasMm std::unique_ptr m_lastRegistrationRequest{}; // Most recent de-registration request std::unique_ptr m_lastDeregistrationRequest{}; + // Most recent service request + std::unique_ptr m_lastServiceRequest{}; // Indicates the last de-registration cause EDeregCause m_lastDeregCause{}; + // Indicates the last service request cause + EServiceReqCause m_lastServiceReqCause{}; // Last time PLMN search is triggered long m_lastPlmnSearchTrigger{}; // Registration attempt counter @@ -52,6 +56,8 @@ class NasMm bool m_registeredForEmergency{}; // Network feature support information nas::IE5gsNetworkFeatureSupport m_nwFeatureSupport{}; + // Last time Service Request needed indication for Data + long m_lastTimeServiceReqNeededIndForData{}; friend class UeCmdHandler; @@ -66,7 +72,6 @@ class NasMm void triggerMmCycle(); void performMmCycle(); void switchMmState(EMmState state, EMmSubState subState); - void switchRmState(ERmState state); void switchCmState(ECmState state); void switchUState(E5UState state); void onSwitchMmState(EMmState oldState, EMmState newState, EMmSubState oldSubState, EMmSubState newSubSate); @@ -80,14 +85,13 @@ class NasMm void receiveMmMessage(const nas::PlainMmMessage &msg); void receiveDlNasTransport(const nas::DlNasTransport &msg); void receiveMmStatus(const nas::FiveGMmStatus &msg); - void receiveMmCause(const nas::IE5gMmCause &msg); void sendMmStatus(nas::EMmCause cause); public: /* Registration */ void sendMobilityRegistration(ERegUpdateCause updateCause); private: /* Registration */ - void sendInitialRegistration(bool isEmergencyReg, bool dueToDereg); + void sendInitialRegistration(EInitialRegCause regCause); void receiveRegistrationAccept(const nas::RegistrationAccept &msg); void receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg); void receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg); @@ -133,6 +137,7 @@ class NasMm nas::IE5gsMobileIdentity getOrGeneratePreferredId(); private: /* Service */ + void sendServiceRequest(EServiceReqCause reqCause); void receiveServiceAccept(const nas::ServiceAccept &msg); void receiveServiceReject(const nas::ServiceReject &msg); @@ -142,16 +147,18 @@ class NasMm private: /* Radio */ void localReleaseConnection(); - void handlePlmnSearchResponse(const std::string &gnbName); - void handlePlmnSearchFailure(); + void handlePlmnSearchResponse(const std::vector &measures); void handleRrcConnectionSetup(); void handleRrcConnectionRelease(); + void handleServingCellChange(const UeCellInfo &servingCell); void handleRadioLinkFailure(); + void handlePaging(const std::vector &tmsiIds); private: /* Access Control */ bool isHighPriority(); bool hasEmergency(); void setN1Capability(bool enabled); + bool isInNonAllowedArea(); private: /* eCall */ bool startECallInactivityIfNeeded(); @@ -167,6 +174,7 @@ class NasMm void deliverUlTransport(const nas::UlNasTransport &msg); // used by SM bool isRegistered(); // used by SM bool isRegisteredForEmergency(); // used by SM + void serviceNeededForUplinkData(); // used by SM }; } // 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 new file mode 100644 index 000000000..d0bdc469f --- /dev/null +++ b/src/ue/nas/mm/radio.cpp @@ -0,0 +1,267 @@ +// +// 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 + +namespace nr::ue +{ + +void NasMm::handlePlmnSearchResponse(const std::vector &measures) +{ + // TODO + // if (m_base->nodeListener) + // m_base->nodeListener->onConnected(app::NodeType::UE, m_base->config->getNodeName(), app::NodeType::GNB, + // gnbName); + + 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; + } + + if (measures.empty()) + { + 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); + 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); + 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; + + std::vector suitable{}; + std::vector acceptable{}; + + for (auto &item : measures) + { + acceptable.push_back(item); + + 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; + } + + 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 + { + unlistedPlmn++; + continue; + } + } + + int totalForbidden = listedAsForbiddenPlmn + listedAsForbiddenTai + listedAsForbiddenServiceAreaPlmn + + listedAsForbiddenServiceAreaTai; + + 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); + }; + + 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); + }; + + if (!suitable.empty()) + { + 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); + } + else if (!acceptable.empty()) + { + std::stable_sort(acceptable.begin(), acceptable.end(), [](auto &x, auto &y) { return x.dbm >= y.dbm; }); + + logWarningAcceptable(); + + auto *w = new NwUeNasToRrc(NwUeNasToRrc::CELL_SELECTION_COMMAND); + w->cellId = acceptable[0].cellId; + w->isSuitableCell = false; + m_base->rrcTask->push(w); + } + else + { + 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) + { + logErrorSuitableAcceptable(); + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NO_CELL_AVAILABLE); + return; + } + } +} + +void NasMm::handleServingCellChange(const UeCellInfo &servingCell) +{ + if (m_cmState == ECmState::CM_CONNECTED) + { + 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; + } + + m_logger->info("Serving cell determined [%s]", servingCell.gnbName.c_str()); + + if (m_mmState == EMmState::MM_REGISTERED || m_mmState == EMmState::MM_DEREGISTERED) + { + bool isSuitable = servingCell.cellCategory == ECellCategory::SUITABLE_CELL; + + 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); + } + // todo: else, other states abnormal case + + resetRegAttemptCounter(); + + 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}}; +} + +void NasMm::handleRrcConnectionSetup() +{ + switchCmState(ECmState::CM_CONNECTED); +} + +void NasMm::handleRrcConnectionRelease() +{ + switchCmState(ECmState::CM_IDLE); +} + +void NasMm::handleRadioLinkFailure() +{ + 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); + else if (m_mmState == EMmState::MM_DEREGISTERED) + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); +} + +void NasMm::localReleaseConnection() +{ + m_logger->info("Performing local release of NAS connection"); + + m_base->rrcTask->push(new NwUeNasToRrc(NwUeNasToRrc::LOCAL_RELEASE_CONNECTION)); +} + +void NasMm::handlePaging(const std::vector &tmsiIds) +{ + if (m_usim->m_storedGuti.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) + { + tmsiMatches = true; + break; + } + } + + if (!tmsiMatches) + return; + + m_timers->t3346.stop(); + + if (m_mmState == EMmState::MM_REGISTERED_INITIATED || m_mmState == EMmState::MM_DEREGISTERED_INITIATED || + m_mmState == EMmState::MM_SERVICE_REQUEST_INITIATED) + { + m_logger->debug("Ignoring received Paging, another procedure already initiated"); + return; + } + + m_logger->debug("Responding to received Paging"); + + if (m_cmState == ECmState::CM_CONNECTED) + sendMobilityRegistration(ERegUpdateCause::PAGING_OR_NOTIFICATION); + else + sendServiceRequest(EServiceReqCause::IDLE_PAGING); +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/mm/register.cpp b/src/ue/nas/mm/register.cpp similarity index 96% rename from src/ue/mm/register.cpp rename to src/ue/nas/mm/register.cpp index 04d6b6f1a..8a327e295 100644 --- a/src/ue/mm/register.cpp +++ b/src/ue/nas/mm/register.cpp @@ -16,7 +16,7 @@ namespace nr::ue { -void NasMm::sendInitialRegistration(bool isEmergencyReg, bool dueToDereg) +void NasMm::sendInitialRegistration(EInitialRegCause regCause) { if (m_rmState != ERmState::RM_DEREGISTERED) { @@ -24,6 +24,8 @@ void NasMm::sendInitialRegistration(bool isEmergencyReg, bool dueToDereg) return; } + bool isEmergencyReg = regCause == EInitialRegCause::EMERGENCY_SERVICES; + // 5.5.1.2.7 Abnormal cases in the UE // a) Timer T3346 is running. if (m_timers->t3346.isRunning() && !isEmergencyReg && !hasEmergency()) @@ -33,7 +35,7 @@ void NasMm::sendInitialRegistration(bool isEmergencyReg, bool dueToDereg) bool highPriority = isHighPriority(); // The UE shall not start the registration procedure for initial registration in the following case - if (!highPriority && !dueToDereg) + if (!highPriority && regCause != EInitialRegCause::DUE_TO_DEREGISTRATION) { m_logger->debug("Initial registration canceled, T3346 is running"); return; @@ -123,6 +125,13 @@ void NasMm::sendMobilityRegistration(ERegUpdateCause updateCause) } } + // 5.6.1.7 Abnormal cases in the UE + // d) Registration procedure for mobility and periodic registration update is triggered + if (m_mmState == EMmState::MM_SERVICE_REQUEST_INITIATED) + { + m_timers->t3517.stop(); + } + m_logger->debug("Sending %s with update cause [%s]", nas::utils::EnumToString(updateCause == ERegUpdateCause::T3512_EXPIRY ? nas::ERegistrationType::PERIODIC_REGISTRATION_UPDATING @@ -230,13 +239,12 @@ void NasMm::receiveInitialRegistrationAccept(const nas::RegistrationAccept &msg) }); } // .. 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)); + nas::utils::AddToPlmnList(m_usim->m_equivalentPlmnList, nas::utils::PlmnFrom(*m_usim->m_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); - switchRmState(ERmState::RM_REGISTERED); 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 @@ -337,7 +345,7 @@ void NasMm::receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg }); } // .. 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)); + nas::utils::AddToPlmnList(m_usim->m_equivalentPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); // Store the service area list m_usim->m_serviceAreaList = msg.serviceAreaList.value_or(nas::IEServiceAreaList{}); @@ -347,7 +355,6 @@ void NasMm::receiveMobilityRegistrationAccept(const nas::RegistrationAccept &msg resetRegAttemptCounter(); m_serCounter = 0; switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE); - switchRmState(ERmState::RM_REGISTERED); switchUState(E5UState::U1_UPDATED); // "If the ACCEPT message included a T3512 value IE, the UE shall use the value in T3512 value IE as @@ -412,6 +419,10 @@ 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 + // registration update is successfully completed + m_serCounter = 0; + if (sendComplete) sendNasMessage(nas::RegistrationComplete{}); @@ -454,8 +465,6 @@ void NasMm::receiveInitialRegistrationReject(const nas::RegistrationReject &msg) nas::utils::EnumToString(msg.eapMessage->eap->code)); } - switchRmState(ERmState::RM_DEREGISTERED); - auto handleAbnormalCase = [this, regType, cause]() { m_logger->debug("Handling Registration Reject abnormal case"); @@ -542,12 +551,12 @@ void NasMm::receiveInitialRegistrationReject(const nas::RegistrationReject &msg) if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - // TODO add to forbidden tai + nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRoaming, *m_usim->m_currentTai); } 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)); + nas::utils::AddToPlmnList(m_usim->m_forbiddenPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); } if (cause == nas::EMmCause::CONGESTION) @@ -605,8 +614,6 @@ void NasMm::receiveMobilityRegistrationReject(const nas::RegistrationReject &msg nas::utils::EnumToString(msg.eapMessage->eap->code)); } - switchRmState(ERmState::RM_DEREGISTERED); - auto handleAbnormalCase = [this, regType, cause]() { m_logger->debug("Handling Registration Reject abnormal case"); @@ -702,12 +709,12 @@ void NasMm::receiveMobilityRegistrationReject(const nas::RegistrationReject &msg if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA) { - // TODO add to forbidden tai + nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRoaming, *m_usim->m_currentTai); } 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)); + nas::utils::AddToPlmnList(m_usim->m_forbiddenPlmnList, nas::utils::PlmnFrom(*m_usim->m_currentPlmn)); } if (cause == nas::EMmCause::CONGESTION) @@ -807,7 +814,9 @@ void NasMm::handleAbnormalMobilityRegFailure(nas::ERegistrationType regType) // "If the registration attempt counter is less than 5:" if (m_regCounter < 5) { - bool includedInTaiList = false; // TODO + 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}}); // "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" diff --git a/src/ue/mm/security.cpp b/src/ue/nas/mm/security.cpp similarity index 97% rename from src/ue/mm/security.cpp rename to src/ue/nas/mm/security.cpp index 9f850981e..6a023d9c4 100644 --- a/src/ue/mm/security.cpp +++ b/src/ue/nas/mm/security.cpp @@ -109,8 +109,8 @@ void NasMm::receiveSecurityModeCommand(const nas::SecurityModeCommand &msg) nsCtx->ciphering = msg.selectedNasSecurityAlgorithms.ciphering; keys::DeriveNasKeys(*nsCtx); - m_logger->debug("Derived NAS keys integrity[%s] ciphering[%s]", nsCtx->keys.kNasInt.toHexString().c_str(), - nsCtx->keys.kNasEnc.toHexString().c_str()); + // m_logger->debug("Derived NAS keys integrity[%s] ciphering[%s]", nsCtx->keys.kNasInt.toHexString().c_str(), + // nsCtx->keys.kNasEnc.toHexString().c_str()); m_logger->debug("Selected integrity[%d] ciphering[%d]", (int)nsCtx->integrity, (int)nsCtx->ciphering); // The UE shall in addition reset the uplink NAS COUNT counter if a) the SECURITY MODE COMMAND message is received diff --git a/src/ue/nas/mm/service.cpp b/src/ue/nas/mm/service.cpp new file mode 100644 index 000000000..27055cc9e --- /dev/null +++ b/src/ue/nas/mm/service.cpp @@ -0,0 +1,408 @@ +// +// 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 + +namespace nr::ue +{ + +void NasMm::sendServiceRequest(EServiceReqCause reqCause) +{ + m_logger->debug("Sending Service Request due to [%s]", ToJson(reqCause).str().c_str()); + + // 5.6.1.7 Abnormal cases in the UE + // a) Timer T3525 + if (m_timers->t3525.isRunning()) + { + if (reqCause != EServiceReqCause::IDLE_PAGING && + reqCause != EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP && + reqCause != EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP && !isHighPriority() && !hasEmergency() && + reqCause != EServiceReqCause::EMERGENCY_FALLBACK) + { + m_logger->debug("Service Request canceled, T3346 is running"); + return; + } + } + // c) Timer T3346 is running. + if (m_timers->t3346.isRunning()) + { + if (reqCause != EServiceReqCause::IDLE_PAGING && + reqCause != EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP && + reqCause != EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP && !isHighPriority() && !hasEmergency() && + reqCause != EServiceReqCause::EMERGENCY_FALLBACK) + { + m_logger->debug("Service Request canceled, T3346 is running"); + return; + } + } + + auto request = std::make_unique(); + + if (reqCause == EServiceReqCause::IDLE_PAGING) + { + if (m_sm->anyUplinkDataPending()) + { + request->uplinkDataStatus = nas::IEUplinkDataStatus{}; + request->uplinkDataStatus->psi = m_sm->getUplinkDataStatus(); + } + request->serviceType.serviceType = nas::EServiceType::MOBILE_TERMINATED_SERVICES; + } + else if (reqCause == EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP) + { + // TODO: This case not handled since the non-3gpp access is not supported + m_logger->err("Unhandled case in ServiceRequest"); + } + else if (reqCause == EServiceReqCause::IDLE_UPLINK_SIGNAL_PENDING) + { + if (isHighPriority()) + request->serviceType.serviceType = nas::EServiceType::HIGH_PRIORITY_ACCESS; + else if (hasEmergency()) + request->serviceType.serviceType = nas::EServiceType::EMERGENCY_SERVICES; + else + request->serviceType.serviceType = nas::EServiceType::SIGNALLING; + + if (isInNonAllowedArea()) + { + // TODO: This case not handled since PS data off not supported + m_logger->err("Unhandled case in ServiceRequest"); + } + } + else if (reqCause == EServiceReqCause::IDLE_UPLINK_DATA_PENDING || + reqCause == EServiceReqCause::CONNECTED_UPLINK_DATA_PENDING) + { + request->uplinkDataStatus = nas::IEUplinkDataStatus{}; + request->uplinkDataStatus->psi = m_sm->getUplinkDataStatus(); + + if (isHighPriority()) + request->serviceType.serviceType = nas::EServiceType::HIGH_PRIORITY_ACCESS; + else if (m_sm->anyEmergencyUplinkDataPending()) + request->serviceType.serviceType = nas::EServiceType::EMERGENCY_SERVICES; + else + request->serviceType.serviceType = nas::EServiceType::DATA; + } + else if (reqCause == EServiceReqCause::NON_3GPP_AS_ESTABLISHED) + { + // TODO: This case not handled since the non-3gpp access is not supported + m_logger->err("Unhandled case found in ServiceRequest"); + } + else if (reqCause == EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP) + { + // TODO: This case not handled since the non-3gpp access is not supported + m_logger->err("Unhandled case occurred in ServiceRequest"); + } + else if (reqCause == EServiceReqCause::EMERGENCY_FALLBACK) + { + request->serviceType.serviceType = nas::EServiceType::EMERGENCY_SERVICES_FALLBACK; + } + else if (reqCause == EServiceReqCause::FALLBACK_INDICATION) + { + if (isHighPriority()) + request->serviceType.serviceType = nas::EServiceType::HIGH_PRIORITY_ACCESS; + else + { + // TODO: fallback indication not supported yet + } + } + + // Assign ngKSI + if (m_usim->m_currentNsCtx) + { + request->ngKSI.tsc = m_usim->m_currentNsCtx->tsc; + request->ngKSI.ksi = m_usim->m_currentNsCtx->ngKsi; + } + + // Assign TMSI (TMSI is a part of GUTI) + request->tmsi = m_usim->m_storedGuti; + if (request->tmsi.type != nas::EIdentityType::NO_IDENTITY) + { + request->tmsi.type = nas::EIdentityType::TMSI; + request->tmsi.gutiOrTmsi.plmn = {}; + request->tmsi.gutiOrTmsi.amfRegionId = {}; + } + + // Assign PDU session status + request->pduSessionStatus = nas::IEPduSessionStatus{}; + request->pduSessionStatus->psi = m_sm->getPduSessionStatus(); + + // Send the message and process the timers + sendNasMessage(*request); + m_lastServiceRequest = std::move(request); + m_lastServiceReqCause = reqCause; + m_timers->t3517.start(); + switchMmState(EMmState::MM_SERVICE_REQUEST_INITIATED, EMmSubState::MM_SERVICE_REQUEST_INITIATED_NA); +} + +void NasMm::receiveServiceAccept(const nas::ServiceAccept &msg) +{ + if (m_mmState != EMmState::MM_SERVICE_REQUEST_INITIATED) + { + m_logger->warn("Service Accept ignored since the MM state is not MM_SERVICE_REQUEST_INITIATED"); + sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE); + return; + } + + if (m_lastServiceReqCause != EServiceReqCause::EMERGENCY_FALLBACK) + { + m_logger->info("Service Accept received"); + m_serCounter = 0; + m_timers->t3517.stop(); + switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + } + else + { + // todo: emergency fallback + } + + // Handle PDU session status + if (msg.pduSessionStatus.has_value()) + { + auto statusInUe = m_sm->getPduSessionStatus(); + auto statusInNw = msg.pduSessionStatus->psi; + for (int i = 1; i < 16; i++) + if (statusInUe[i] && !statusInNw[i]) + m_sm->localReleaseSession(i); + } + + // Handle PDU session reactivation result + if (msg.pduSessionReactivationResult.has_value()) + { + // todo: not handled since non-3gpp access is not supported + } + + // Handle PDU session reactivation result error cause + if (msg.pduSessionReactivationResultErrorCause.has_value()) + { + for (auto &item : msg.pduSessionReactivationResultErrorCause->values) + m_logger->err("PDU session reactivation result error PSI[%d] cause[%s]", item.pduSessionId, + nas::utils::EnumToString(item.causeValue)); + } + + // Handle EAP message + if (msg.eapMessage.has_value()) + { + if (msg.eapMessage->eap->code == eap::ECode::FAILURE) + receiveEapFailureMessage(*msg.eapMessage->eap); + else + m_logger->warn("Network sent EAP with inconvenient type in ServiceAccept, ignoring EAP IE."); + } +} + +void NasMm::receiveServiceReject(const nas::ServiceReject &msg) +{ + if (m_mmState != EMmState::MM_SERVICE_REQUEST_INITIATED || !m_lastServiceRequest) + { + m_logger->warn("Service Reject ignored since the MM state is not MM_SERVICE_REQUEST_INITIATED"); + sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE); + return; + } + + if (msg.sht == nas::ESecurityHeaderType::NOT_PROTECTED) + m_logger->warn("Not protected Service Reject message received"); + + // "On receipt of the SERVICE REJECT message, if the UE is in state 5GMM-SERVICE-REQUEST-INITIATED and the message + // is integrity protected, the UE shall reset the service request attempt counter and stop timer T3517 if running." + m_serCounter = 0; + m_timers->t3517.stop(); + + auto cause = msg.mmCause.value; + m_logger->err("Service Reject received with cause [%s]", nas::utils::EnumToString(cause)); + + auto handleAbnormalCase = [this]() { + m_logger->debug("Handling Service Reject abnormal case"); + + switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + m_timers->t3517.stop(); + }; + + // Handle PDU session status + if (msg.pduSessionStatus.has_value() && msg.sht != nas::ESecurityHeaderType::NOT_PROTECTED) + { + auto statusInUe = m_sm->getPduSessionStatus(); + auto statusInNw = msg.pduSessionStatus->psi; + for (int i = 1; i < 16; i++) + if (statusInUe[i] && !statusInNw[i]) + m_sm->localReleaseSession(i); + } + + // Handle EAP message + if (msg.eapMessage.has_value()) + { + if (msg.eapMessage->eap->code == eap::ECode::FAILURE) + receiveEapFailureMessage(*msg.eapMessage->eap); + else + m_logger->warn("Network sent EAP with inconvenient type in ServiceReject, ignoring EAP IE."); + } + + /* Handle MM Cause */ + + 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::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) + switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); + + if (cause == nas::EMmCause::UE_IDENTITY_CANNOT_BE_DERIVED_FROM_NETWORK || + cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) + switchUState(E5UState::U2_NOT_UPDATED); + + if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || + cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED || + cause == nas::EMmCause::UE_IDENTITY_CANNOT_BE_DERIVED_FROM_NETWORK || + 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_usim->m_currentNsCtx = {}; + m_usim->m_nonCurrentNsCtx = {}; + } + + if (cause == nas::EMmCause::IMPLICITY_DEREGISTERED) + { + m_usim->m_nonCurrentNsCtx = {}; + } + + if (cause == nas::EMmCause::PLMN_NOT_ALLOWED) + { + m_usim->m_equivalentPlmnList = {}; + } + + 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)); + } + + if (cause == nas::EMmCause::TA_NOT_ALLOWED) + { + nas::utils::AddToTaiList(m_usim->m_forbiddenTaiListRps, *m_usim->m_currentTai); + } + + 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); + } + + if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || + cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED) + { + m_usim->invalidate(); + } + + if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME || + 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); + } + + if (cause == nas::EMmCause::IMPLICITY_DEREGISTERED) + { + switchMmState(EMmState::MM_DEREGISTERED, 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); + } + + if (cause == nas::EMmCause::TA_NOT_ALLOWED) + { + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); + } + + if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA) + { + switchMmState(EMmState::MM_REGISTERED, 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); + } + + if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED) + { + 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); + } + + if (cause == nas::EMmCause::IMPLICITY_DEREGISTERED) + { + if (!hasEmergency()) + sendInitialRegistration(EInitialRegCause::DUE_TO_SERVICE_REJECT); + } + + if (cause == nas::EMmCause::CONGESTION) + { + if (msg.t3346Value.has_value() && nas::utils::HasValue(*msg.t3346Value)) + { + if (!hasEmergency()) + { + switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + + m_timers->t3517.stop(); + } + + m_timers->t3346.stop(); + + if (msg.sht != nas::ESecurityHeaderType::NOT_PROTECTED) + m_timers->t3346.start(*msg.t3346Value); + else + m_timers->t3346.start(nas::IEGprsTimer2{5}); + } + else + { + handleAbnormalCase(); + } + } + + if (cause == nas::EMmCause::RESTRICTED_SERVICE_AREA) + { + switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NON_ALLOWED_SERVICE); + + if (m_lastServiceRequest->serviceType.serviceType != nas::EServiceType::ELEVATED_SIGNALLING) + sendMobilityRegistration(ERegUpdateCause::RESTRICTED_SERVICE_AREA); + } + + if (hasEmergency()) + { + // Spec says that upper layers should be informed as well, for additional action for emergency + // registration, but no need for now. + handleAbnormalCase(); + } + + if (cause != nas::EMmCause::ILLEGAL_UE && cause != nas::EMmCause::ILLEGAL_ME && + cause != nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED && + cause != nas::EMmCause::UE_IDENTITY_CANNOT_BE_DERIVED_FROM_NETWORK && + cause != nas::EMmCause::IMPLICITY_DEREGISTERED && 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 && cause != nas::EMmCause::CONGESTION && + cause != nas::EMmCause::N1_MODE_NOT_ALLOWED && cause != nas::EMmCause::RESTRICTED_SERVICE_AREA && + cause != nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED) + { + handleAbnormalCase(); + } +} + +} // namespace nr::ue diff --git a/src/ue/mm/slice.cpp b/src/ue/nas/mm/slice.cpp similarity index 98% rename from src/ue/mm/slice.cpp rename to src/ue/nas/mm/slice.cpp index 34a42dc11..7b048e0e6 100644 --- a/src/ue/mm/slice.cpp +++ b/src/ue/nas/mm/slice.cpp @@ -9,7 +9,7 @@ #include "mm.hpp" #include #include -#include +#include namespace nr::ue { diff --git a/src/ue/mm/timer.cpp b/src/ue/nas/mm/timer.cpp similarity index 80% rename from src/ue/mm/timer.cpp rename to src/ue/nas/mm/timer.cpp index 7bc2f36b7..2f75e2b8f 100644 --- a/src/ue/mm/timer.cpp +++ b/src/ue/nas/mm/timer.cpp @@ -29,7 +29,7 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE) { logExpired(); - sendInitialRegistration(false, false); + sendInitialRegistration(EInitialRegCause::T3346_EXPIRY); } break; } @@ -54,7 +54,6 @@ 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.. - switchRmState(ERmState::RM_DEREGISTERED); switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); switchUState(E5UState::U2_NOT_UPDATED); @@ -87,6 +86,26 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) } break; } + case 3517: { + if (m_mmState == EMmState::MM_SERVICE_REQUEST_INITIATED) + { + logExpired(); + + switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NA); + + if (m_cmState == ECmState::CM_IDLE && m_lastServiceReqCause != EServiceReqCause::EMERGENCY_FALLBACK) + { + if (!hasEmergency() && !isHighPriority() && m_lastServiceReqCause != EServiceReqCause::IDLE_PAGING && + m_lastServiceReqCause != EServiceReqCause::IDLE_3GPP_NOTIFICATION_N3GPP && + m_lastServiceReqCause != EServiceReqCause::CONNECTED_3GPP_NOTIFICATION_N3GPP) + m_serCounter++; + + if (m_serCounter >= 5) + m_timers->t3525.start(); + } + } + break; + } case 3519: { m_usim->m_storedSuci = {}; break; @@ -112,7 +131,7 @@ void NasMm::onTimerExpire(nas::NasTimer &timer) if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED && m_lastDeregistrationRequest != nullptr) { logExpired(); - m_logger->debug("Retrying de-registration request"); + m_logger->debug("Retransmitting De-registration Request due to T3521 expiry"); sendNasMessage(*m_lastDeregistrationRequest); m_timers->t3521.start(false); diff --git a/src/ue/mm/transport.cpp b/src/ue/nas/mm/transport.cpp similarity index 75% rename from src/ue/mm/transport.cpp rename to src/ue/nas/mm/transport.cpp index 3ec6123f6..0060e5a0b 100644 --- a/src/ue/mm/transport.cpp +++ b/src/ue/nas/mm/transport.cpp @@ -10,22 +10,50 @@ #include #include #include +#include #include -#include namespace nr::ue { +static bool IsInitialNasMessage(const nas::PlainMmMessage &msg) +{ + auto msgType = msg.messageType; + return msgType == nas::EMessageType::REGISTRATION_REQUEST || + msgType == nas::EMessageType::DEREGISTRATION_REQUEST_UE_ORIGINATING || + msgType == nas::EMessageType::SERVICE_REQUEST; +} + +static bool IsAcceptedWithoutIntegrity(const nas::PlainMmMessage &msg) +{ + auto msgType = msg.messageType; + return msgType == nas::EMessageType::IDENTITY_REQUEST || msgType == nas::EMessageType::AUTHENTICATION_REQUEST || + msgType == nas::EMessageType::AUTHENTICATION_RESULT || msgType == nas::EMessageType::AUTHENTICATION_REJECT || + msgType == nas::EMessageType::REGISTRATION_REJECT || + msgType == nas::EMessageType::DEREGISTRATION_ACCEPT_UE_TERMINATED || + msgType == nas::EMessageType::SERVICE_REJECT; +} + +static bool BypassCiphering(const nas::PlainMmMessage &msg) +{ + return IsInitialNasMessage(msg); +} + void NasMm::sendNasMessage(const nas::PlainMmMessage &msg) { - // TODO trigger on send + 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; + } OctetString pdu{}; - if (m_usim->m_currentNsCtx && - (m_usim->m_currentNsCtx->integrity != nas::ETypeOfIntegrityProtectionAlgorithm::IA0 || - m_usim->m_currentNsCtx->ciphering != nas::ETypeOfCipheringAlgorithm::EA0)) + if (m_usim->m_currentNsCtx && (m_usim->m_currentNsCtx->integrity != nas::ETypeOfIntegrityProtectionAlgorithm::IA0 || + m_usim->m_currentNsCtx->ciphering != nas::ETypeOfCipheringAlgorithm::EA0)) { - auto secured = nas_enc::Encrypt(*m_usim->m_currentNsCtx, msg); + auto secured = nas_enc::Encrypt(*m_usim->m_currentNsCtx, msg, BypassCiphering(msg)); nas::EncodeNasMessage(*secured, pdu); } else @@ -61,9 +89,9 @@ void NasMm::receiveNasMessage(const nas::NasMessage &msg) if (mmMsg.sht == nas::ESecurityHeaderType::NOT_PROTECTED) { - // If any NAS signalling message is received as not integrity protected even though the secure exchange of NAS - // messages has been established by the network, then the NAS shall discard this message - if (m_usim->m_currentNsCtx) + // If any NAS signalling message is received as not integrity protected even though the secure exchange of NAS + // messages has been established by the network, then the NAS shall discard this message + if (m_usim->m_currentNsCtx && !IsAcceptedWithoutIntegrity((const nas::PlainMmMessage &)mmMsg)) { m_logger->err( "Not integrity protected NAS message received after security establishment. Ignoring received " @@ -103,7 +131,7 @@ void NasMm::receiveNasMessage(const nas::NasMessage &msg) if (!m_usim->m_currentNsCtx) { - m_logger->warn("Secured NAS message received while no security context"); + m_logger->err("Secured NAS message received while no security context"); sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE); return; } @@ -191,7 +219,7 @@ void NasMm::receiveMmMessage(const nas::PlainMmMessage &msg) receiveDlNasTransport((const nas::DlNasTransport &)msg); break; default: - m_logger->err("Unhandled NAS MM message received: %d", (int)msg.messageType); + m_logger->err("Unhandled NAS MM message received [%d]", (int)msg.messageType); break; } } @@ -200,7 +228,7 @@ void NasMm::receiveDlNasTransport(const nas::DlNasTransport &msg) { if (msg.payloadContainerType.payloadContainerType != nas::EPayloadContainerType::N1_SM_INFORMATION) { - m_logger->err("Unhandled DL NAS Transport type: %d", (int)msg.payloadContainerType.payloadContainerType); + m_logger->err("Unhandled DL NAS Transport type [%d]", (int)msg.payloadContainerType.payloadContainerType); return; } @@ -217,7 +245,7 @@ void NasMm::receiveDlNasTransport(const nas::DlNasTransport &msg) void NasMm::sendMmStatus(nas::EMmCause cause) { - m_logger->warn("Sending MM Status with cause %s", nas::utils::EnumToString(cause)); + m_logger->warn("Sending MM Status with cause [%s]", nas::utils::EnumToString(cause)); nas::FiveGMmStatus m; m.mmCause.value = cause; @@ -226,12 +254,7 @@ void NasMm::sendMmStatus(nas::EMmCause cause) void NasMm::receiveMmStatus(const nas::FiveGMmStatus &msg) { - receiveMmCause(msg.mmCause); -} - -void NasMm::receiveMmCause(const nas::IE5gMmCause &msg) -{ - m_logger->err("MM cause received: %s", nas::utils::EnumToString(msg.value)); + m_logger->err("MM status received with cause [%s]", nas::utils::EnumToString(msg.mmCause.value)); } } // namespace nr::ue diff --git a/src/ue/sm/allocation.cpp b/src/ue/nas/sm/allocation.cpp similarity index 100% rename from src/ue/sm/allocation.cpp rename to src/ue/nas/sm/allocation.cpp diff --git a/src/ue/sm/base.cpp b/src/ue/nas/sm/base.cpp similarity index 100% rename from src/ue/sm/base.cpp rename to src/ue/nas/sm/base.cpp diff --git a/src/ue/sm/establishment.cpp b/src/ue/nas/sm/establishment.cpp similarity index 99% rename from src/ue/sm/establishment.cpp rename to src/ue/nas/sm/establishment.cpp index daffb55a1..62a04dd7f 100644 --- a/src/ue/sm/establishment.cpp +++ b/src/ue/nas/sm/establishment.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace nr::ue { @@ -89,6 +89,7 @@ void NasSm::sendEstablishmentRequest(const SessionConfig &config) ps->sessionAmbr = {}; ps->authorizedQoSFlowDescriptions = {}; ps->pduAddress = {}; + ps->uplinkPending = false; /* Make PCO */ nas::ProtocolConfigurationOptions opt{}; diff --git a/src/ue/sm/interface.cpp b/src/ue/nas/sm/interface.cpp similarity index 96% rename from src/ue/sm/interface.cpp rename to src/ue/nas/sm/interface.cpp index cfeae4554..139628469 100644 --- a/src/ue/sm/interface.cpp +++ b/src/ue/nas/sm/interface.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace nr::ue { diff --git a/src/ue/sm/procedure.cpp b/src/ue/nas/sm/procedure.cpp similarity index 98% rename from src/ue/sm/procedure.cpp rename to src/ue/nas/sm/procedure.cpp index 4ad14c51e..617b80fd6 100644 --- a/src/ue/sm/procedure.cpp +++ b/src/ue/nas/sm/procedure.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace nr::ue { diff --git a/src/ue/sm/release.cpp b/src/ue/nas/sm/release.cpp similarity index 99% rename from src/ue/sm/release.cpp rename to src/ue/nas/sm/release.cpp index f85cf90ce..b07c44641 100644 --- a/src/ue/sm/release.cpp +++ b/src/ue/nas/sm/release.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace nr::ue { diff --git a/src/ue/sm/resource.cpp b/src/ue/nas/sm/resource.cpp similarity index 55% rename from src/ue/sm/resource.cpp rename to src/ue/nas/sm/resource.cpp index f84d59b53..34ce0b4eb 100644 --- a/src/ue/sm/resource.cpp +++ b/src/ue/nas/sm/resource.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace nr::ue { @@ -58,4 +59,50 @@ bool NasSm::anyEmergencySession() [](auto &ps) { return ps->psState != EPsState::INACTIVE && ps->isEmergency; }); } +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(); +} + +bool NasSm::anyUplinkDataPending() +{ + auto status = getUplinkDataStatus(); + for (int i = 1; i < 16; i++) + if (status[i]) + return true; + return false; +} + +bool NasSm::anyEmergencyUplinkDataPending() +{ + auto status = getUplinkDataStatus(); + for (int i = 1; i < 16; i++) + if (status[i] && m_pduSessions[i]->isEmergency) + return true; + return false; +} + +std::bitset<16> NasSm::getUplinkDataStatus() +{ + std::bitset<16> res{}; + for (int i = 1; i < 16; i++) + if (m_pduSessions[i]->psState == EPsState::ACTIVE && m_pduSessions[i]->uplinkPending) + res[i] = true; + return res; +} + +std::bitset<16> NasSm::getPduSessionStatus() +{ + std::bitset<16> res{}; + for (int i = 1; i < 16; i++) + if (m_pduSessions[i]->psState == EPsState::ACTIVE) + res[i] = true; + return res; +} + } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/sm/sm.hpp b/src/ue/nas/sm/sm.hpp similarity index 91% rename from src/ue/sm/sm.hpp rename to src/ue/nas/sm/sm.hpp index 8d91e7ee4..f5a39523e 100644 --- a/src/ue/sm/sm.hpp +++ b/src/ue/nas/sm/sm.hpp @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include #include @@ -49,6 +50,11 @@ class NasSm void localReleaseSession(int psi); void localReleaseAllSessions(); bool anyEmergencySession(); + void handleUplinkStatusChange(int psi, bool isPending); + bool anyUplinkDataPending(); + bool anyEmergencyUplinkDataPending(); + std::bitset<16> getUplinkDataStatus(); + std::bitset<16> getPduSessionStatus(); /* Session Release */ void sendReleaseRequest(int psi); diff --git a/src/ue/sm/timer.cpp b/src/ue/nas/sm/timer.cpp similarity index 98% rename from src/ue/sm/timer.cpp rename to src/ue/nas/sm/timer.cpp index 5b5ede1ac..2b8e418a5 100644 --- a/src/ue/sm/timer.cpp +++ b/src/ue/nas/sm/timer.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace nr::ue { diff --git a/src/ue/sm/transport.cpp b/src/ue/nas/sm/transport.cpp similarity index 96% rename from src/ue/sm/transport.cpp rename to src/ue/nas/sm/transport.cpp index 2b5d5b74d..6a406b6e6 100644 --- a/src/ue/sm/transport.cpp +++ b/src/ue/nas/sm/transport.cpp @@ -8,7 +8,7 @@ #include "sm.hpp" #include -#include +#include namespace nr::ue { @@ -70,7 +70,7 @@ void NasSm::receiveSmMessage(const nas::SmMessage &msg) void NasSm::receiveSmStatus(const nas::FiveGSmStatus &msg) { - m_logger->err("SM Status received: %s", nas::utils::EnumToString(msg.smCause.value)); + m_logger->err("SM Status received with cause [%s]", nas::utils::EnumToString(msg.smCause.value)); if (msg.smCause.value == nas::ESmCause::INVALID_PTI_VALUE) { diff --git a/src/ue/nas/task.cpp b/src/ue/nas/task.cpp index fa17a8319..302beb8a4 100644 --- a/src/ue/nas/task.cpp +++ b/src/ue/nas/task.cpp @@ -28,10 +28,7 @@ NasTask::NasTask(TaskBase *base) : base{base}, timers{} void NasTask::onStart() { - logger->debug("NAS layer started"); - usim->initialize(base->config->supi.has_value(), base->config->initials); - usim->m_currentPlmn = base->config->hplmn; // TODO: normally assigned after plmn search sm->onStart(mm); mm->onStart(sm, usim); @@ -67,18 +64,34 @@ void NasTask::onLoop() auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeNasToNas::PERFORM_MM_CYCLE: + case NwUeNasToNas::PERFORM_MM_CYCLE: { mm->handleNasEvent(*w); break; - case NwUeNasToNas::NAS_TIMER_EXPIRE: + } + case NwUeNasToNas::NAS_TIMER_EXPIRE: { if (w->timer->isMmTimer()) mm->handleNasEvent(*w); else sm->handleNasEvent(*w); break; - case NwUeNasToNas::ESTABLISH_INITIAL_SESSIONS: + } + case NwUeNasToNas::ESTABLISH_INITIAL_SESSIONS: { sm->establishInitialSessions(); break; + } + default: + break; + } + break; + } + case NtsMessageType::UE_APP_TO_NAS: { + auto *w = dynamic_cast(msg); + switch (w->present) + { + case NwUeAppToNas::UPLINK_STATUS_CHANGE: { + sm->handleUplinkStatusChange(w->psi, w->isPending); + break; + } default: break; } diff --git a/src/ue/nas/task.hpp b/src/ue/nas/task.hpp index b726ac832..d3e575be0 100644 --- a/src/ue/nas/task.hpp +++ b/src/ue/nas/task.hpp @@ -12,9 +12,9 @@ #include #include #include -#include +#include +#include #include -#include #include #include diff --git a/src/ue/nas/usim.hpp b/src/ue/nas/usim.hpp index 0cfadb838..50c5f3c13 100644 --- a/src/ue/nas/usim.hpp +++ b/src/ue/nas/usim.hpp @@ -24,18 +24,21 @@ class Usim bool m_isValid{}; public: - // Location related - nas::IE5gsMobileIdentity m_storedGuti{}; - std::optional m_lastVisitedRegisteredTai{}; + // State related E5UState m_uState{}; // Identity related nas::IE5gsMobileIdentity m_storedSuci{}; + nas::IE5gsMobileIdentity m_storedGuti{}; // Plmn related - Plmn m_currentPlmn{}; + std::optional m_servingCell{}; + std::optional m_currentPlmn{}; + std::optional m_currentTai{}; + std::optional m_lastVisitedRegisteredTai{}; nas::IE5gsTrackingAreaIdentityList m_taiList{}; - nas::IE5gsTrackingAreaIdentityList m_forbiddenTaiList{}; + nas::IE5gsTrackingAreaIdentityList m_forbiddenTaiListRoaming{}; // 5GS 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{}; diff --git a/src/ue/nts.hpp b/src/ue/nts.hpp index 725387221..0946d49ce 100644 --- a/src/ue/nts.hpp +++ b/src/ue/nts.hpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -22,97 +21,6 @@ namespace nr::ue { -struct NwUeMrToMr : NtsMessage -{ - enum PR - { - RLS_CONNECTED, - RLS_RELEASED, - RLS_SEARCH_FAILURE, - RLS_START_WAITING_TIMER, - RLS_SEND_OVER_UDP, - RLS_RECEIVE_OVER_UDP - } present; - - // RLS_CONNECTED - std::string gnbName{}; - - // RLS_RELEASED - // RLS_SEARCH_FAILURE - rls::ECause cause{}; - - // RLS_START_WAITING_TIMER - int period{}; - - // RLS_SEND_OVER_UDP - InetAddress address{}; - - // RLS_RECEIVE_OVER_UDP - rls::EPayloadType type{}; - - // RLS_SEND_OVER_UDP - // RLS_RECEIVE_OVER_UDP - OctetString pdu{}; - - explicit NwUeMrToMr(PR present) : NtsMessage(NtsMessageType::UE_MR_TO_MR), present(present) - { - } -}; - -struct NwUeMrToRrc : NtsMessage -{ - enum PR - { - PLMN_SEARCH_RESPONSE, - PLMN_SEARCH_FAILURE, - RRC_PDU_DELIVERY, - RADIO_LINK_FAILURE - } present; - - // PLMN_SEARCH_RESPONSE - std::string gnbName{}; - - // RRC_PDU_DELIVERY - rrc::RrcChannel channel{}; - OctetString pdu{}; - - explicit NwUeMrToRrc(PR present) : NtsMessage(NtsMessageType::UE_MR_TO_RRC), present(present) - { - } -}; - -struct NwUeMrToApp : NtsMessage -{ - enum PR - { - DATA_PDU_DELIVERY - } present; - - // DATA_PDU_DELIVERY - int psi{}; - OctetString data{}; - - explicit NwUeMrToApp(PR present) : NtsMessage(NtsMessageType::UE_MR_TO_APP), present(present) - { - } -}; - -struct NwAppToMr : NtsMessage -{ - enum PR - { - DATA_PDU_DELIVERY - } present; - - // DATA_PDU_DELIVERY - int psi{}; - OctetString data{}; - - explicit NwAppToMr(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_MR), present(present) - { - } -}; - struct NwAppToTun : NtsMessage { enum PR @@ -155,17 +63,24 @@ struct NwUeRrcToNas : NtsMessage { NAS_DELIVERY, PLMN_SEARCH_RESPONSE, - PLMN_SEARCH_FAILURE, RRC_CONNECTION_SETUP, RRC_CONNECTION_RELEASE, RADIO_LINK_FAILURE, + SERVING_CELL_CHANGE, + PAGING, } present; // NAS_DELIVERY OctetString nasPdu{}; // PLMN_SEARCH_RESPONSE - std::string gnbName{}; + std::vector measurements{}; + + // SERVING_CELL_CHANGE + UeCellInfo servingCell{}; + + // PAGING + std::vector pagingTmsi{}; explicit NwUeRrcToNas(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_NAS), present(present) { @@ -179,7 +94,8 @@ struct NwUeNasToRrc : NtsMessage PLMN_SEARCH_REQUEST, LOCAL_RELEASE_CONNECTION, INITIAL_NAS_DELIVERY, - UPLINK_NAS_DELIVERY + UPLINK_NAS_DELIVERY, + CELL_SELECTION_COMMAND, } present; // INITIAL_NAS_DELIVERY @@ -189,28 +105,59 @@ struct NwUeNasToRrc : NtsMessage // INITIAL_NAS_DELIVERY long rrcEstablishmentCause{}; + // CELL_SELECTION_COMMAND + GlobalNci cellId{}; + bool isSuitableCell{}; // otherwise 'acceptable' + explicit NwUeNasToRrc(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_RRC), present(present) { } }; -struct NwUeRrcToMr : NtsMessage +struct NwUeRrcToRls : NtsMessage { enum PR { PLMN_SEARCH_REQUEST, + CELL_SELECTION_COMMAND, RRC_PDU_DELIVERY, - RRC_CONNECTION_RELEASE, + RESET_STI, } present; + // CELL_SELECTION_COMMAND + GlobalNci cellId{}; + bool isSuitableCell{}; // otherwise 'acceptable' + // RRC_PDU_DELIVERY rrc::RrcChannel channel{}; OctetString pdu{}; - // RRC_CONNECTION_RELEASE - rls::ECause cause{}; + explicit NwUeRrcToRls(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_RLS), present(present) + { + } +}; - explicit NwUeRrcToMr(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_MR), present(present) +struct NwUeRlsToRrc : NtsMessage +{ + enum PR + { + PLMN_SEARCH_RESPONSE, + SERVING_CELL_CHANGE, + RRC_PDU_DELIVERY, + RADIO_LINK_FAILURE + } present; + + // PLMN_SEARCH_RESPONSE + std::vector measurements{}; + + // SERVING_CELL_CHANGE + UeCellInfo servingCell{}; + + // RRC_PDU_DELIVERY + rrc::RrcChannel channel{}; + OctetString pdu{}; + + explicit NwUeRlsToRrc(PR present) : NtsMessage(NtsMessageType::UE_RLS_TO_RRC), present(present) { } }; @@ -244,10 +191,59 @@ struct NwUeNasToApp : NtsMessage } }; +struct NwUeAppToNas : NtsMessage +{ + enum PR + { + UPLINK_STATUS_CHANGE, + } present; + + // UPLINK_STATUS_CHANGE + int psi{}; + bool isPending{}; + + explicit NwUeAppToNas(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_NAS), present(present) + { + } +}; + +struct NwUeAppToRls : NtsMessage +{ + enum PR + { + DATA_PDU_DELIVERY + } present; + + // DATA_PDU_DELIVERY + int psi{}; + OctetString pdu{}; + + explicit NwUeAppToRls(PR present) : NtsMessage(NtsMessageType::UE_APP_TO_RLS), present(present) + { + } +}; + +struct NwUeRlsToApp : NtsMessage +{ + enum PR + { + DATA_PDU_DELIVERY + } present; + + // DATA_PDU_DELIVERY + int psi{}; + OctetString pdu{}; + + explicit NwUeRlsToApp(PR present) : NtsMessage(NtsMessageType::UE_RLS_TO_APP), present(present) + { + } +}; + struct NwUeStatusUpdate : NtsMessage { static constexpr const int SESSION_ESTABLISHMENT = 1; static constexpr const int SESSION_RELEASE = 2; + static constexpr const int CM_STATE = 3; const int what{}; @@ -257,6 +253,9 @@ struct NwUeStatusUpdate : NtsMessage // SESSION_RELEASE int psi{}; + // CM_STATE + ECmState cmState{}; + explicit NwUeStatusUpdate(const int what) : NtsMessage(NtsMessageType::UE_STATUS_UPDATE), what(what) { } diff --git a/src/ue/rls/measurement.cpp b/src/ue/rls/measurement.cpp new file mode 100644 index 000000000..38a5e1f25 --- /dev/null +++ b/src/ue/rls/measurement.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 + +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 new file mode 100644 index 000000000..a1de7d1ab --- /dev/null +++ b/src/ue/rls/task.cpp @@ -0,0 +1,133 @@ +// +// 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 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{} +{ + 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_sti = utils::Random64(); +} + +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(); +} + +void UeRlsTask::onLoop() +{ + NtsMessage *msg = take(); + if (!msg) + return; + + switch (msg->msgType) + { + case NtsMessageType::UE_RRC_TO_RLS: { + auto *w = dynamic_cast(msg); + switch (w->present) + { + case NwUeRrcToRls::PLMN_SEARCH_REQUEST: + plmnSearchRequested(); + break; + case NwUeRrcToRls::CELL_SELECTION_COMMAND: + handleCellSelectionCommand(w->cellId, w->isSuitableCell); + break; + case NwUeRrcToRls::RRC_PDU_DELIVERY: + deliverUplinkPdu(rls::EPduType::RRC, std::move(w->pdu), + OctetString::FromOctet4(static_cast(w->channel))); + break; + case NwUeRrcToRls::RESET_STI: + m_sti = utils::Random64(); + break; + } + break; + } + case NtsMessageType::UE_APP_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))); + break; + } + } + break; + } + case NtsMessageType::TIMER_EXPIRED: { + auto *w = dynamic_cast(msg); + if (w->timerId == TIMER_ID_MEASUREMENT) + { + setTimer(TIMER_ID_MEASUREMENT, m_measurementPeriod); + onMeasurement(); + } + 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) + { + m_logger->err("Unable to decode RLS message"); + break; + } + receiveRlsMessage(w->fromAddress, *rlsMsg); + break; + } + default: + m_logger->unhandledNts(msg); + break; + } + + delete msg; +} + +void UeRlsTask::onQuit() +{ + m_udpTask->quit(); + delete m_udpTask; +} + +void UeRlsTask::slowDownMeasurements() +{ + m_measurementPeriod = TIMER_PERIOD_MEASUREMENT_MAX; +} + +} // namespace nr::ue diff --git a/src/ue/rls/task.hpp b/src/ue/rls/task.hpp new file mode 100644 index 000000000..0afb5c2ff --- /dev/null +++ b/src/ue/rls/task.hpp @@ -0,0 +1,71 @@ +// +// 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 +#include +#include +#include +#include +#include + +namespace nr::ue +{ + +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; + + friend class UeCmdHandler; + + public: + explicit UeRlsTask(TaskBase *base); + ~UeRlsTask() override = default; + + protected: + 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 new file mode 100644 index 000000000..4fafa335b --- /dev/null +++ b/src/ue/rls/transport.cpp @@ -0,0 +1,76 @@ +// +// 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/rrc/channel.cpp b/src/ue/rrc/channel.cpp index de4c70733..8c7dae2b8 100644 --- a/src/ue/rrc/channel.cpp +++ b/src/ue/rrc/channel.cpp @@ -9,7 +9,7 @@ #include "task.hpp" #include -#include +#include #include #include @@ -68,34 +68,11 @@ void UeRrcTask::handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &rr asn::Free(asn_DEF_ASN_RRC_PCCH_Message, pdu); break; } - case rrc::RrcChannel::UL_CCCH: { - auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_UL_CCCH_Message, rrcPdu); - if (pdu == nullptr) - m_logger->err("RRC UL-CCCH PDU decoding failed."); - else - receiveRrcMessage(pdu); - asn::Free(asn_DEF_ASN_RRC_UL_CCCH_Message, pdu); - break; - } - case rrc::RrcChannel::UL_CCCH1: { - auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_UL_CCCH1_Message, rrcPdu); - if (pdu == nullptr) - m_logger->err("RRC UL-CCCH1 PDU decoding failed."); - else - receiveRrcMessage(pdu); - asn::Free(asn_DEF_ASN_RRC_UL_CCCH1_Message, pdu); + case rrc::RrcChannel::UL_CCCH: + case rrc::RrcChannel::UL_CCCH1: + case rrc::RrcChannel::UL_DCCH: break; } - case rrc::RrcChannel::UL_DCCH: { - auto *pdu = rrc::encode::Decode(asn_DEF_ASN_RRC_UL_DCCH_Message, rrcPdu); - if (pdu == nullptr) - m_logger->err("RRC UL-DCCH PDU decoding failed."); - else - receiveRrcMessage(pdu); - asn::Free(asn_DEF_ASN_RRC_UL_DCCH_Message, pdu); - break; - } - } } void UeRrcTask::sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg) @@ -107,70 +84,10 @@ void UeRrcTask::sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg) return; } - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); + auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); nw->channel = rrc::RrcChannel::BCCH_BCH; nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); -} - -void UeRrcTask::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) - { - m_logger->err("RRC BCCH-DL-SCH encoding failed."); - return; - } - - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::BCCH_DL_SCH; - nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); -} - -void UeRrcTask::sendRrcMessage(ASN_RRC_DL_CCCH_Message *msg) -{ - OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_DL_CCCH_Message, msg); - if (pdu.length() == 0) - { - m_logger->err("RRC DL-CCCH encoding failed."); - return; - } - - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::DL_CCCH; - nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); -} - -void UeRrcTask::sendRrcMessage(ASN_RRC_DL_DCCH_Message *msg) -{ - OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_DL_DCCH_Message, msg); - if (pdu.length() == 0) - { - m_logger->err("RRC DL-DCCH encoding failed."); - return; - } - - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::DL_DCCH; - nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); -} - -void UeRrcTask::sendRrcMessage(ASN_RRC_PCCH_Message *msg) -{ - OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_PCCH_Message, msg); - if (pdu.length() == 0) - { - m_logger->err("RRC PCCH encoding failed."); - return; - } - - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); - nw->channel = rrc::RrcChannel::PCCH; - nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); + m_base->rlsTask->push(nw); } void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH_Message *msg) @@ -182,10 +99,10 @@ void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH_Message *msg) return; } - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); + auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); nw->channel = rrc::RrcChannel::UL_CCCH; nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); + m_base->rlsTask->push(nw); } void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH1_Message *msg) @@ -197,10 +114,10 @@ void UeRrcTask::sendRrcMessage(ASN_RRC_UL_CCCH1_Message *msg) return; } - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); + auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); nw->channel = rrc::RrcChannel::UL_CCCH1; nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); + m_base->rlsTask->push(nw); } void UeRrcTask::sendRrcMessage(ASN_RRC_UL_DCCH_Message *msg) @@ -212,10 +129,10 @@ void UeRrcTask::sendRrcMessage(ASN_RRC_UL_DCCH_Message *msg) return; } - auto *nw = new NwUeRrcToMr(NwUeRrcToMr::RRC_PDU_DELIVERY); + auto *nw = new NwUeRrcToRls(NwUeRrcToRls::RRC_PDU_DELIVERY); nw->channel = rrc::RrcChannel::UL_DCCH; nw->pdu = std::move(pdu); - m_base->mrTask->push(nw); + m_base->rlsTask->push(nw); } void UeRrcTask::receiveRrcMessage(ASN_RRC_BCCH_BCH_Message *msg) @@ -268,37 +185,18 @@ void UeRrcTask::receiveRrcMessage(ASN_RRC_DL_DCCH_Message *msg) void UeRrcTask::receiveRrcMessage(ASN_RRC_PCCH_Message *msg) { - // TODO -} - -void UeRrcTask::receiveRrcMessage(ASN_RRC_UL_CCCH_Message *msg) -{ - if (msg->message.present != ASN_RRC_UL_CCCH_MessageType_PR_c1) + if (msg->message.present != ASN_RRC_PCCH_MessageType_PR_c1) return; auto &c1 = msg->message.choice.c1; switch (c1->present) { - case ASN_RRC_UL_CCCH_MessageType__c1_PR_NOTHING: + case ASN_RRC_PCCH_MessageType__c1_PR_paging: + receivePaging(*c1->choice.paging); + break; + default: break; - case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcSetupRequest: - break; // todo - case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcResumeRequest: - break; // todo - case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcReestablishmentRequest: - break; // todo - case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcSystemInfoRequest: - break; // todo } } -void UeRrcTask::receiveRrcMessage(ASN_RRC_UL_CCCH1_Message *msg) -{ - // TODO -} - -void UeRrcTask::receiveRrcMessage(ASN_RRC_UL_DCCH_Message *msg) -{ -} - } // namespace nr::ue diff --git a/src/ue/rrc/handler.cpp b/src/ue/rrc/handler.cpp index e44fa314d..0c3974f04 100644 --- a/src/ue/rrc/handler.cpp +++ b/src/ue/rrc/handler.cpp @@ -9,13 +9,15 @@ #include "task.hpp" #include #include -#include #include #include #include #include #include +#include +#include +#include #include #include #include @@ -129,12 +131,31 @@ void UeRrcTask::receiveRrcRelease(const ASN_RRC_RRCRelease &msg) { m_logger->debug("RRC Release received"); m_state = ERrcState::RRC_IDLE; - - auto *wr = new NwUeRrcToMr(NwUeRrcToMr::RRC_CONNECTION_RELEASE); - wr->cause = rls::ECause::RRC_NORMAL_RELEASE; - m_base->mrTask->push(wr); - m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_RELEASE)); } +void UeRrcTask::receivePaging(const ASN_RRC_Paging &msg) +{ + std::vector tmsiIds{}; + + asn::ForeachItem(*msg.pagingRecordList, [&tmsiIds](auto &pagingRecord) { + if (pagingRecord.ue_Identity.present == ASN_RRC_PagingUE_Identity_PR_ng_5G_S_TMSI) + { + auto recordTmsi = asn::GetOctetString(pagingRecord.ue_Identity.choice.ng_5G_S_TMSI); + auto tmsiOs = BitBuffer{recordTmsi.data()}; + + GutiMobileIdentity tmsi{}; + tmsi.amfSetId = tmsiOs.readBits(10); + tmsi.amfPointer = tmsiOs.readBits(6); + tmsi.tmsi = octet4{static_cast(tmsiOs.readBitsLong(32) & 0xFFFFFFFFu)}; + + tmsiIds.push_back(tmsi); + } + }); + + auto *w = new NwUeRrcToNas(NwUeRrcToNas::PAGING); + w->pagingTmsi = std::move(tmsiIds); + m_base->nasTask->push(w); +} + } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/task.cpp b/src/ue/rrc/task.cpp index a5a8ac5cf..571809bd0 100644 --- a/src/ue/rrc/task.cpp +++ b/src/ue/rrc/task.cpp @@ -13,8 +13,8 @@ #include #include #include -#include #include +#include #include namespace nr::ue @@ -29,7 +29,6 @@ UeRrcTask::UeRrcTask(TaskBase *base) : m_base{base} void UeRrcTask::onStart() { - m_logger->debug("RRC layer started"); } void UeRrcTask::onQuit() @@ -45,55 +44,63 @@ void UeRrcTask::onLoop() switch (msg->msgType) { - case NtsMessageType::UE_MR_TO_RRC: { - auto *w = dynamic_cast(msg); + case NtsMessageType::UE_NAS_TO_RRC: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeMrToRrc::PLMN_SEARCH_RESPONSE: { - auto *nw = new NwUeRrcToNas(NwUeRrcToNas::PLMN_SEARCH_RESPONSE); - nw->gnbName = std::move(w->gnbName); - m_base->nasTask->push(nw); + case NwUeNasToRrc::PLMN_SEARCH_REQUEST: { + m_base->rlsTask->push(new NwUeRrcToRls(NwUeRrcToRls::PLMN_SEARCH_REQUEST)); break; } - case NwUeMrToRrc::PLMN_SEARCH_FAILURE: { - m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::PLMN_SEARCH_FAILURE)); + case NwUeNasToRrc::INITIAL_NAS_DELIVERY: { + deliverInitialNas(std::move(w->nasPdu), w->rrcEstablishmentCause); break; } - case NwUeMrToRrc::RRC_PDU_DELIVERY: { - handleDownlinkRrc(w->channel, w->pdu); + case NwUeNasToRrc::UPLINK_NAS_DELIVERY: { + deliverUplinkNas(std::move(w->nasPdu)); break; } - case NwUeMrToRrc::RADIO_LINK_FAILURE: { - handleRadioLinkFailure(); + 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; } } break; } - case NtsMessageType::UE_NAS_TO_RRC: { - auto *w = dynamic_cast(msg); + case NtsMessageType::UE_RLS_TO_RRC: { + auto *w = dynamic_cast(msg); switch (w->present) { - case NwUeNasToRrc::PLMN_SEARCH_REQUEST: { - m_base->mrTask->push(new NwUeRrcToMr(NwUeRrcToMr::PLMN_SEARCH_REQUEST)); + 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 NwUeNasToRrc::INITIAL_NAS_DELIVERY: - deliverInitialNas(std::move(w->nasPdu), w->rrcEstablishmentCause); + case NwUeRlsToRrc::SERVING_CELL_CHANGE: { + auto *wr = new NwUeRrcToNas(NwUeRrcToNas::SERVING_CELL_CHANGE); + wr->servingCell = w->servingCell; + m_base->nasTask->push(wr); break; - case NwUeNasToRrc::UPLINK_NAS_DELIVERY: - deliverUplinkNas(std::move(w->nasPdu)); + } + case NwUeRlsToRrc::RRC_PDU_DELIVERY: { + handleDownlinkRrc(w->channel, w->pdu); break; - case NwUeNasToRrc::LOCAL_RELEASE_CONNECTION: - m_state = ERrcState::RRC_IDLE; - - auto *wr = new NwUeRrcToMr(NwUeRrcToMr::RRC_CONNECTION_RELEASE); - wr->cause = rls::ECause::RRC_LOCAL_RELEASE; - m_base->mrTask->push(wr); - - m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_RELEASE)); + } + case NwUeRlsToRrc::RADIO_LINK_FAILURE: { + handleRadioLinkFailure(); break; } + } break; } default: diff --git a/src/ue/rrc/task.hpp b/src/ue/rrc/task.hpp index dd3be84fa..96c669d2a 100644 --- a/src/ue/rrc/task.hpp +++ b/src/ue/rrc/task.hpp @@ -35,6 +35,7 @@ extern "C" struct ASN_RRC_RRCSetup; struct ASN_RRC_RRCReject; struct ASN_RRC_RRCRelease; + struct ASN_RRC_Paging; } namespace nr::ue @@ -73,15 +74,12 @@ class UeRrcTask : public NtsTask 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_BCCH_DL_SCH_Message *msg); - void sendRrcMessage(ASN_RRC_DL_CCCH_Message *msg); - void sendRrcMessage(ASN_RRC_DL_DCCH_Message *msg); - void sendRrcMessage(ASN_RRC_PCCH_Message *msg); void sendRrcMessage(ASN_RRC_UL_CCCH_Message *msg); void sendRrcMessage(ASN_RRC_UL_CCCH1_Message *msg); void sendRrcMessage(ASN_RRC_UL_DCCH_Message *msg); @@ -92,9 +90,6 @@ class UeRrcTask : public NtsTask void receiveRrcMessage(ASN_RRC_DL_CCCH_Message *msg); void receiveRrcMessage(ASN_RRC_DL_DCCH_Message *msg); void receiveRrcMessage(ASN_RRC_PCCH_Message *msg); - void receiveRrcMessage(ASN_RRC_UL_CCCH_Message *msg); - void receiveRrcMessage(ASN_RRC_UL_CCCH1_Message *msg); - void receiveRrcMessage(ASN_RRC_UL_DCCH_Message *msg); }; } // namespace nr::ue diff --git a/src/ue/types.cpp b/src/ue/types.cpp index e7a4ca4f4..303d4dfc1 100644 --- a/src/ue/types.cpp +++ b/src/ue/types.cpp @@ -185,6 +185,8 @@ Json ToJson(const ERegUpdateCause &v) 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: @@ -243,4 +245,33 @@ Json ToJson(const UePduSessionInfo &v) }); } +Json ToJson(const EServiceReqCause &v) +{ + 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 54fa71fe9..9dd1c5ddc 100644 --- a/src/ue/types.hpp +++ b/src/ue/types.hpp @@ -24,9 +24,9 @@ namespace nr::ue { class UeAppTask; -class UeMrTask; class NasTask; class UeRrcTask; +class UeRlsTask; class UserEquipment; struct SupportedAlgs @@ -122,9 +122,9 @@ struct TaskBase NtsTask *cliCallbackTask{}; UeAppTask *appTask{}; - UeMrTask *mrTask{}; NasTask *nasTask{}; UeRrcTask *rrcTask{}; + UeRlsTask *rlsTask{}; }; struct UeTimers @@ -250,6 +250,7 @@ struct PduSession const int psi; EPsState psState{}; + bool uplinkPending{}; nas::EPduSessionType sessionType{}; std::optional apn{}; @@ -395,6 +396,7 @@ struct UePduSessionInfo std::string type{}; std::string address{}; bool isEmergency{}; + bool uplinkPending{}; }; enum class ERegUpdateCause @@ -418,9 +420,10 @@ enum class ERegUpdateCause // when the UE receives an indication of "RRC Connection failure" from the lower layers and does not have signalling // pending (i.e. when the lower layer requests NAS signalling connection recovery) except for the case specified in // subclause 5.3.1.4; + CONNECTION_RECOVERY, // when the UE receives a fallback indication from the lower layers and does not have signalling pending (i.e. when // the lower layer requests NAS signalling connection recovery, see subclauses 5.3.1.4 and 5.3.1.2); - CONNECTION_RECOVERY, + FALLBACK_INDICATION, // when the UE changes the 5GMM capability or the S1 UE network capability or both MM_OR_S1_CAPABILITY_CHANGE, // when the UE's usage setting changes @@ -451,6 +454,38 @@ enum class ERegUpdateCause RESTRICTED_SERVICE_AREA }; +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 + // indicating non-3GPP access + CONNECTED_3GPP_NOTIFICATION_N3GPP, + // c) the UE, in 5GMM-IDLE mode over 3GPP access, has uplink signalling pending + IDLE_UPLINK_SIGNAL_PENDING, + // d) the UE, in 5GMM-IDLE mode over 3GPP access, has uplink user data pending + IDLE_UPLINK_DATA_PENDING, + // e) the UE, in 5GMM-CONNECTED mode or in 5GMM-CONNECTED mode with RRC inactive indication, has user data pending + // due to no user-plane resources established for PDU session(s) used for user data transport + CONNECTED_UPLINK_DATA_PENDING, + // f) the UE in 5GMM-IDLE mode over non-3GPP access, receives an indication from the lower layers of non-3GPP + // access, that the access stratum connection is established between UE and network + NON_3GPP_AS_ESTABLISHED, + // g) the UE, in 5GMM-IDLE mode over 3GPP access, receives a notification from the network with access type + // indicating 3GPP access when the UE is in 5GMM-CONNECTED mode over non-3GPP access + IDLE_3GPP_NOTIFICATION_N3GPP, + // h) the UE, in 5GMM-IDLE, 5GMM-CONNECTED mode over 3GPP access, or 5GMM-CONNECTED mode with RRC inactive + // indication, receives a request for emergency services fallback from the upper layer and performs emergency + // services fallback as specified in subclause 4.13.4.2 of 3GPP TS 23.502 [9] + EMERGENCY_FALLBACK, + // i) the UE, in 5GMM-CONNECTED mode over 3GPP access or in 5GMM-CONNECTED mode with RRC inactive indication, + // receives a fallback indication from the lower layers (see subclauses 5.3.1.2 and 5.3.1.4) and or the UE has a + // pending NAS procedure other than a registration, service request, or de-registration procedure + FALLBACK_INDICATION +}; + Json ToJson(const ECmState &state); Json ToJson(const ERmState &state); Json ToJson(const EMmState &state); @@ -461,5 +496,6 @@ Json ToJson(const UeTimers &v); Json ToJson(const ERegUpdateCause &v); Json ToJson(const EPsState &v); Json ToJson(const UePduSessionInfo &v); +Json ToJson(const EServiceReqCause &v); } // namespace nr::ue diff --git a/src/ue/ue.cpp b/src/ue/ue.cpp index 0d9354569..47fb14d53 100644 --- a/src/ue/ue.cpp +++ b/src/ue/ue.cpp @@ -9,9 +9,9 @@ #include "ue.hpp" #include "app/task.hpp" -#include "mr/task.hpp" #include "nas/task.hpp" #include "rrc/task.hpp" +#include "rls/task.hpp" namespace nr::ue { @@ -29,8 +29,8 @@ UserEquipment::UserEquipment(UeConfig *config, app::IUeController *ueController, base->nasTask = new NasTask(base); base->rrcTask = new UeRrcTask(base); - base->mrTask = new UeMrTask(base); base->appTask = new UeAppTask(base); + base->rlsTask = new UeRlsTask(base); taskBase = base; } @@ -39,12 +39,12 @@ UserEquipment::~UserEquipment() { taskBase->nasTask->quit(); taskBase->rrcTask->quit(); - taskBase->mrTask->quit(); + taskBase->rlsTask->quit(); taskBase->appTask->quit(); delete taskBase->nasTask; delete taskBase->rrcTask; - delete taskBase->mrTask; + delete taskBase->rlsTask; delete taskBase->appTask; delete taskBase->logBase; @@ -56,7 +56,7 @@ void UserEquipment::start() { taskBase->nasTask->start(); taskBase->rrcTask->start(); - taskBase->mrTask->start(); + taskBase->rlsTask->start(); taskBase->appTask->start(); } diff --git a/src/urs/rls/gnb_entity.cpp b/src/urs/rls/gnb_entity.cpp deleted file mode 100644 index 85a125ae3..000000000 --- a/src/urs/rls/gnb_entity.cpp +++ /dev/null @@ -1,245 +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 "gnb_entity.hpp" - -#include -#include - -static const octet3 AppVersion = octet3{cons::Major, cons::Minor, cons::Patch}; - -namespace rls -{ - -RlsGnbEntity::RlsGnbEntity(std::string nodeName) - : nodeName(std::move(nodeName)), token(utils::Random64()), ueIdMap(), idUeMap(), ueAddressMap(), heartbeatMap(), - setupCompleteWaiting(), acceptConnections() -{ -} - -void RlsGnbEntity::onHeartbeat() -{ - uint64_t current = utils::CurrentTimeMillis(); - - std::vector uesToRemove{}; - - for (auto &v : heartbeatMap) - { - if (current - v.second > Constants::HB_TIMEOUT_UE_TO_GNB) - uesToRemove.push_back(v.first); - else - sendHeartbeat(v.first); - } - - for (int ue : uesToRemove) - removeUe(ue, ECause::HEARTBEAT_TIMEOUT); -} - -void RlsGnbEntity::downlinkPayloadDelivery(int ue, EPayloadType type, OctetString &&payload) -{ - if (!idUeMap.count(ue)) - { - logWarn("UE connection released or not established"); - return; - } - - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_PAYLOAD_TRANSPORT; - m.appVersion = AppVersion; - m.ueToken = idUeMap[ue]; - m.gnbToken = token; - m.payloadType = type; - m.payload = std::move(payload); - sendRlsMessage(ue, m); -} - -void RlsGnbEntity::setAcceptConnections(bool accept) -{ - acceptConnections = accept; -} - -void RlsGnbEntity::releaseConnection(int ue, ECause cause) -{ - sendReleaseIndication(ue, cause); - removeUe(ue, cause); -} - -void RlsGnbEntity::localReleaseConnection(int ue, ECause cause) -{ - removeUe(ue, cause); -} - -void RlsGnbEntity::sendRlsMessage(int ue, const RlsMessage &msg) -{ - OctetString buf{}; - if (!Encode(msg, buf)) - { - logError("PDU encoding failed"); - return; - } - - sendRlsPdu(ueAddressMap[ue], std::move(buf)); -} - -void RlsGnbEntity::sendHeartbeat(int ue) -{ - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_HEARTBEAT; - m.appVersion = AppVersion; - m.ueToken = idUeMap[ue]; - m.gnbToken = token; - sendRlsMessage(ue, m); -} - -void RlsGnbEntity::sendReleaseIndication(int ue, ECause cause) -{ - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_RELEASE_INDICATION; - m.appVersion = AppVersion; - m.ueToken = idUeMap[ue]; - m.gnbToken = token; - m.cause = cause; - sendRlsMessage(ue, m); -} - -void RlsGnbEntity::removeUe(int ue, ECause cause) -{ - if (idUeMap.count(ue) == 0) - return; - - uint64_t ueToken = idUeMap[ue]; - ueIdMap.erase(ueToken); - idUeMap.erase(ue); - ueAddressMap.erase(ue); - heartbeatMap.erase(ue); - if (!setupCompleteWaiting.count(ue)) - onUeReleased(ue, cause); - setupCompleteWaiting.erase(ue); -} - -void RlsGnbEntity::onReceive(const InetAddress &address, const OctetString &pdu) -{ - RlsMessage msg{}; - - auto res = Decode(OctetView{pdu}, msg, AppVersion); - if (res == DecodeRes::FAILURE) - { - logError("PDU decoding failed"); - return; - } - if (res == DecodeRes::VERSION_MISMATCH) - { - logError("Version mismatch between UE and gNB"); - return; - } - - if (msg.msgCls == EMessageClass::ERROR_INDICATION) - { - logError("RLS error indication received | " + std::to_string((int)msg.cause) + " | " + msg.str); - return; - } - - if (msg.ueToken == 0) - { - logWarn("UE gNB token received"); - return; - } - - if (msg.msgType == EMessageType::RLS_SETUP_REQUEST) - { - if (!acceptConnections) - { - // ignore setup request - return; - } - - if (ueIdMap.count(msg.ueToken)) - { - sendSetupFailure(address, msg.ueToken, ECause::TOKEN_CONFLICT); - return; - } - - int ueId = utils::NextId(); - ueIdMap[msg.ueToken] = ueId; - idUeMap[ueId] = msg.ueToken; - ueAddressMap[ueId] = address; - heartbeatMap[ueId] = utils::CurrentTimeMillis(); - setupCompleteWaiting.insert(ueId); - - sendSetupResponse(ueId); - return; - } - - if (!ueIdMap.count(msg.ueToken)) - { - logWarn("Unknown UE token"); - return; - } - - int ue = ueIdMap[msg.ueToken]; - heartbeatMap[ue] = utils::CurrentTimeMillis(); - - if (msg.msgType == EMessageType::RLS_SETUP_COMPLETE) - { - setupCompleteWaiting.erase(ue); - onUeConnected(ue, msg.str); - } - else if (msg.msgType == EMessageType::RLS_HEARTBEAT) - { - heartbeatMap[ue] = utils::CurrentTimeMillis(); - } - else if (msg.msgType == EMessageType::RLS_RELEASE_INDICATION) - { - removeUe(ue, msg.cause); - } - else if (msg.msgType == EMessageType::RLS_PAYLOAD_TRANSPORT) - { - deliverUplinkPayload(ue, msg.payloadType, std::move(msg.payload)); - } - else - { - logWarn("Bad message type received from UE"); - } -} - -void RlsGnbEntity::sendSetupResponse(int ue) -{ - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_SETUP_RESPONSE; - m.appVersion = AppVersion; - m.ueToken = idUeMap[ue]; - m.gnbToken = token; - m.str = nodeName; - sendRlsMessage(ue, m); -} - -void RlsGnbEntity::sendSetupFailure(const InetAddress &address, uint64_t ueToken, ECause cause) -{ - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_SETUP_FAILURE; - m.appVersion = AppVersion; - m.ueToken = ueToken; - m.gnbToken = token; - m.cause = cause; - - OctetString buf{}; - if (!Encode(m, buf)) - { - logWarn("PDU encoding failed"); - return; - } - - sendRlsPdu(address, std::move(buf)); -} - -} // namespace rls \ No newline at end of file diff --git a/src/urs/rls/gnb_entity.hpp b/src/urs/rls/gnb_entity.hpp deleted file mode 100644 index 5f3a85a4e..000000000 --- a/src/urs/rls/gnb_entity.hpp +++ /dev/null @@ -1,64 +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. -// - -#pragma once - -#include "rls.hpp" - -#include -#include -#include -#include - -namespace rls -{ - -class RlsGnbEntity -{ - private: - std::string nodeName; - uint64_t token; - std::unordered_map ueIdMap; // UE token to gNB-internal UE id. - std::unordered_map idUeMap; // gNB-internal UE id to UE token. - std::unordered_map ueAddressMap; // UE token to address - std::unordered_map heartbeatMap; // UE token to last heartbeat time - std::set setupCompleteWaiting; - bool acceptConnections; - - public: - explicit RlsGnbEntity(std::string nodeName); - virtual ~RlsGnbEntity() = default; - - protected: - virtual void logWarn(const std::string &msg) = 0; - virtual void logError(const std::string &msg) = 0; - - virtual void onUeConnected(int ue, std::string name) = 0; - virtual void onUeReleased(int ue, ECause cause) = 0; - - virtual void sendRlsPdu(const InetAddress &address, OctetString &&pdu) = 0; - virtual void deliverUplinkPayload(int ue, EPayloadType type, OctetString &&payload) = 0; - - public: - void onHeartbeat(); - void onReceive(const InetAddress &address, const OctetString &pdu); - void downlinkPayloadDelivery(int ue, EPayloadType type, OctetString &&payload); - void setAcceptConnections(bool accept); - void releaseConnection(int ue, ECause cause); - void localReleaseConnection(int ue, ECause cause); - - private: - void sendReleaseIndication(int ue, ECause cause); - void sendHeartbeat(int ue); - void removeUe(int ue, ECause cause); - void sendRlsMessage(int ue, const RlsMessage &msg); - void sendSetupFailure(const InetAddress &address, uint64_t ueToken, ECause cause); - void sendSetupResponse(int ue); -}; - -} // namespace rls \ No newline at end of file diff --git a/src/urs/rls/rls.cpp b/src/urs/rls/rls.cpp deleted file mode 100644 index b3f883172..000000000 --- a/src/urs/rls/rls.cpp +++ /dev/null @@ -1,107 +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 "rls.hpp" - -namespace rls -{ - -DecodeRes Decode(const OctetView &stream, RlsMessage &output, octet3 appVersion) -{ - output.msgCls = static_cast(stream.readI()); - if (output.msgCls == EMessageClass::ERROR_INDICATION) - { - output.cause = static_cast(stream.readI()); - uint16_t errLen = stream.read2US(); - output.str = stream.readUtf8String(errLen); - return DecodeRes::OK; - } - if (output.msgCls == EMessageClass::NORMAL_MESSAGE) - { - output.appVersion = stream.read3(); - if ((int)output.appVersion != (int)appVersion) - return DecodeRes::VERSION_MISMATCH; - output.msgType = static_cast(stream.readI()); - if (output.msgType != EMessageType::RLS_SETUP_REQUEST && output.msgType != EMessageType::RLS_SETUP_COMPLETE && - output.msgType != EMessageType::RLS_SETUP_FAILURE && output.msgType != EMessageType::RLS_HEARTBEAT && - output.msgType != EMessageType::RLS_RELEASE_INDICATION && - output.msgType != EMessageType::RLS_PAYLOAD_TRANSPORT && output.msgType != EMessageType::RLS_SETUP_RESPONSE) - return DecodeRes::FAILURE; - output.ueToken = stream.read8UL(); - output.gnbToken = stream.read8UL(); - output.payloadType = static_cast(stream.readI()); - uint16_t len = stream.read2US(); - output.payload = stream.readOctetString(len); - output.cause = static_cast(stream.readI()); - len = stream.read2US(); - output.str = stream.readUtf8String(len); - return DecodeRes::OK; - } - - return DecodeRes::FAILURE; -} - -bool Encode(const RlsMessage &msg, OctetString &stream) -{ - stream.appendOctet(static_cast(msg.msgCls)); - if (msg.msgCls == EMessageClass::ERROR_INDICATION) - { - stream.appendOctet(static_cast(msg.cause)); - stream.appendOctet2(msg.str.length()); - for (char c : msg.str) - stream.appendOctet(c); - return true; - } - if (msg.msgCls == EMessageClass::NORMAL_MESSAGE) - { - if (msg.msgType != EMessageType::RLS_SETUP_REQUEST && msg.msgType != EMessageType::RLS_SETUP_COMPLETE && - msg.msgType != EMessageType::RLS_SETUP_FAILURE && msg.msgType != EMessageType::RLS_HEARTBEAT && - msg.msgType != EMessageType::RLS_RELEASE_INDICATION && msg.msgType != EMessageType::RLS_PAYLOAD_TRANSPORT && - msg.msgType != EMessageType::RLS_SETUP_RESPONSE) - return false; - - stream.appendOctet3(msg.appVersion); - stream.appendOctet(static_cast(msg.msgType)); - stream.appendOctet8(msg.ueToken); - stream.appendOctet8(msg.gnbToken); - stream.appendOctet(static_cast(msg.payloadType)); - stream.appendOctet2(msg.payload.length()); - stream.append(msg.payload); - stream.appendOctet(static_cast(msg.cause)); - stream.appendOctet2(msg.str.length()); - stream.appendUtf8(msg.str); - - return true; - } - return false; -} - -const char *CauseToString(ECause cause) -{ - switch (cause) - { - case ECause::UNSPECIFIED: - return "RLS-UNSPECIFIED"; - case ECause::TOKEN_CONFLICT: - return "RLS-TOKEN-CONFLICT"; - case ECause::EMPTY_SEARCH_LIST: - return "RLS-EMPTY-SEARCH-LIST"; - case ECause::SETUP_TIMEOUT: - return "RLS-SETUP-TIMEOUT"; - case ECause::HEARTBEAT_TIMEOUT: - return "RLS-HEARTBEAT-TIMEOUT"; - case ECause::RRC_NORMAL_RELEASE: - return "RLS-RRC-NORMAL-RELEASE"; - case ECause::RRC_LOCAL_RELEASE: - return "RLS-RRC-LOCAL-RELEASE"; - default: - return "?"; - } -} - -} // namespace rls diff --git a/src/urs/rls/rls.hpp b/src/urs/rls/rls.hpp deleted file mode 100644 index 1d04fbda1..000000000 --- a/src/urs/rls/rls.hpp +++ /dev/null @@ -1,109 +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. -// - -#pragma once - -#include -#include -#include -#include - -namespace rls -{ - -enum class EUeState -{ - IDLE, - SEARCH, - CONNECTED, - RELEASED, -}; - -enum class EMessageClass : uint8_t -{ - RESERVED = 0, - ERROR_INDICATION, - NORMAL_MESSAGE -}; - -enum class EMessageType : uint8_t -{ - RESERVED = 0, - RLS_SETUP_REQUEST, - RLS_SETUP_RESPONSE, - RLS_SETUP_FAILURE, - RLS_SETUP_COMPLETE, - RLS_HEARTBEAT, - RLS_RELEASE_INDICATION, - RLS_PAYLOAD_TRANSPORT -}; - -enum class ECause : uint8_t -{ - // Error causes (treated as radio link failure) - UNSPECIFIED = 0, - TOKEN_CONFLICT, - EMPTY_SEARCH_LIST, - SETUP_TIMEOUT, - HEARTBEAT_TIMEOUT, - - // Successful causes - RRC_NORMAL_RELEASE, // release with UE-gNB coordination over RRC - RRC_LOCAL_RELEASE, // release locally without UE-gNB coordination -}; - -// Checks if the cause treated as radio link failure -inline bool IsRlf(ECause cause) -{ - return cause != ECause::RRC_NORMAL_RELEASE && cause != ECause::RRC_LOCAL_RELEASE; -} - -enum class EPayloadType : uint8_t -{ - RRC, - DATA, -}; - -struct Constants -{ - static constexpr const int UE_WAIT_TIMEOUT = 500; - static constexpr const int HB_TIMEOUT_GNB_TO_UE = 3000; - static constexpr const int HB_TIMEOUT_UE_TO_GNB = 5000; - static constexpr const int HB_PERIOD_GNB_TO_UE = 1500; - static constexpr const int HB_PERIOD_UE_TO_GNB = 2000; -}; - -struct RlsMessage -{ - EMessageClass msgCls{}; - - ECause cause{}; // only for RLS_SETUP_FAILURE and error indication messages - std::string str{}; // only for error indication, RLS_SETUP_RESPONSE, RLS_SETUP_COMPLETE, RLS_SETUP_FAILURE - - octet3 appVersion{}; // only for normal messages - EMessageType msgType{}; // only for normal messages - uint64_t ueToken{}; // only for normal messages - uint64_t gnbToken{}; // only for normal messages except RLS_SETUP_REQUEST - - EPayloadType payloadType{}; // only for RLC_PAYLOAD_TRANSPORT - OctetString payload{}; // only for RLC_PAYLOAD_TRANSPORT -}; - -enum class DecodeRes -{ - OK, - VERSION_MISMATCH, - FAILURE, -}; - -DecodeRes Decode(const OctetView &stream, RlsMessage &output, octet3 appVersion); -bool Encode(const RlsMessage &msg, OctetString &stream); - -const char *CauseToString(ECause cause); - -} // namespace rls diff --git a/src/urs/rls/ue_entity.cpp b/src/urs/rls/ue_entity.cpp deleted file mode 100644 index f18bb61af..000000000 --- a/src/urs/rls/ue_entity.cpp +++ /dev/null @@ -1,307 +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 "ue_entity.hpp" - -#include -#include -#include - -static const octet3 AppVersion = octet3{cons::Major, cons::Minor, cons::Patch}; - -namespace rls -{ - -RlsUeEntity::RlsUeEntity(std::string nodeName, std::vector gnbSearchList) - : nodeName(std::move(nodeName)), gnbSearchList(std::move(gnbSearchList)), state(EUeState::IDLE), nextSearch(0), - ueToken(0), gnbToken(0), lastGnbHeartbeat(0), lastError(ECause::UNSPECIFIED) -{ -} - -void RlsUeEntity::onHeartbeat() -{ - if (state != EUeState::CONNECTED) - return; - - if (utils::CurrentTimeMillis() - lastGnbHeartbeat > Constants::HB_TIMEOUT_GNB_TO_UE) - { - state = EUeState::RELEASED; - nextSearch = 0; - ueToken = 0; - gnbToken = 0; - lastGnbHeartbeat = 0; - lastError = ECause::UNSPECIFIED; - onRelease(ECause::HEARTBEAT_TIMEOUT); - return; - } - - RlsMessage msg; - msg.gnbToken = gnbToken; - msg.ueToken = ueToken; - msg.msgType = EMessageType::RLS_HEARTBEAT; - msg.msgCls = EMessageClass::NORMAL_MESSAGE; - msg.appVersion = AppVersion; - sendRlsMessage(selected, msg); -} - -void RlsUeEntity::onWaitingTimerExpire() -{ - if (state != EUeState::SEARCH) - return; - - if (nextSearch + 1 >= gnbSearchList.size()) - { - resetEntity(); - searchFailure(ECause::SETUP_TIMEOUT); - } - else - { - nextSearch++; - ueToken = utils::Random64(); - startWaitingTimer(Constants::UE_WAIT_TIMEOUT); - sendSetupRequest(); - } -} - -void RlsUeEntity::onUplinkDelivery(EPayloadType type, OctetString &&payload) -{ - if (state != EUeState::CONNECTED) - { - logWarn("RLS uplink delivery request without connection"); - return; - } - - RlsMessage msg; - msg.gnbToken = gnbToken; - msg.ueToken = ueToken; - msg.msgType = EMessageType::RLS_PAYLOAD_TRANSPORT; - msg.msgCls = EMessageClass::NORMAL_MESSAGE; - msg.appVersion = AppVersion; - msg.payloadType = type; - msg.payload = std::move(payload); - sendRlsMessage(selected, msg); -} - -void RlsUeEntity::startGnbSearch() -{ - if (state == EUeState::SEARCH) - return; - - if (state != EUeState::IDLE) - { - logWarn("gNB search request while in not IDLE state"); - return; - } - - if (gnbSearchList.empty()) - { - searchFailure(ECause::EMPTY_SEARCH_LIST); - return; - } - - nextSearch = 0; - ueToken = utils::Random64(); - startWaitingTimer(Constants::UE_WAIT_TIMEOUT); - state = EUeState::SEARCH; - sendSetupRequest(); -} - -void RlsUeEntity::onReceive(const InetAddress &address, const OctetString &pdu) -{ - if (ueToken == 0) - { - logWarn("Received PDU ignored, UE entity is not initialized"); - return; - } - - RlsMessage msg{}; - - auto res = Decode(OctetView{pdu}, msg, AppVersion); - if (res == DecodeRes::FAILURE) - { - logError("PDU decoding failed"); - return; - } - if (res == DecodeRes::VERSION_MISMATCH) - { - logError("Version mismatch between UE and gNB"); - return; - } - - if (msg.msgCls == EMessageClass::ERROR_INDICATION) - { - logError("RLS error indication received | " + std::to_string((int)msg.cause) + " | " + msg.str); - return; - } - - if (msg.ueToken != ueToken) - { - logWarn("UE token mismatched message received"); - return; - } - - if (msg.gnbToken == 0) - { - logWarn("Bad gNB token received"); - return; - } - - if (state != EUeState::CONNECTED && state != EUeState::SEARCH) - { - logWarn("RLS received while in not CONNECTED or SEARCH"); - return; - } - - if (state == EUeState::CONNECTED) - { - if (gnbToken != msg.gnbToken) - { - logWarn("gNB token mismatched message received"); - return; - } - - lastGnbHeartbeat = utils::CurrentTimeMillis(); - - if (msg.msgType == EMessageType::RLS_PAYLOAD_TRANSPORT) - { - deliverPayload(msg.payloadType, std::move(msg.payload)); - } - else if (msg.msgType == EMessageType::RLS_HEARTBEAT) - { - lastGnbHeartbeat = utils::CurrentTimeMillis(); - } - else if (msg.msgType == EMessageType::RLS_RELEASE_INDICATION) - { - state = EUeState::RELEASED; - nextSearch = 0; - ueToken = 0; - gnbToken = 0; - lastGnbHeartbeat = 0; - lastError = ECause::UNSPECIFIED; - onRelease(msg.cause); - } - else - { - logWarn("RLS receive invalid message type in CONNECTED " + std::to_string((int)msg.msgType)); - } - } - - if (state == EUeState::SEARCH) - { - if (msg.msgType == EMessageType::RLS_SETUP_RESPONSE) - { - state = EUeState::CONNECTED; - gnbToken = msg.gnbToken; - lastError = ECause::UNSPECIFIED; - nextSearch = 0; - lastGnbHeartbeat = utils::CurrentTimeMillis(); - selected = address; - sendSetupComplete(); - onConnect(msg.str); - } - else if (msg.msgType == EMessageType::RLS_SETUP_FAILURE) - { - if (msg.cause != ECause::UNSPECIFIED) - lastError = msg.cause; - - if (nextSearch + 1 >= gnbSearchList.size()) - { - searchFailure(lastError); - resetEntity(); - } - else - { - nextSearch++; - ueToken = utils::Random64(); - startWaitingTimer(Constants::UE_WAIT_TIMEOUT); - sendSetupRequest(); - } - } - else - { - logWarn("RLS receive invalid message type in IDLE " + std::to_string((int)msg.msgType)); - } - } -} - -void RlsUeEntity::releaseConnection(ECause cause) -{ - sendReleaseIndication(cause); - localReleaseConnection(cause); -} - -void RlsUeEntity::resetEntity() -{ - state = EUeState::IDLE; - nextSearch = 0; - ueToken = 0; - gnbToken = 0; - lastError = ECause::UNSPECIFIED; - lastGnbHeartbeat = 0; - selected = {}; -} - -void RlsUeEntity::sendSetupRequest() -{ - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_SETUP_REQUEST; - m.appVersion = AppVersion; - m.ueToken = ueToken; - m.gnbToken = 0; - sendRlsMessage(gnbSearchList[nextSearch], m); -} - -void RlsUeEntity::sendSetupComplete() -{ - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_SETUP_COMPLETE; - m.appVersion = AppVersion; - m.ueToken = ueToken; - m.gnbToken = gnbToken; - m.str = nodeName; - sendRlsMessage(selected, m); -} - -void RlsUeEntity::sendReleaseIndication(ECause cause) -{ - RlsMessage m; - m.msgCls = EMessageClass::NORMAL_MESSAGE; - m.msgType = EMessageType::RLS_RELEASE_INDICATION; - m.appVersion = AppVersion; - m.ueToken = ueToken; - m.gnbToken = gnbToken; - m.cause = cause; - sendRlsMessage(selected, m); -} - -void RlsUeEntity::sendRlsMessage(const InetAddress &address, const RlsMessage &msg) -{ - OctetString stream{}; - if (!Encode(msg, stream)) - { - logWarn("PDU encoding failed"); - return; - } - - sendRlsPdu(address, std::move(stream)); -} - -void RlsUeEntity::localReleaseConnection(ECause cause) -{ - state = EUeState::RELEASED; - nextSearch = 0; - ueToken = 0; - gnbToken = 0; - lastGnbHeartbeat = 0; - lastError = ECause::UNSPECIFIED; - onRelease(cause); -} - -} // namespace rls \ No newline at end of file diff --git a/src/urs/rls/ue_entity.hpp b/src/urs/rls/ue_entity.hpp deleted file mode 100644 index 11bb4a5fd..000000000 --- a/src/urs/rls/ue_entity.hpp +++ /dev/null @@ -1,64 +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. -// - -#pragma once - -#include "rls.hpp" - -#include -#include - -namespace rls -{ - -class RlsUeEntity -{ - private: - std::string nodeName; - std::vector gnbSearchList; - - EUeState state; - size_t nextSearch; - uint64_t ueToken; - uint64_t gnbToken; - uint64_t lastGnbHeartbeat; - ECause lastError; - InetAddress selected; - - public: - explicit RlsUeEntity(std::string nodeName, std::vector gnbSearchList); - virtual ~RlsUeEntity() = default; - - protected: - virtual void logWarn(const std::string &msg) = 0; - virtual void logError(const std::string &msg) = 0; - virtual void startWaitingTimer(int period) = 0; - virtual void searchFailure(ECause cause) = 0; - virtual void onRelease(ECause cause) = 0; - virtual void onConnect(const std::string &gnbName) = 0; - virtual void sendRlsPdu(const InetAddress &address, OctetString &&pdu) = 0; - virtual void deliverPayload(EPayloadType type, OctetString &&payload) = 0; - - public: - void onHeartbeat(); - void onWaitingTimerExpire(); - void onReceive(const InetAddress &address, const OctetString &pdu); - void onUplinkDelivery(EPayloadType type, OctetString &&payload); - void startGnbSearch(); - void releaseConnection(ECause cause); - void localReleaseConnection(ECause cause); - void resetEntity(); - - private: - void sendSetupRequest(); - void sendSetupComplete(); - void sendReleaseIndication(ECause cause); - void sendRlsMessage(const InetAddress &address, const RlsMessage &msg); -}; - -} // namespace rls \ No newline at end of file diff --git a/src/urs/rls_pdu.cpp b/src/urs/rls_pdu.cpp new file mode 100644 index 000000000..497c3cdf9 --- /dev/null +++ b/src/urs/rls_pdu.cpp @@ -0,0 +1,129 @@ +// +// 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 +{ + +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) + + stream.appendOctet(cons::Major); + stream.appendOctet(cons::Minor); + stream.appendOctet(cons::Patch); + stream.appendOctet(static_cast(msg.msgType)); + stream.appendOctet8(msg.sti); + if (msg.msgType == EMessageType::CELL_INFO_REQUEST) + { + auto &m = (const RlsCellInfoRequest &)msg; + stream.appendOctet4(m.simPos.x); + stream.appendOctet4(m.simPos.y); + stream.appendOctet4(m.simPos.z); + } + else if (msg.msgType == EMessageType::CELL_INFO_RESPONSE) + { + auto &m = (const RlsCellInfoResponse &)msg; + AppendGlobalNci(m.cellId, stream); + stream.appendOctet4(m.tac); + 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) + { + auto &m = (const RlsPduDelivery &)msg; + stream.appendOctet(static_cast(m.pduType)); + stream.appendOctet4(m.pdu.length()); + stream.append(m.pdu); + stream.appendOctet4(m.payload.length()); + stream.append(m.payload); + } +} + +std::unique_ptr DecodeRlsMessage(const OctetView &stream) +{ + auto first = stream.readI(); // (Just for old RLS compatibility) + if (first != 3) + return nullptr; + + if (stream.read() != cons::Major) + return nullptr; + if (stream.read() != cons::Minor) + return nullptr; + if (stream.read() != cons::Patch) + return nullptr; + + auto msgType = static_cast(stream.readI()); + uint64_t sti = stream.read8UL(); + + if (msgType == EMessageType::CELL_INFO_REQUEST) + { + 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) + { + auto res = std::make_unique(sti); + res->cellId = DecodeGlobalNci(stream); + res->tac = stream.read4I(); + res->dbm = stream.read4I(); + res->gnbName = stream.readUtf8String(stream.read4I()); + res->linkIp = stream.readUtf8String(stream.read4I()); + return res; + } + else if (msgType == EMessageType::PDU_DELIVERY) + { + auto res = std::make_unique(sti); + res->pduType = static_cast(stream.readI()); + res->pdu = stream.readOctetString(stream.read4I()); + res->payload = stream.readOctetString(stream.read4I()); + return res; + } + + return nullptr; +} + +} // namespace rls diff --git a/src/urs/rls_pdu.hpp b/src/urs/rls_pdu.hpp new file mode 100644 index 000000000..8e9569d72 --- /dev/null +++ b/src/urs/rls_pdu.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. +// + +#pragma once + +#include +#include +#include +#include +#include + +namespace rls +{ + +enum class EMessageType : uint8_t +{ + RESERVED = 0, + CELL_INFO_REQUEST, + CELL_INFO_RESPONSE, + PDU_DELIVERY +}; + +enum class EPduType : uint8_t +{ + RESERVED = 0, + RRC, + DATA +}; + +struct RlsMessage +{ + const EMessageType msgType; + const uint64_t sti{}; + + explicit RlsMessage(EMessageType msgType, uint64_t sti) : msgType(msgType), sti(sti) + { + } +}; + +struct RlsCellInfoRequest : RlsMessage +{ + Vector3 simPos{}; + + explicit RlsCellInfoRequest(uint64_t sti) : RlsMessage(EMessageType::CELL_INFO_REQUEST, sti) + { + } +}; + +struct RlsCellInfoResponse : RlsMessage +{ + GlobalNci cellId{}; + int tac{}; + int dbm{}; + std::string gnbName{}; + std::string linkIp{}; + + explicit RlsCellInfoResponse(uint64_t sti) : RlsMessage(EMessageType::CELL_INFO_RESPONSE, sti) + { + } +}; + +struct RlsPduDelivery : RlsMessage +{ + EPduType pduType{}; + OctetString pdu{}; + OctetString payload{}; + + explicit RlsPduDelivery(uint64_t sti) : RlsMessage(EMessageType::PDU_DELIVERY, sti) + { + } +}; + +void EncodeRlsMessage(const RlsMessage &msg, OctetString &stream); +std::unique_ptr DecodeRlsMessage(const OctetView &stream); + +} // namespace rls \ No newline at end of file diff --git a/src/utils/common.cpp b/src/utils/common.cpp index 537f792e8..2ec0d7e6a 100644 --- a/src/utils/common.cpp +++ b/src/utils/common.cpp @@ -26,7 +26,7 @@ static_assert(sizeof(float) == sizeof(uint32_t)); static_assert(sizeof(double) == sizeof(uint64_t)); static_assert(sizeof(long long) == sizeof(uint64_t)); -static std::atomic IdCounter = 1; +static std::atomic g_idCounter = 1; static bool IPv6FromString(const char *szAddress, uint8_t *address) { @@ -135,7 +135,7 @@ std::vector utils::HexStringToVector(const std::string &hex) int utils::NextId() { - int res = ++IdCounter; + int res = ++g_idCounter; if (res == 0) { // ID counter overflows. diff --git a/src/utils/common.hpp b/src/utils/common.hpp index b07f1fd96..084a046fa 100644 --- a/src/utils/common.hpp +++ b/src/utils/common.hpp @@ -66,4 +66,11 @@ static std::string IntToHex(T i) return stream.str(); } +template +inline void HashCombine(std::size_t &seed, const T &v) +{ + std::hash hasher{}; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + } // namespace utils \ No newline at end of file diff --git a/src/utils/common_types.cpp b/src/utils/common_types.cpp index 5fc072e49..32791ffda 100644 --- a/src/utils/common_types.cpp +++ b/src/utils/common_types.cpp @@ -7,6 +7,7 @@ // #include "common_types.hpp" +#include "common.hpp" #include #include #include @@ -85,8 +86,39 @@ bool operator==(const SingleSlice &lhs, const SingleSlice &rhs) return ((int)*lhs.sd) == ((int)*rhs.sd); } +bool operator==(const Plmn &lhs, const Plmn &rhs) +{ + if (lhs.mcc != rhs.mcc) + return false; + if (lhs.mnc != rhs.mnc) + return false; + return lhs.isLongMnc == rhs.isLongMnc; +} + +bool operator==(const GlobalNci &lhs, const GlobalNci &rhs) +{ + return lhs.plmn == rhs.plmn && lhs.nci == rhs.nci; +} + void NetworkSlice::addIfNotExists(const SingleSlice &slice) { if (!std::any_of(slices.begin(), slices.end(), [&slice](auto &s) { return s == slice; })) slices.push_back(slice); } + +std::size_t std::hash::operator()(const Plmn &v) const noexcept +{ + std::size_t h = 0; + utils::HashCombine(h, v.mcc); + utils::HashCombine(h, v.mnc); + utils::HashCombine(h, v.isLongMnc); + return h; +} + +std::size_t std::hash::operator()(const GlobalNci &v) const noexcept +{ + std::size_t h = 0; + utils::HashCombine(h, v.plmn); + utils::HashCombine(h, v.nci); + return h; +} diff --git a/src/utils/common_types.hpp b/src/utils/common_types.hpp index f2be86dc2..b2810583d 100644 --- a/src/utils/common_types.hpp +++ b/src/utils/common_types.hpp @@ -12,9 +12,9 @@ #include "octet.hpp" #include #include +#include #include #include -#include enum class EPagingDrx { @@ -119,6 +119,74 @@ enum class EDeregCause ECALL_INACTIVITY, }; +enum class EInitialRegCause +{ + UNSPECIFIED, + EMERGENCY_SERVICES, + MM_DEREG_NORMAL_SERVICE, + T3346_EXPIRY, + DUE_TO_DEREGISTRATION, + DUE_TO_SERVICE_REJECT, +}; + +struct GlobalNci +{ + Plmn plmn{}; + int64_t nci{}; + + GlobalNci() = default; + + GlobalNci(const Plmn &plmn, int64_t nci) : plmn(plmn), nci(nci) + { + } +}; + +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{}; +}; + +struct Vector3 +{ + int x{}; + int y{}; + int z{}; + + Vector3() = default; + + Vector3(int x, int y, int z) : x(x), y(y), z(z) + { + } +}; + +bool operator==(const SingleSlice &lhs, const SingleSlice &rhs); +bool operator==(const Plmn &lhs, const Plmn &rhs); +bool operator==(const GlobalNci &lhs, const GlobalNci &rhs); + Json ToJson(const Supi &v); Json ToJson(const Plmn &v); Json ToJson(const SingleSlice &v); @@ -126,4 +194,19 @@ Json ToJson(const NetworkSlice &v); Json ToJson(const PlmnSupport &v); Json ToJson(const EDeregCause &v); -bool operator==(const SingleSlice &lhs, const SingleSlice &rhs); +namespace std +{ + +template <> +struct hash +{ + std::size_t operator()(const Plmn &v) const noexcept; +}; + +template <> +struct hash +{ + std::size_t operator()(const GlobalNci &v) const noexcept; +}; + +} // namespace std \ No newline at end of file diff --git a/src/utils/constants.hpp b/src/utils/constants.hpp index edee487e1..25d8e80ab 100644 --- a/src/utils/constants.hpp +++ b/src/utils/constants.hpp @@ -15,10 +15,10 @@ struct cons // Version information static constexpr const uint8_t Major = 3; static constexpr const uint8_t Minor = 1; - static constexpr const uint8_t Patch = 6; + static constexpr const uint8_t Patch = 7; static constexpr const char *Project = "UERANSIM"; - static constexpr const char *Tag = "v3.1.6"; - static constexpr const char *Name = "UERANSIM v3.1.6"; + static constexpr const char *Tag = "v3.1.7"; + static constexpr const char *Name = "UERANSIM v3.1.7"; static constexpr const char *Owner = "ALİ GÜNGÖR"; // Some port values diff --git a/src/utils/nts.hpp b/src/utils/nts.hpp index f2ab32313..6831dea64 100644 --- a/src/utils/nts.hpp +++ b/src/utils/nts.hpp @@ -34,27 +34,26 @@ enum class NtsMessageType UDP_SERVER_RECEIVE, CLI_SEND_RESPONSE, - GNB_MR_TO_MR, - GNB_MR_TO_RRC, - GNB_RRC_TO_MR, + GNB_RLS_TO_RRC, + GNB_RLS_TO_GTP, + GNB_GTP_TO_RLS, + GNB_RRC_TO_RLS, GNB_NGAP_TO_RRC, GNB_RRC_TO_NGAP, GNB_NGAP_TO_GTP, - GNB_MR_TO_GTP, - GNB_GTP_TO_MR, GNB_SCTP, - UE_MR_TO_MR, - UE_MR_TO_RRC, - UE_MR_TO_APP, - UE_APP_TO_MR, + 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_MR, - UE_NAS_TO_NAS, - UE_NAS_TO_APP, + UE_RRC_TO_RLS, + UE_NAS_TO_NAS, + UE_RLS_TO_RRC, + UE_RLS_TO_APP, + UE_NAS_TO_APP, }; struct NtsMessage @@ -107,6 +106,7 @@ class TimerBase }; // TODO: Limit queue size? +// todo: message priority, especially control plane messages should have more priorty in appTask etc class NtsTask { private: diff --git a/src/utils/octet.hpp b/src/utils/octet.hpp index ba5a14825..23f8cdf37 100644 --- a/src/utils/octet.hpp +++ b/src/utils/octet.hpp @@ -42,7 +42,7 @@ struct octet return static_cast(value); } - inline bool bit(int index) const + [[nodiscard]] inline bool bit(int index) const { assert(index >= 0 && index <= 7); std::bitset<8> bitset = value; @@ -93,11 +93,6 @@ struct octet2 { return value; } - - explicit constexpr operator int16_t() const - { - return static_cast(value); - } }; struct octet3 @@ -171,15 +166,20 @@ struct octet4 return (value >> (24 - index * 8)) & 0xFF; } - explicit constexpr operator int32_t() const + inline explicit constexpr operator int32_t() const { return static_cast(value); } - explicit constexpr operator uint32_t() const + inline explicit constexpr operator uint32_t() const { return value; } + + inline bool operator==(const octet4 &other) const + { + return value == other.value; + } }; struct octet8 diff --git a/src/utils/octet_string.cpp b/src/utils/octet_string.cpp index 186f16168..d3339b170 100644 --- a/src/utils/octet_string.cpp +++ b/src/utils/octet_string.cpp @@ -36,6 +36,12 @@ void OctetString::appendOctet2(octet2 v) m_data.push_back(v[1]); } +void OctetString::appendOctet2(uint16_t v) +{ + appendOctet(static_cast(v >> 8 & 0xFF)); + appendOctet(static_cast(v & 0xFF)); +} + void OctetString::appendOctet2(int v) { appendOctet2(octet2{v}); diff --git a/src/utils/octet_string.hpp b/src/utils/octet_string.hpp index 2ab6a6420..6e30a104c 100644 --- a/src/utils/octet_string.hpp +++ b/src/utils/octet_string.hpp @@ -40,6 +40,7 @@ class OctetString void appendOctet(int v); void appendOctet(int bigHalf, int littleHalf); void appendOctet2(octet2 v); + void appendOctet2(uint16_t v); void appendOctet2(int v); void appendOctet3(octet3 v); void appendOctet3(int v); diff --git a/src/utils/octet_view.hpp b/src/utils/octet_view.hpp index 62c69eb89..aa44b06df 100644 --- a/src/utils/octet_view.hpp +++ b/src/utils/octet_view.hpp @@ -63,11 +63,6 @@ class OctetView return octet2{read(), read()}; } - inline int16_t read2S() const - { - return (int16_t)read2(); - } - inline uint16_t read2US() const { return (uint16_t)read2();