diff --git a/README.md b/README.md index 790c78077..36725db07 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- +

@@ -29,7 +29,7 @@ And, since the project is rapidly developing, please make sure that you have alw ## Contributing -Implementing UE and RAN is not an easy task and requires a lot of time. We are always open to public contributions and pull requests. +Implementing UE and RAN is not an easy task and is very time-consuming. We are always open to public contributions and pull requests. ## Supporting diff --git a/src/app/cli_cmd.cpp b/src/app/cli_cmd.cpp index 25d377cc6..d6d782b14 100644 --- a/src/app/cli_cmd.cpp +++ b/src/app/cli_cmd.cpp @@ -23,7 +23,7 @@ static opt::OptionsDescription Desc(const std::string &subCommand, const std::string &desc, const std::string &usage, bool helpIfEmpty) { - return {subCommand, cons::Tag, desc, {}, subCommand, {usage}, helpIfEmpty}; + return {{}, {}, desc, {}, subCommand, {usage}, helpIfEmpty, true}; } class OptionsHandler : public opt::IOptionsHandler @@ -71,9 +71,7 @@ static std::map g_gnbCmdToDescription = { }; static std::map g_gnbCmdToUsage = { - {"status", "[option...]"}, {"info", "[option...]"}, - {"amf-list", "[option...]"}, {"amf-info", " [option...]"}, - {"ue-list", "[option...]"}, {"ue-count", "[option...]"}, + {"status", ""}, {"info", ""}, {"amf-list", ""}, {"amf-info", ""}, {"ue-list", ""}, {"ue-count", ""}, }; static std::map g_gnbCmdToHelpIfEmpty = {{"status", false}, {"info", false}, @@ -84,18 +82,21 @@ static std::map g_ueCmdToDescription = { {"info", "Show some information about the UE"}, {"status", "Show some status information about the UE"}, {"timers", "Dump current status of the timers in the UE"}, + {"deregister", "Perform de-registration by the UE"}, }; static std::map g_ueCmdToUsage = { - {"info", "[option...]"}, - {"status", "[option...]"}, - {"timers", "[option...]"}, + {"info", ""}, + {"status", ""}, + {"timers", ""}, + {"deregister", ""}, }; static std::map g_ueCmdToHelpIfEmpty = { {"info", false}, {"status", false}, {"timers", false}, + {"deregister", true}, }; std::unique_ptr ParseGnbCliCommand(std::vector &&tokens, std::string &error, @@ -224,6 +225,22 @@ std::unique_ptr ParseUeCliCommand(std::vector &&token { return std::make_unique(UeCliCommand::TIMERS); } + else if (subCmd == "deregister") + { + auto cmd = std::make_unique(UeCliCommand::DE_REGISTER); + if (options.positionalCount() == 0) + CMD_ERR("De-registration type is expected") + if (options.positionalCount() > 1) + CMD_ERR("Only one de-registration type is expected") + auto type = options.getPositional(0); + if (type == "switch-off") + cmd->isSwitchOff = true; + else if (type == "disable-5g") + cmd->dueToDisable5g = true; + else if (type != "normal") + CMD_ERR("Invalid de-registration type, possible values are: \"normal\", \"disable-5g\", \"switch-off\"") + return cmd; + } return nullptr; } diff --git a/src/app/cli_cmd.hpp b/src/app/cli_cmd.hpp index f3e8dbf05..cc45c7f5d 100644 --- a/src/app/cli_cmd.hpp +++ b/src/app/cli_cmd.hpp @@ -42,8 +42,13 @@ struct UeCliCommand INFO, STATUS, TIMERS, + DE_REGISTER, } present; + // DE_REGISTER + bool isSwitchOff{}; + bool dueToDisable5g{}; + explicit UeCliCommand(PR present) : present(present) { } diff --git a/src/app/monitor.hpp b/src/app/monitor.hpp index e6cbef568..d8d599df9 100644 --- a/src/app/monitor.hpp +++ b/src/app/monitor.hpp @@ -35,6 +35,7 @@ enum class StateType MM_SUB, RM, CM, + U5, }; class INodeListener diff --git a/src/app/proc_table.hpp b/src/app/proc_table.hpp index ef67523b2..97aa70070 100644 --- a/src/app/proc_table.hpp +++ b/src/app/proc_table.hpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace app @@ -41,4 +42,12 @@ inline void CreateProcTable(const std::unordered_map &nodeMap, i CreateProcTable(nodes, cmdPort); } +template +inline void CreateProcTable(const ConcurrentMap &nodeMap, int cmdPort) +{ + std::vector nodes{}; + nodeMap.invokeForeach([&nodes](const auto &node) { nodes.push_back(node.first); }); + CreateProcTable(nodes, cmdPort); +} + } // namespace app diff --git a/src/app/ue_ctl.cpp b/src/app/ue_ctl.cpp new file mode 100644 index 000000000..81ec45ae6 --- /dev/null +++ b/src/app/ue_ctl.cpp @@ -0,0 +1,9 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "ue_ctl.hpp" diff --git a/src/app/ue_ctl.hpp b/src/app/ue_ctl.hpp new file mode 100644 index 000000000..377b3198a --- /dev/null +++ b/src/app/ue_ctl.hpp @@ -0,0 +1,25 @@ +// +// 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 + +namespace nr::ue +{ +class UserEquipment; +} + +namespace app +{ + +class IUeController +{ + public: + virtual void performSwitchOff(nr::ue::UserEquipment *ue) = 0; +}; + +} // namespace app diff --git a/src/asn/utils/ngap.cpp b/src/asn/utils/ngap.cpp index 41263f612..e16694e1a 100644 --- a/src/asn/utils/ngap.cpp +++ b/src/asn/utils/ngap.cpp @@ -448,8 +448,9 @@ int GetProcedureCode(NgapMessageType messageType) return 40; case NgapMessageType::UEContextReleaseCommand: case NgapMessageType::UEContextReleaseComplete: - case NgapMessageType::UEContextReleaseRequest: return 41; + case NgapMessageType::UEContextReleaseRequest: + return 42; case NgapMessageType::UERadioCapabilityCheckRequest: case NgapMessageType::UERadioCapabilityCheckResponse: case NgapMessageType::UERadioCapabilityInfoIndication: diff --git a/src/cli.cpp b/src/cli.cpp index bb66fa455..fb96b1aa4 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -133,7 +133,7 @@ static void ReadOptions(int argc, char **argv) { opt::OptionsDescription desc{"UERANSIM", cons::Tag, "Command Line Interface", cons::Owner, "nr-cli", {" [option...]", "--dump"}, - true}; + true, false}; opt::OptionItem itemDump = {'d', "dump", "List all UE and gNBs in the environment", std::nullopt}; opt::OptionItem itemExec = {'e', "exec", "Execute the given command directly without an interactive shell", diff --git a/src/gnb.cpp b/src/gnb.cpp index 71f3c1e17..e5b28b6eb 100644 --- a/src/gnb.cpp +++ b/src/gnb.cpp @@ -79,7 +79,7 @@ static void ReadOptions(int argc, char **argv) { opt::OptionsDescription desc{cons::Project, cons::Tag, "5G-SA gNB implementation", cons::Owner, "nr-gnb", {"-c [option...]"}, - true}; + true, false}; opt::OptionItem itemConfigFile = {'c', "config", "Use specified configuration file for gNB", "config-file"}; opt::OptionItem itemDisableCmd = {'l', "disable-cmd", "Disable command line functionality for this instance", @@ -153,7 +153,7 @@ static void ReceiveCommand(app::CliMessage &msg) } auto *gnb = g_gnbMap[msg.nodeName]; - gnb->pushCommand(std::move(cmd), msg.clientAddr, g_cliRespTask); + gnb->pushCommand(std::move(cmd), msg.clientAddr); } static void Loop() @@ -196,15 +196,18 @@ int main(int argc, char **argv) std::cout << cons::Name << std::endl; - auto *gnb = new nr::gnb::GNodeB(g_refConfig, nullptr); + if (!g_options.disableCmd) + { + g_cliServer = new app::CliServer{}; + g_cliRespTask = new app::CliResponseTask(g_cliServer); + } + + auto *gnb = new nr::gnb::GNodeB(g_refConfig, nullptr, g_cliRespTask); g_gnbMap[g_refConfig->name] = gnb; if (!g_options.disableCmd) { - g_cliServer = new app::CliServer{}; app::CreateProcTable(g_gnbMap, g_cliServer->assignedAddress().getPort()); - - g_cliRespTask = new app::CliResponseTask(g_cliServer); g_cliRespTask->start(); } diff --git a/src/gnb/app/cmd_handler.cpp b/src/gnb/app/cmd_handler.cpp index 01256d59f..995f8576e 100644 --- a/src/gnb/app/cmd_handler.cpp +++ b/src/gnb/app/cmd_handler.cpp @@ -23,42 +23,52 @@ namespace nr::gnb { -void GnbCmdHandler::PauseTasks(TaskBase &base) +void GnbCmdHandler::sendResult(const InetAddress &address, const std::string &output) { - base.gtpTask->requestPause(); - base.mrTask->requestPause(); - base.ngapTask->requestPause(); - base.rrcTask->requestPause(); - base.sctpTask->requestPause(); + m_base->cliCallbackTask->push(new app::NwCliSendResponse(address, output, false)); } -void GnbCmdHandler::UnpauseTasks(TaskBase &base) +void GnbCmdHandler::sendError(const InetAddress &address, const std::string &output) { - base.gtpTask->requestUnpause(); - base.mrTask->requestUnpause(); - base.ngapTask->requestUnpause(); - base.rrcTask->requestUnpause(); - base.sctpTask->requestUnpause(); + m_base->cliCallbackTask->push(new app::NwCliSendResponse(address, output, true)); } -bool GnbCmdHandler::IsAllPaused(TaskBase &base) +void GnbCmdHandler::pauseTasks() { - if (!base.gtpTask->isPauseConfirmed()) + m_base->gtpTask->requestPause(); + m_base->mrTask->requestPause(); + m_base->ngapTask->requestPause(); + m_base->rrcTask->requestPause(); + m_base->sctpTask->requestPause(); +} + +void GnbCmdHandler::unpauseTasks() +{ + m_base->gtpTask->requestUnpause(); + m_base->mrTask->requestUnpause(); + m_base->ngapTask->requestUnpause(); + m_base->rrcTask->requestUnpause(); + m_base->sctpTask->requestUnpause(); +} + +bool GnbCmdHandler::isAllPaused() +{ + if (!m_base->gtpTask->isPauseConfirmed()) return false; - if (!base.mrTask->isPauseConfirmed()) + if (!m_base->mrTask->isPauseConfirmed()) return false; - if (!base.ngapTask->isPauseConfirmed()) + if (!m_base->ngapTask->isPauseConfirmed()) return false; - if (!base.rrcTask->isPauseConfirmed()) + if (!m_base->rrcTask->isPauseConfirmed()) return false; - if (!base.sctpTask->isPauseConfirmed()) + if (!m_base->sctpTask->isPauseConfirmed()) return false; return true; } -void GnbCmdHandler::HandleCmd(TaskBase &base, NwGnbCliCommand &msg) +void GnbCmdHandler::handleCmd(NwGnbCliCommand &msg) { - PauseTasks(base); + pauseTasks(); uint64_t currentTime = utils::CurrentTimeMillis(); uint64_t endTime = currentTime + PAUSE_CONFIRM_TIMEOUT; @@ -67,7 +77,7 @@ void GnbCmdHandler::HandleCmd(TaskBase &base, NwGnbCliCommand &msg) while (currentTime < endTime) { currentTime = utils::CurrentTimeMillis(); - if (IsAllPaused(base)) + if (isAllPaused()) { isPaused = true; break; @@ -77,60 +87,60 @@ void GnbCmdHandler::HandleCmd(TaskBase &base, NwGnbCliCommand &msg) if (!isPaused) { - msg.sendError("gNB is unable process command due to pausing timeout"); + sendError(msg.address, "gNB is unable process command due to pausing timeout"); } else { - HandleCmdImpl(base, msg); + handleCmdImpl(msg); } - UnpauseTasks(base); + unpauseTasks(); } -void GnbCmdHandler::HandleCmdImpl(TaskBase &base, NwGnbCliCommand &msg) +void GnbCmdHandler::handleCmdImpl(NwGnbCliCommand &msg) { switch (msg.cmd->present) { case app::GnbCliCommand::STATUS: { - msg.sendResult(ToJson(base.appTask->m_statusInfo).dumpYaml()); + sendResult(msg.address, ToJson(m_base->appTask->m_statusInfo).dumpYaml()); break; } case app::GnbCliCommand::INFO: { - msg.sendResult(ToJson(*base.config).dumpYaml()); + sendResult(msg.address, ToJson(*m_base->config).dumpYaml()); break; } case app::GnbCliCommand::AMF_LIST: { Json json = Json::Arr({}); - for (auto &amf : base.ngapTask->m_amfCtx) + for (auto &amf : m_base->ngapTask->m_amfCtx) json.push(Json::Obj({{"id", amf.first}})); - msg.sendResult(json.dumpYaml()); + sendResult(msg.address, json.dumpYaml()); break; } case app::GnbCliCommand::AMF_INFO: { - if (base.ngapTask->m_amfCtx.count(msg.cmd->amfId) == 0) - msg.sendError("AMF not found with given ID"); + if (m_base->ngapTask->m_amfCtx.count(msg.cmd->amfId) == 0) + sendError(msg.address, "AMF not found with given ID"); else { - auto amf = base.ngapTask->m_amfCtx[msg.cmd->amfId]; - msg.sendResult(ToJson(*amf).dumpYaml()); + auto amf = m_base->ngapTask->m_amfCtx[msg.cmd->amfId]; + sendResult(msg.address, ToJson(*amf).dumpYaml()); } break; } case app::GnbCliCommand::UE_LIST: { Json json = Json::Arr({}); - for (auto &ue : base.ngapTask->m_ueCtx) + for (auto &ue : m_base->ngapTask->m_ueCtx) { json.push(Json::Obj({ - {"ue-name", base.mrTask->m_ueMap[ue.first].name}, + {"ue-name", m_base->mrTask->m_ueMap[ue.first].name}, {"ran-ngap-id", ue.second->ranUeNgapId}, {"amf-ngap-id", ue.second->amfUeNgapId}, })); } - msg.sendResult(json.dumpYaml()); + sendResult(msg.address, json.dumpYaml()); break; } case app::GnbCliCommand::UE_COUNT: { - msg.sendResult(std::to_string(base.ngapTask->m_ueCtx.size())); + sendResult(msg.address, std::to_string(m_base->ngapTask->m_ueCtx.size())); break; } } diff --git a/src/gnb/app/cmd_handler.hpp b/src/gnb/app/cmd_handler.hpp index 784e36eb2..4341dbff7 100644 --- a/src/gnb/app/cmd_handler.hpp +++ b/src/gnb/app/cmd_handler.hpp @@ -17,15 +17,26 @@ namespace nr::gnb class GnbCmdHandler { private: - static void PauseTasks(TaskBase &base); - static void UnpauseTasks(TaskBase &base); - static bool IsAllPaused(TaskBase &base); + TaskBase *m_base; public: - static void HandleCmd(TaskBase &base, NwGnbCliCommand &msg); + explicit GnbCmdHandler(TaskBase *base) : m_base(base) + { + } + + void handleCmd(NwGnbCliCommand &msg); + + private: + void pauseTasks(); + void unpauseTasks(); + bool isAllPaused(); + + private: + void handleCmdImpl(NwGnbCliCommand &msg); private: - static void HandleCmdImpl(TaskBase &base, NwGnbCliCommand &msg); + void sendResult(const InetAddress &address, const std::string &output); + void sendError(const InetAddress &address, const std::string &output); }; } // namespace nr::gnb diff --git a/src/gnb/app/task.cpp b/src/gnb/app/task.cpp index 02a5f39fa..7b5d9f0ee 100644 --- a/src/gnb/app/task.cpp +++ b/src/gnb/app/task.cpp @@ -42,7 +42,8 @@ void GnbAppTask::onLoop() } case NtsMessageType::GNB_CLI_COMMAND: { auto *w = dynamic_cast(msg); - GnbCmdHandler::HandleCmd(*m_base, *w); + GnbCmdHandler handler{m_base}; + handler.handleCmd(*w); break; } default: diff --git a/src/gnb/gnb.cpp b/src/gnb/gnb.cpp index ab9515032..a277bc437 100644 --- a/src/gnb/gnb.cpp +++ b/src/gnb/gnb.cpp @@ -20,12 +20,13 @@ namespace nr::gnb { -GNodeB::GNodeB(GnbConfig *config, app::INodeListener *nodeListener) +GNodeB::GNodeB(GnbConfig *config, app::INodeListener *nodeListener, NtsTask *cliCallbackTask) { auto *base = new TaskBase(); base->config = config; base->logBase = new LogBase("logs/" + config->name + ".log"); base->nodeListener = nodeListener; + base->cliCallbackTask = cliCallbackTask; base->appTask = new GnbAppTask(base); base->sctpTask = new SctpTask(base); @@ -68,9 +69,9 @@ void GNodeB::start() taskBase->gtpTask->start(); } -void GNodeB::pushCommand(std::unique_ptr cmd, const InetAddress &address, NtsTask *callbackTask) +void GNodeB::pushCommand(std::unique_ptr cmd, const InetAddress &address) { - taskBase->appTask->push(new NwGnbCliCommand(std::move(cmd), address, callbackTask)); + taskBase->appTask->push(new NwGnbCliCommand(std::move(cmd), address)); } } // namespace nr::gnb diff --git a/src/gnb/gnb.hpp b/src/gnb/gnb.hpp index ca357cc7c..07f7af994 100644 --- a/src/gnb/gnb.hpp +++ b/src/gnb/gnb.hpp @@ -27,12 +27,12 @@ class GNodeB TaskBase *taskBase; public: - GNodeB(GnbConfig *config, app::INodeListener *nodeListener); + GNodeB(GnbConfig *config, app::INodeListener *nodeListener, NtsTask *cliCallbackTask); virtual ~GNodeB(); public: void start(); - void pushCommand(std::unique_ptr cmd, const InetAddress &address, NtsTask *callbackTask); + void pushCommand(std::unique_ptr cmd, const InetAddress &address); }; } // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/gtp/task.cpp b/src/gnb/gtp/task.cpp index 94284284c..61a6650fa 100644 --- a/src/gnb/gtp/task.cpp +++ b/src/gnb/gtp/task.cpp @@ -66,6 +66,10 @@ void GtpTask::onLoop() handleSessionCreate(w->resource); break; } + case NwGnbNgapToGtp::UE_CONTEXT_RELEASE: { + handleUeContextDelete(w->ueId); + break; + } } break; } @@ -119,6 +123,34 @@ void GtpTask::handleSessionCreate(PduSessionResource *session) updateAmbrForSession(sessionInd); } +void GtpTask::handleUeContextDelete(int ueId) +{ + // Find PDU sessions of the UE + std::vector sessions{}; + m_sessionTree.enumerateByUe(ueId, sessions); + + for (auto &session : sessions) + { + // Remove all session information from rate limiter + m_rateLimiter->updateSessionUplinkLimit(session, 0); + m_rateLimiter->updateUeDownlinkLimit(session, 0); + + // And remove from PDU session table + int teid = m_pduSessions[session]->downTunnel.teid; + m_pduSessions.erase(session); + + // And remove from the tree + m_sessionTree.remove(session, teid); + } + + // Remove all user information from rate limiter + m_rateLimiter->updateUeUplinkLimit(ueId, 0); + m_rateLimiter->updateUeDownlinkLimit(ueId, 0); + + // Remove UE context + m_ueContexts.erase(ueId); +} + void GtpTask::handleUplinkData(int ueId, int psi, OctetString &&pdu) { const uint8_t *data = pdu.data(); diff --git a/src/gnb/gtp/task.hpp b/src/gnb/gtp/task.hpp index 9bfd64ec6..bf7603cee 100644 --- a/src/gnb/gtp/task.hpp +++ b/src/gnb/gtp/task.hpp @@ -48,6 +48,7 @@ class GtpTask : public NtsTask void handleUdpReceive(const udp::NwUdpServerReceive &msg); void handleUeContextUpdate(const GtpUeContextUpdate &msg); void handleSessionCreate(PduSessionResource *session); + void handleUeContextDelete(int ueId); void handleUplinkData(int ueId, int psi, OctetString &&data); void updateAmbrForUe(int ueId); diff --git a/src/gnb/gtp/utils.cpp b/src/gnb/gtp/utils.cpp index 5acae54c4..a2bfad0ad 100644 --- a/src/gnb/gtp/utils.cpp +++ b/src/gnb/gtp/utils.cpp @@ -57,6 +57,16 @@ void PduSessionTree::remove(uint64_t session, uint32_t downTeid) } } +void PduSessionTree::enumerateByUe(int ue, std::vector &output) +{ + if (mapByUeId.count(ue) == 0) + return; + auto &map = mapByUeId[ue]; + + for (auto &item : map) + output.push_back(item.second); +} + TokenBucket::TokenBucket(long byteCapacity) : byteCapacity(byteCapacity) { if (byteCapacity > 0) diff --git a/src/gnb/gtp/utils.hpp b/src/gnb/gtp/utils.hpp index c2b5b5f7e..a7db2ee18 100644 --- a/src/gnb/gtp/utils.hpp +++ b/src/gnb/gtp/utils.hpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace nr::gnb { @@ -52,6 +53,7 @@ class PduSessionTree uint64_t findByDownTeid(uint32_t teid); uint64_t findBySessionId(int ue, int psi); void remove(uint64_t session, uint32_t downTeid); + void enumerateByUe(int ue, std::vector& output); }; class TokenBucket diff --git a/src/gnb/mr/task.cpp b/src/gnb/mr/task.cpp index 349901dee..20ade5315 100644 --- a/src/gnb/mr/task.cpp +++ b/src/gnb/mr/task.cpp @@ -8,9 +8,9 @@ #include "task.hpp" #include "rls.hpp" +#include #include #include -#include #include #include @@ -105,6 +105,10 @@ void GnbMrTask::onLoop() m_rlsEntity->setAcceptConnections(true); break; } + case NwGnbRrcToMr::AN_RELEASE: { + m_rlsEntity->localReleaseConnection(w->ueId, rls::ECause::RRC_RELEASE); + break; + } } break; } @@ -150,6 +154,15 @@ void GnbMrTask::onUeConnected(int ue, const std::string &name) 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 is now: %d", m_ueMap.size()); } diff --git a/src/gnb/ngap/context.cpp b/src/gnb/ngap/context.cpp index 3b9c4675f..4bdf946e9 100644 --- a/src/gnb/ngap/context.cpp +++ b/src/gnb/ngap/context.cpp @@ -9,6 +9,9 @@ #include "task.hpp" #include "utils.hpp" +#include +#include + #include #include #include @@ -22,8 +25,8 @@ #include #include #include +#include #include -#include namespace nr::gnb { @@ -63,8 +66,15 @@ void NgapTask::receiveContextRelease(int amfId, ASN_NGAP_UEContextReleaseCommand if (ue == nullptr) return; - // todo: NG-RAN node shall release all related signalling and user data transport resources - // ... + // Notify RRC task + auto *w1 = new NwGnbNgapToRrc(NwGnbNgapToRrc::AN_RELEASE); + w1->ueId = ue->ctxId; + m_base->rrcTask->push(w1); + + // Notify GTP task + auto *w2 = new NwGnbNgapToGtp(NwGnbNgapToGtp::UE_CONTEXT_RELEASE); + w2->ueId = ue->ctxId; + m_base->gtpTask->push(w2); auto *response = asn::ngap::NewMessagePdu({}); sendNgapUeAssociated(ue->ctxId, response); @@ -103,4 +113,18 @@ void NgapTask::receiveContextModification(int amfId, ASN_NGAP_UEContextModificat m_base->gtpTask->push(w); } +void NgapTask::sendContextRelease(int ueId, NgapCause cause) +{ + m_logger->debug("Sending UE Context release request (NG-RAN node initiated)"); + + auto *ieCause = asn::New(); + ieCause->id = ASN_NGAP_ProtocolIE_ID_id_Cause; + ieCause->criticality = ASN_NGAP_Criticality_ignore; + ieCause->value.present = ASN_NGAP_UEContextReleaseRequest_IEs__value_PR_Cause; + ngap_utils::ToCauseAsn_Ref(cause, ieCause->value.choice.Cause); + + auto *pdu = asn::ngap::NewMessagePdu({ieCause}); + sendNgapUeAssociated(ueId, pdu); +} + } // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/ngap/interface.cpp b/src/gnb/ngap/interface.cpp index 79e62f061..fb12be8a4 100644 --- a/src/gnb/ngap/interface.cpp +++ b/src/gnb/ngap/interface.cpp @@ -11,8 +11,8 @@ #include #include -#include #include +#include #include #include @@ -28,6 +28,7 @@ #include #include #include +#include namespace nr::gnb { @@ -298,4 +299,60 @@ void NgapTask::receiveAmfConfigurationUpdate(int amfId, ASN_NGAP_AMFConfiguratio } } +void NgapTask::receiveOverloadStart(int amfId, ASN_NGAP_OverloadStart *msg) +{ + m_logger->debug("AMF overload start received"); + + auto *amf = findAmfContext(amfId); + if (amf == nullptr) + return; + + amf->overloadInfo = {}; + amf->overloadInfo.status = EOverloadStatus::OVERLOADED; + + auto *ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMFOverloadResponse); + if (ie && ie->OverloadResponse.present == ASN_NGAP_OverloadResponse_PR_overloadAction) + { + switch (ie->OverloadResponse.choice.overloadAction) + { + case ASN_NGAP_OverloadAction_reject_non_emergency_mo_dt: + amf->overloadInfo.indication.action = EOverloadAction::REJECT_NON_EMERGENCY_MO_DATA; + break; + case ASN_NGAP_OverloadAction_reject_rrc_cr_signalling: + amf->overloadInfo.indication.action = EOverloadAction::REJECT_SIGNALLING; + break; + case ASN_NGAP_OverloadAction_permit_emergency_sessions_and_mobile_terminated_services_only: + amf->overloadInfo.indication.action = EOverloadAction::ONLY_EMERGENCY_AND_MT; + break; + case ASN_NGAP_OverloadAction_permit_high_priority_sessions_and_mobile_terminated_services_only: + amf->overloadInfo.indication.action = EOverloadAction::ONLY_HIGH_PRI_AND_MT; + break; + default: + m_logger->warn("AMF overload action [%d] could not understand", + (int)ie->OverloadResponse.choice.overloadAction); + break; + } + } + + ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMFTrafficLoadReductionIndication); + if (ie) + amf->overloadInfo.indication.loadReductionPerc = static_cast(ie->TrafficLoadReductionIndication); + + ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_OverloadStartNSSAIList); + if (ie) + { + // TODO + /*asn::ForeachItem(ie->OverloadStartNSSAIList, [](auto &item) { + item.sliceOverloadList; + });*/ + } +} + +void NgapTask::receiveOverloadStop(int amfId, ASN_NGAP_OverloadStop *msg) +{ + m_logger->debug("AMF overload stop received"); + + // TODO +} + } // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/ngap/radio.cpp b/src/gnb/ngap/radio.cpp new file mode 100644 index 000000000..5c2acf1f9 --- /dev/null +++ b/src/gnb/ngap/radio.cpp @@ -0,0 +1,30 @@ +// +// 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 "encode.hpp" +#include "task.hpp" +#include "utils.hpp" + +#include +#include + +namespace nr::gnb +{ + +void NgapTask::handleRadioLinkFailure(int ueId) +{ + // Notify GTP task + auto *w2 = new NwGnbNgapToGtp(NwGnbNgapToGtp::UE_CONTEXT_RELEASE); + w2->ueId = ueId; + m_base->gtpTask->push(w2); + + // Notify AMF + sendContextRelease(ueId, NgapCause::RadioNetwork_radio_connection_with_ue_lost); +} + +} // namespace nr::gnb diff --git a/src/gnb/ngap/task.cpp b/src/gnb/ngap/task.cpp index 2ea3b90da..c00b1979d 100644 --- a/src/gnb/ngap/task.cpp +++ b/src/gnb/ngap/task.cpp @@ -60,6 +60,10 @@ void NgapTask::onLoop() handleUplinkNasTransport(w->ueId, w->pdu); break; } + case NwGnbRrcToNgap::RADIO_LINK_FAILURE: { + handleRadioLinkFailure(w->ueId); + break; + } } break; } diff --git a/src/gnb/ngap/task.hpp b/src/gnb/ngap/task.hpp index 885eeefec..cea09762c 100644 --- a/src/gnb/ngap/task.hpp +++ b/src/gnb/ngap/task.hpp @@ -28,6 +28,8 @@ extern "C" struct ASN_NGAP_InitialContextSetupRequest; extern "C" struct ASN_NGAP_UEContextReleaseCommand; extern "C" struct ASN_NGAP_UEContextModificationRequest; extern "C" struct ASN_NGAP_AMFConfigurationUpdate; +extern "C" struct ASN_NGAP_OverloadStart; +extern "C" struct ASN_NGAP_OverloadStop; namespace nr::gnb { @@ -81,6 +83,8 @@ class NgapTask : public NtsTask void receiveNgSetupFailure(int amfId, ASN_NGAP_NGSetupFailure *msg); void receiveErrorIndication(int amfId, ASN_NGAP_ErrorIndication *msg); void receiveAmfConfigurationUpdate(int amfId, ASN_NGAP_AMFConfigurationUpdate *msg); + void receiveOverloadStart(int amfId, ASN_NGAP_OverloadStart *msg); + void receiveOverloadStop(int amfId, ASN_NGAP_OverloadStop *msg); /* Message transport */ void sendNgapNonUe(int amfId, ASN_NGAP_NGAP_PDU *pdu); @@ -104,10 +108,14 @@ class NgapTask : public NtsTask void receiveInitialContextSetup(int amfId, ASN_NGAP_InitialContextSetupRequest *msg); void receiveContextRelease(int amfId, ASN_NGAP_UEContextReleaseCommand *msg); void receiveContextModification(int amfId, ASN_NGAP_UEContextModificationRequest *msg); + void sendContextRelease(int ueId, NgapCause cause); /* NAS Node Selection */ NgapAmfContext *selectAmf(int ueId); NgapAmfContext *selectNewAmfForReAllocation(int ueId, int initiatedAmfId, int amfSetId); + + /* Radio resource control */ + void handleRadioLinkFailure(int ueId); }; } // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/ngap/transport.cpp b/src/gnb/ngap/transport.cpp index a2effa6cd..aefccd79f 100644 --- a/src/gnb/ngap/transport.cpp +++ b/src/gnb/ngap/transport.cpp @@ -224,6 +224,12 @@ void NgapTask::handleSctpMessage(int amfId, uint16_t stream, const UniqueBuffer case ASN_NGAP_InitiatingMessage__value_PR_AMFConfigurationUpdate: receiveAmfConfigurationUpdate(amf->ctxId, &value.choice.AMFConfigurationUpdate); break; + case ASN_NGAP_InitiatingMessage__value_PR_OverloadStart: + receiveOverloadStart(amf->ctxId, &value.choice.OverloadStart); + break; + case ASN_NGAP_InitiatingMessage__value_PR_OverloadStop: + receiveOverloadStop(amf->ctxId, &value.choice.OverloadStop); + 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 522417ab4..4d724692a 100644 --- a/src/gnb/nts.hpp +++ b/src/gnb/nts.hpp @@ -28,11 +28,15 @@ struct NwGnbMrToRrc : NtsMessage { enum PR { - RRC_PDU_DELIVERY + RRC_PDU_DELIVERY, + RADIO_LINK_FAILURE, } present; // RRC_PDU_DELIVERY + // RADIO_LINK_FAILURE int ueId{}; + + // RRC_PDU_DELIVERY rrc::RrcChannel channel{}; OctetString pdu{}; @@ -46,11 +50,15 @@ struct NwGnbRrcToMr : NtsMessage enum PR { NGAP_LAYER_INITIALIZED, - RRC_PDU_DELIVERY + RRC_PDU_DELIVERY, + AN_RELEASE, } present; // RRC_PDU_DELIVERY + // AN_RELEASE int ueId{}; + + // RRC_PDU_DELIVERY rrc::RrcChannel channel{}; OctetString pdu{}; @@ -65,10 +73,14 @@ struct NwGnbNgapToRrc : NtsMessage { NGAP_LAYER_INITIALIZED, NAS_DELIVERY, + AN_RELEASE, } present; // NAS_DELIVERY + // AN_RELEASE int ueId{}; + + // NAS_DELIVERY OctetString pdu{}; explicit NwGnbNgapToRrc(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_RRC), present(present) @@ -81,12 +93,17 @@ struct NwGnbRrcToNgap : NtsMessage enum PR { INITIAL_NAS_DELIVERY, - UPLINK_NAS_DELIVERY + UPLINK_NAS_DELIVERY, + RADIO_LINK_FAILURE } present; // INITIAL_NAS_DELIVERY // UPLINK_NAS_DELIVERY + // RADIO_LINK_FAILURE int ueId{}; + + // INITIAL_NAS_DELIVERY + // UPLINK_NAS_DELIVERY OctetString pdu{}; // INITIAL_NAS_DELIVERY @@ -102,7 +119,8 @@ struct NwGnbNgapToGtp : NtsMessage enum PR { UE_CONTEXT_UPDATE, - SESSION_CREATE + SESSION_CREATE, + UE_CONTEXT_RELEASE, } present; // UE_CONTEXT_UPDATE @@ -111,6 +129,9 @@ struct NwGnbNgapToGtp : NtsMessage // SESSION_CREATE PduSessionResource *resource{}; + // UE_CONTEXT_RELEASE + int ueId{}; + explicit NwGnbNgapToGtp(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_GTP), present(present) { } @@ -246,21 +267,10 @@ struct NwGnbCliCommand : NtsMessage { std::unique_ptr cmd; InetAddress address; - NtsTask *callbackTask; - - NwGnbCliCommand(std::unique_ptr cmd, InetAddress address, NtsTask *callbackTask) - : NtsMessage(NtsMessageType::GNB_CLI_COMMAND), cmd(std::move(cmd)), address(address), callbackTask(callbackTask) - { - } - - void sendResult(const std::string &output) const - { - callbackTask->push(new app::NwCliSendResponse(address, output, false)); - } - void sendError(const std::string &output) const + NwGnbCliCommand(std::unique_ptr cmd, InetAddress address) + : NtsMessage(NtsMessageType::GNB_CLI_COMMAND), cmd(std::move(cmd)), address(address) { - callbackTask->push(new app::NwCliSendResponse(address, output, true)); } }; diff --git a/src/gnb/rrc/handler.cpp b/src/gnb/rrc/handler.cpp index 6ce46516e..3e921ea42 100644 --- a/src/gnb/rrc/handler.cpp +++ b/src/gnb/rrc/handler.cpp @@ -7,6 +7,11 @@ // #include "task.hpp" + +#include +#include +#include + #include #include #include @@ -15,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -25,8 +32,6 @@ #include #include #include -#include -#include namespace nr::gnb { @@ -134,4 +139,40 @@ void GnbRrcTask::receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplet m_base->ngapTask->push(w); } +void GnbRrcTask::releaseConnection(int ueId) +{ + m_logger->debug("Releasing RRC connection for UE[%d]", ueId); + + // Send RRC Release message + auto *pdu = asn::New(); + pdu->message.present = ASN_RRC_DL_DCCH_MessageType_PR_c1; + pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1); + pdu->message.choice.c1->present = ASN_RRC_DL_DCCH_MessageType__c1_PR_rrcRelease; + auto &rrcRelease = pdu->message.choice.c1->choice.rrcRelease = asn::New(); + rrcRelease->rrc_TransactionIdentifier = getNextTid(); + rrcRelease->criticalExtensions.present = ASN_RRC_RRCRelease__criticalExtensions_PR_rrcRelease; + rrcRelease->criticalExtensions.choice.rrcRelease = asn::New(); + + 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); +} + +void GnbRrcTask::handleRadioLinkFailure(int ueId) +{ + // Notify NGAP task + auto *w = new NwGnbRrcToNgap(NwGnbRrcToNgap::RADIO_LINK_FAILURE); + w->ueId = ueId; + m_base->ngapTask->push(w); + + // Delete UE RRC context + m_ueCtx.erase(ueId); +} + } // namespace nr::gnb \ No newline at end of file diff --git a/src/gnb/rrc/task.cpp b/src/gnb/rrc/task.cpp index 5078669be..cd9b6f7ab 100644 --- a/src/gnb/rrc/task.cpp +++ b/src/gnb/rrc/task.cpp @@ -46,6 +46,10 @@ void GnbRrcTask::onLoop() handleUplinkRrc(w->ueId, w->channel, w->pdu); break; } + case NwGnbMrToRrc::RADIO_LINK_FAILURE: { + handleRadioLinkFailure(w->ueId); + break; + } } break; } @@ -61,6 +65,10 @@ void GnbRrcTask::onLoop() handleDownlinkNasDelivery(w->ueId, w->pdu); break; } + case NwGnbNgapToRrc::AN_RELEASE: { + releaseConnection(w->ueId); + break; + } } break; } diff --git a/src/gnb/rrc/task.hpp b/src/gnb/rrc/task.hpp index 512078d1e..9cefe6dae 100644 --- a/src/gnb/rrc/task.hpp +++ b/src/gnb/rrc/task.hpp @@ -69,6 +69,8 @@ class GnbRrcTask : public NtsTask void handleUplinkRrc(int ueId, rrc::RrcChannel channel, const OctetString &rrcPdu); void handleDownlinkNasDelivery(int ueId, const OctetString &nasPdu); void deliverUplinkNas(int ueId, OctetString &&nasPdu); + void releaseConnection(int ueId); + void handleRadioLinkFailure(int ueId); void receiveUplinkInformationTransfer(int ueId, const ASN_RRC_ULInformationTransfer &msg); void receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg); diff --git a/src/gnb/types.hpp b/src/gnb/types.hpp index a1e287dc1..7ba55157c 100644 --- a/src/gnb/types.hpp +++ b/src/gnb/types.hpp @@ -30,44 +30,77 @@ class SctpTask; enum class EAmfState { - NOT_CONNECTED, + NOT_CONNECTED = 0, WAITING_NG_SETUP, CONNECTED }; struct SctpAssociation { - int associationId; - int inStreams; - int outStreams; + int associationId{}; + int inStreams{}; + int outStreams{}; }; struct Guami { - Plmn plmn; - int amfRegionId; // 8-bit - int amfSetId; // 10-bit - int amfPointer; // 6-bit + Plmn plmn{}; + int amfRegionId{}; // 8-bit + int amfSetId{}; // 10-bit + int amfPointer{}; // 6-bit }; struct ServedGuami { - Guami guami; - std::string backupAmfName; + Guami guami{}; + std::string backupAmfName{}; +}; + +// TODO: update cli and json for overload related types + +enum class EOverloadAction +{ + UNSPECIFIED_OVERLOAD, + REJECT_NON_EMERGENCY_MO_DATA, + REJECT_SIGNALLING, + ONLY_EMERGENCY_AND_MT, + ONLY_HIGH_PRI_AND_MT, +}; + +enum class EOverloadStatus +{ + NOT_OVERLOADED, + OVERLOADED +}; + +struct OverloadInfo +{ + struct Indication + { + // Reduce the signalling traffic by the indicated percentage + int loadReductionPerc{}; + + // If reduction percentage is not present, this action shall be used + EOverloadAction action{}; + }; + + EOverloadStatus status{}; + Indication indication{}; }; struct NgapAmfContext { - int ctxId; - SctpAssociation association; - int nextStream; // next available SCTP stream for uplink - std::string address; - uint16_t port; - std::string amfName; - long relativeCapacity; - EAmfState state; - std::vector servedGuamiList; - std::vector plmnSupportList; + int ctxId{}; + SctpAssociation association{}; + int nextStream{}; // next available SCTP stream for uplink + std::string address{}; + uint16_t port{}; + std::string amfName{}; + long relativeCapacity{}; + EAmfState state{}; + OverloadInfo overloadInfo{}; + std::vector servedGuamiList{}; + std::vector plmnSupportList{}; }; struct AggregateMaximumBitRate @@ -78,7 +111,7 @@ struct AggregateMaximumBitRate struct NgapUeContext { - const int ctxId; + const int ctxId{}; int64_t amfUeNgapId = -1; // -1 if not assigned int64_t ranUeNgapId{}; @@ -94,7 +127,7 @@ struct NgapUeContext struct RrcUeContext { - const int ueId; + const int ueId{}; int64_t initialRandomId = -1; long establishmentCause{}; @@ -106,8 +139,8 @@ struct RrcUeContext struct NgapIdPair { - std::optional amfUeNgapId; - std::optional ranUeNgapId; + std::optional amfUeNgapId{}; + std::optional ranUeNgapId{}; NgapIdPair() : amfUeNgapId{}, ranUeNgapId{} { @@ -209,7 +242,7 @@ struct PduSessionResource PduSessionType sessionType = PduSessionType::UNSTRUCTURED; GtpTunnel upTunnel{}; GtpTunnel downTunnel{}; - asn::Unique qosFlows; + asn::Unique qosFlows{}; PduSessionResource(const int ueId, const int psi) : ueId(ueId), psi(psi) { @@ -218,7 +251,7 @@ struct PduSessionResource struct GnbStatusInfo { - bool isNgapUp; + bool isNgapUp{}; }; struct GtpUeContext @@ -233,9 +266,9 @@ struct GtpUeContext struct GtpUeContextUpdate { - bool isCreate; - int ueId; - AggregateMaximumBitRate ueAmbr; + bool isCreate{}; + int ueId{}; + AggregateMaximumBitRate ueAmbr{}; GtpUeContextUpdate(bool isCreate, int ueId, const AggregateMaximumBitRate &ueAmbr) : isCreate(isCreate), ueId(ueId), ueAmbr(ueAmbr) @@ -245,27 +278,27 @@ struct GtpUeContextUpdate struct GnbAmfConfig { - std::string address; - uint16_t port; + std::string address{}; + uint16_t port{}; }; struct GnbConfig { /* Read from config file */ - int64_t nci; // 36-bit - int gnbIdLength; // 22..32 bit - Plmn plmn; - int tac; - std::vector nssais; - std::vector amfConfigs; - std::string portalIp; - std::string ngapIp; - std::string gtpIp; - bool ignoreStreamIds; + int64_t nci{}; // 36-bit + int gnbIdLength{}; // 22..32 bit + Plmn plmn{}; + int tac{}; + std::vector nssais{}; + std::vector amfConfigs{}; + std::string portalIp{}; + std::string ngapIp{}; + std::string gtpIp{}; + bool ignoreStreamIds{}; /* Assigned by program */ - std::string name; - EPagingDrx pagingDrx; + std::string name{}; + EPagingDrx pagingDrx{}; [[nodiscard]] inline uint32_t getGnbId() const { @@ -280,22 +313,23 @@ struct GnbConfig struct TaskBase { - GnbConfig *config; - LogBase *logBase; - app::INodeListener *nodeListener; - - GnbAppTask *appTask; - GtpTask *gtpTask; - GnbMrTask *mrTask; - NgapTask *ngapTask; - GnbRrcTask *rrcTask; - SctpTask *sctpTask; + GnbConfig *config{}; + LogBase *logBase{}; + app::INodeListener *nodeListener{}; + NtsTask *cliCallbackTask{}; + + GnbAppTask *appTask{}; + GtpTask *gtpTask{}; + GnbMrTask *mrTask{}; + NgapTask *ngapTask{}; + GnbRrcTask *rrcTask{}; + SctpTask *sctpTask{}; }; struct MrUeContext { - int ueId; - std::string name; + int ueId{}; + std::string name{}; }; Json ToJson(const GnbStatusInfo &v); diff --git a/src/nas/ie3.hpp b/src/nas/ie3.hpp index 67461e018..19885d2a6 100644 --- a/src/nas/ie3.hpp +++ b/src/nas/ie3.hpp @@ -65,7 +65,7 @@ struct IEEpsNasSecurityAlgorithms : InformationElement3 struct IEGprsTimer : InformationElement3 { - int timerValue : 5; + int timerValue; // 5-bit EGprsTimerValueUnit timerValueUnit; IEGprsTimer(); @@ -91,7 +91,7 @@ struct IEIntegrityProtectionMaximumDataRate : InformationElement3 struct IEMaximumNumberOfSupportedPacketFilters : InformationElement3 { - int value : 11; + int value; // 11-bit IEMaximumNumberOfSupportedPacketFilters(); explicit IEMaximumNumberOfSupportedPacketFilters(int value); diff --git a/src/nas/ie4.hpp b/src/nas/ie4.hpp index 7fe92c094..b191f3ed9 100644 --- a/src/nas/ie4.hpp +++ b/src/nas/ie4.hpp @@ -152,7 +152,7 @@ struct IES1UeNetworkCapability : InformationElement4 struct IEGprsTimer3 : InformationElement4 { - int timerValue : 5; + int timerValue; // 5-bit EGprsTimerValueUnit3 unit; IEGprsTimer3(); diff --git a/src/nas/timer.cpp b/src/nas/timer.cpp index f0f8dba40..edd6bd427 100644 --- a/src/nas/timer.cpp +++ b/src/nas/timer.cpp @@ -15,7 +15,7 @@ namespace nas NasTimer::NasTimer(int timerCode, bool isMmTimer, int defaultInterval) : timerCode(timerCode), mmTimer(isMmTimer), interval(defaultInterval), startMillis(0), running(false), - _lastDebugPrintMs(0) + expiryCount(0), _lastDebugPrintMs(0) { } @@ -34,21 +34,28 @@ bool NasTimer::isMmTimer() const return mmTimer; } -void NasTimer::start() +void NasTimer::start(bool clearExpiryCount) { + if (clearExpiryCount) + resetExpiryCount(); startMillis = utils::CurrentTimeMillis(); running = true; } -void NasTimer::start(const nas::IEGprsTimer2 &v) +void NasTimer::start(const nas::IEGprsTimer2 &v, bool clearExpiryCount) { + if (clearExpiryCount) + resetExpiryCount(); interval = v.value; startMillis = utils::CurrentTimeMillis(); running = true; } -void NasTimer::start(const nas::IEGprsTimer3 &v) +void NasTimer::start(const nas::IEGprsTimer3 &v, bool clearExpiryCount) { + if (clearExpiryCount) + resetExpiryCount(); + int secs = 0; int val = v.timerValue; @@ -72,8 +79,11 @@ void NasTimer::start(const nas::IEGprsTimer3 &v) running = true; } -void NasTimer::stop() +void NasTimer::stop(bool clearExpiryCount) { + if (clearExpiryCount) + resetExpiryCount(); + if (running) { startMillis = utils::CurrentTimeMillis(); @@ -97,7 +107,8 @@ bool NasTimer::performTick() if (remainingSec < 0) { - stop(); + stop(false); + expiryCount++; return true; } } @@ -118,6 +129,16 @@ int NasTimer::getRemaining() const return static_cast(std::max(interval - elapsed, 0L)); } +void NasTimer::resetExpiryCount() +{ + expiryCount = 0; +} + +int NasTimer::getExpiryCount() const +{ + return expiryCount; +} + Json ToJson(const NasTimer &v) { int interval = v.getInterval(); diff --git a/src/nas/timer.hpp b/src/nas/timer.hpp index 3610f0be7..9bc5c598d 100644 --- a/src/nas/timer.hpp +++ b/src/nas/timer.hpp @@ -24,22 +24,26 @@ class NasTimer int interval; long startMillis; bool running; + int expiryCount; + long _lastDebugPrintMs; public: NasTimer(int timerCode, bool isMmTimer, int defaultInterval); public: - void start(); - void start(const IEGprsTimer2 &v); - void start(const IEGprsTimer3 &v); - void stop(); + void start(bool clearExpiryCount = true); + void start(const IEGprsTimer2 &v, bool clearExpiryCount = true); + void start(const IEGprsTimer3 &v, bool clearExpiryCount = true); + void stop(bool clearExpiryCount = true); + void resetExpiryCount(); bool performTick(); [[nodiscard]] bool isRunning() const; [[nodiscard]] int getCode() const; [[nodiscard]] bool isMmTimer() const; [[nodiscard]] int getInterval() const; [[nodiscard]] int getRemaining() const; + [[nodiscard]] int getExpiryCount() const; }; Json ToJson(const NasTimer &v); diff --git a/src/nas/values.hpp b/src/nas/values.hpp index 3d38f0b81..0cb3a73ed 100644 --- a/src/nas/values.hpp +++ b/src/nas/values.hpp @@ -20,7 +20,7 @@ namespace nas struct VAmfSetId { - int value : 10; + int value; // 10-bit explicit VAmfSetId(int value); @@ -53,9 +53,9 @@ struct VQoSFlowParameter struct VQoSFlowDescription { - int qfi : 6; + int qfi; // 6-bit EQoSOperationCode opCode; - int numOfParameters : 6; + int numOfParameters; // 6-bit bool eBit; std::vector> parameterList; @@ -216,7 +216,7 @@ struct VPartialTrackingAreaIdentityList struct VPduSessionReactivationResultErrorCause { - int pduSessionId : 4; + int pduSessionId; EMmCause causeValue; VPduSessionReactivationResultErrorCause(int pduSessionId, EMmCause causeValue); diff --git a/src/ue.cpp b/src/ue.cpp index 70d71eca9..f0ffcdea5 100644 --- a/src/ue.cpp +++ b/src/ue.cpp @@ -10,18 +10,20 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include static app::CliServer *g_cliServer = nullptr; -nr::ue::UeConfig *g_refConfig = nullptr; -static std::unordered_map g_ueMap{}; +static nr::ue::UeConfig *g_refConfig = nullptr; +static ConcurrentMap g_ueMap{}; static app::CliResponseTask *g_cliRespTask = nullptr; static struct Options @@ -33,6 +35,65 @@ static struct Options int count{}; } g_options{}; +struct NwUeControllerCmd : NtsMessage +{ + enum PR + { + PERFORM_SWITCH_OFF, + } present; + + // PERFORM_SWITCH_OFF + nr::ue::UserEquipment *ue{}; + + explicit NwUeControllerCmd(PR present) : NtsMessage(NtsMessageType::UE_CTL_COMMAND), present(present) + { + } +}; + +class UeControllerTask : public NtsTask +{ + protected: + void onStart() override + { + } + + void onLoop() override + { + auto *msg = take(); + if (msg == nullptr) + return; + if (msg->msgType == NtsMessageType::UE_CTL_COMMAND) + { + auto *w = dynamic_cast(msg); + switch (w->present) + { + case NwUeControllerCmd::PERFORM_SWITCH_OFF: { + std::string key{}; + g_ueMap.invokeForeach([&key, &w](auto &item) { + if (item.second == w->ue) + key = item.first; + }); + + if (key.empty()) + return; + + if (g_ueMap.removeAndGetSize(key) == 0) + exit(0); + + delete w->ue; + break; + } + } + } + } + + void onQuit() override + { + } +}; + +static UeControllerTask *g_controllerTask; + static nr::ue::UeConfig *ReadConfigYaml() { auto *result = new nr::ue::UeConfig(); @@ -59,7 +120,7 @@ static nr::ue::UeConfig *ReadConfigYaml() result->opC = OctetString::FromHex(yaml::GetString(config, "op", 32, 32)); result->amf = OctetString::FromHex(yaml::GetString(config, "amf", 4, 4)); - result->emulationMode = true; + result->autoBehaviour = true; result->configureRouting = !g_options.noRoutingConfigs; // If we have multiple UEs in the same process, then log names should be separated. @@ -132,7 +193,7 @@ static void ReadOptions(int argc, char **argv) { opt::OptionsDescription desc{cons::Project, cons::Tag, "5G-SA UE implementation", cons::Owner, "nr-ue", {"-c [option...]"}, - true}; + true, false}; opt::OptionItem itemConfigFile = {'c', "config", "Use specified configuration file for UE", "config-file"}; opt::OptionItem itemImsi = {'i', "imsi", "Use specified IMSI number instead of provided one", "imsi"}; @@ -214,7 +275,7 @@ static void IncrementNumber(std::string &s, int delta) static nr::ue::UeConfig *GetConfigByUe(int ueIndex) { auto *c = new nr::ue::UeConfig(); - c->emulationMode = g_refConfig->emulationMode; + c->autoBehaviour = g_refConfig->autoBehaviour; c->key = g_refConfig->key.copy(); c->opC = g_refConfig->opC.copy(); c->opType = g_refConfig->opType; @@ -281,14 +342,14 @@ static void ReceiveCommand(app::CliMessage &msg) return; } - if (g_ueMap.count(msg.nodeName) == 0) + auto *ue = g_ueMap.getOrDefault(msg.nodeName); + if (ue == nullptr) { g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Node not found: " + msg.nodeName)); return; } - auto *ue = g_ueMap[msg.nodeName]; - ue->pushCommand(std::move(cmd), msg.clientAddr, g_cliRespTask); + ue->pushCommand(std::move(cmd), msg.clientAddr); } static void Loop() @@ -324,6 +385,17 @@ static void Loop() ReceiveCommand(msg); } +static class UeController : public app::IUeController +{ + public: + void performSwitchOff(nr::ue::UserEquipment *ue) override + { + auto *w = new NwUeControllerCmd(NwUeControllerCmd::PERFORM_SWITCH_OFF); + w->ue = ue; + g_controllerTask->push(w); + } +} g_ueController; + int main(int argc, char **argv) { app::Initialize(); @@ -343,24 +415,29 @@ int main(int argc, char **argv) std::cout << cons::Name << std::endl; + g_controllerTask = new UeControllerTask(); + g_controllerTask->start(); + + if (!g_options.disableCmd) + { + g_cliServer = new app::CliServer{}; + g_cliRespTask = new app::CliResponseTask(g_cliServer); + } + for (int i = 0; i < g_options.count; i++) { auto *config = GetConfigByUe(i); - auto *ue = new nr::ue::UserEquipment(config, nullptr); - g_ueMap[config->getNodeName()] = ue; + auto *ue = new nr::ue::UserEquipment(config, &g_ueController, nullptr, g_cliRespTask); + g_ueMap.put(config->getNodeName(), ue); } if (!g_options.disableCmd) { - g_cliServer = new app::CliServer{}; app::CreateProcTable(g_ueMap, g_cliServer->assignedAddress().getPort()); - - g_cliRespTask = new app::CliResponseTask(g_cliServer); g_cliRespTask->start(); } - for (auto &ue : g_ueMap) - ue.second->start(); + g_ueMap.invokeForeach([](const auto &ue) { ue.second->start(); }); while (true) Loop(); diff --git a/src/ue/app/cmd_handler.cpp b/src/ue/app/cmd_handler.cpp index 7c9a658ca..00c059b84 100644 --- a/src/ue/app/cmd_handler.cpp +++ b/src/ue/app/cmd_handler.cpp @@ -22,34 +22,44 @@ namespace nr::ue { -void UeCmdHandler::PauseTasks(TaskBase &base) +void UeCmdHandler::sendResult(const InetAddress &address, const std::string &output) { - base.mrTask->requestPause(); - base.nasTask->requestPause(); - base.rrcTask->requestPause(); + m_base->cliCallbackTask->push(new app::NwCliSendResponse(address, output, false)); } -void UeCmdHandler::UnpauseTasks(TaskBase &base) +void UeCmdHandler::sendError(const InetAddress &address, const std::string &output) { - base.mrTask->requestUnpause(); - base.nasTask->requestUnpause(); - base.rrcTask->requestUnpause(); + m_base->cliCallbackTask->push(new app::NwCliSendResponse(address, output, true)); } -bool UeCmdHandler::IsAllPaused(TaskBase &base) +void UeCmdHandler::pauseTasks() { - if (!base.mrTask->isPauseConfirmed()) + m_base->mrTask->requestPause(); + m_base->nasTask->requestPause(); + m_base->rrcTask->requestPause(); +} + +void UeCmdHandler::unpauseTasks() +{ + m_base->mrTask->requestUnpause(); + m_base->nasTask->requestUnpause(); + m_base->rrcTask->requestUnpause(); +} + +bool UeCmdHandler::isAllPaused() +{ + if (!m_base->mrTask->isPauseConfirmed()) return false; - if (!base.nasTask->isPauseConfirmed()) + if (!m_base->nasTask->isPauseConfirmed()) return false; - if (!base.rrcTask->isPauseConfirmed()) + if (!m_base->rrcTask->isPauseConfirmed()) return false; return true; } -void UeCmdHandler::HandleCmd(TaskBase &base, NwUeCliCommand &msg) +void UeCmdHandler::handleCmd(NwUeCliCommand &msg) { - PauseTasks(base); + pauseTasks(); uint64_t currentTime = utils::CurrentTimeMillis(); uint64_t endTime = currentTime + PAUSE_CONFIRM_TIMEOUT; @@ -58,7 +68,7 @@ void UeCmdHandler::HandleCmd(TaskBase &base, NwUeCliCommand &msg) while (currentTime < endTime) { currentTime = utils::CurrentTimeMillis(); - if (IsAllPaused(base)) + if (isAllPaused()) { isPaused = true; break; @@ -68,24 +78,24 @@ void UeCmdHandler::HandleCmd(TaskBase &base, NwUeCliCommand &msg) if (!isPaused) { - msg.sendError("UE is unable process command due to pausing timeout"); + sendError(msg.address, "UE is unable process command due to pausing timeout"); } else { - HandleCmdImpl(base, msg); + handleCmdImpl(msg); } - UnpauseTasks(base); + unpauseTasks(); } -void UeCmdHandler::HandleCmdImpl(TaskBase &base, NwUeCliCommand &msg) +void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg) { switch (msg.cmd->present) { case app::UeCliCommand::STATUS: { std::vector pduSessions{}; int index = 0; - for (auto &pduSession : base.appTask->m_statusInfo.pduSessions) + for (auto &pduSession : m_base->appTask->m_pduSessions) { if (pduSession.has_value()) { @@ -96,23 +106,33 @@ void UeCmdHandler::HandleCmdImpl(TaskBase &base, NwUeCliCommand &msg) } Json json = Json::Obj({ - {"cm-state", ToJson(base.nasTask->mm->m_cmState)}, - {"rm-state", ToJson(base.nasTask->mm->m_rmState)}, - {"mm-state", ToJson(base.nasTask->mm->m_mmSubState)}, - {"sim-inserted", base.nasTask->mm->m_validSim}, - {"stored-suci", ToJson(base.nasTask->mm->m_storedSuci)}, - {"stored-guti", ToJson(base.nasTask->mm->m_storedGuti)}, + {"cm-state", ToJson(m_base->nasTask->mm->m_cmState)}, + {"rm-state", ToJson(m_base->nasTask->mm->m_rmState)}, + {"mm-state", ToJson(m_base->nasTask->mm->m_mmSubState)}, + {"sim-inserted", m_base->nasTask->mm->m_validSim}, + {"stored-suci", ToJson(m_base->nasTask->mm->m_storedSuci)}, + {"stored-guti", ToJson(m_base->nasTask->mm->m_storedGuti)}, {"pdu-sessions", Json::Arr(std::move(pduSessions))}, }); - msg.sendResult(json.dumpYaml()); + sendResult(msg.address, json.dumpYaml()); break; } case app::UeCliCommand::INFO: { - msg.sendResult(ToJson(*base.config).dumpYaml()); + sendResult(msg.address, ToJson(*m_base->config).dumpYaml()); break; } case app::UeCliCommand::TIMERS: { - msg.sendResult(ToJson(base.nasTask->timers).dumpYaml()); + sendResult(msg.address, ToJson(m_base->nasTask->timers).dumpYaml()); + break; + } + case app::UeCliCommand::DE_REGISTER: { + m_base->nasTask->mm->sendDeregistration(msg.cmd->isSwitchOff ? nas::ESwitchOff::SWITCH_OFF + : nas::ESwitchOff::NORMAL_DE_REGISTRATION, + msg.cmd->dueToDisable5g); + if (!msg.cmd->isSwitchOff) + sendResult(msg.address, "De-registration procedure triggered"); + else + sendResult(msg.address, "De-registration procedure triggered. UE device will be switched off."); break; } } diff --git a/src/ue/app/cmd_handler.hpp b/src/ue/app/cmd_handler.hpp index 8641d88e3..9a107f5a8 100644 --- a/src/ue/app/cmd_handler.hpp +++ b/src/ue/app/cmd_handler.hpp @@ -17,15 +17,26 @@ namespace nr::ue class UeCmdHandler { private: - static void PauseTasks(TaskBase &base); - static void UnpauseTasks(TaskBase &base); - static bool IsAllPaused(TaskBase &base); + TaskBase *m_base; public: - static void HandleCmd(TaskBase &base, NwUeCliCommand &msg); + explicit UeCmdHandler(TaskBase *base) : m_base(base) + { + } + + void handleCmd(NwUeCliCommand &msg); + + private: + void pauseTasks(); + void unpauseTasks(); + bool isAllPaused(); + + private: + void handleCmdImpl(NwUeCliCommand &msg); private: - static void HandleCmdImpl(TaskBase &base, NwUeCliCommand &msg); + void sendResult(const InetAddress &address, const std::string &output); + void sendError(const InetAddress &address, const std::string &output); }; } // namespace nr::ue diff --git a/src/ue/app/task.cpp b/src/ue/app/task.cpp index 40edf0bb8..15b64c4bf 100644 --- a/src/ue/app/task.cpp +++ b/src/ue/app/task.cpp @@ -14,10 +14,13 @@ #include #include +static constexpr const int SWITCH_OFF_TIMER_ID = 1; +static constexpr const int SWITCH_OFF_DELAY = 500; + namespace nr::ue { -UeAppTask::UeAppTask(TaskBase *base) : m_base{base}, m_statusInfo{}, m_tunTasks{} +UeAppTask::UeAppTask(TaskBase *base) : m_base{base} { m_logger = m_base->logBase->makeUniqueLogger(m_base->config->getLoggerPrefix() + "app"); } @@ -83,13 +86,34 @@ void UeAppTask::onLoop() } break; } + case NtsMessageType::UE_NAS_TO_APP: { + auto *w = dynamic_cast(msg); + switch (w->present) + { + case NwUeNasToApp::PERFORM_SWITCH_OFF: { + setTimer(SWITCH_OFF_TIMER_ID, SWITCH_OFF_DELAY); + break; + } + } + break; + } case NtsMessageType::UE_STATUS_UPDATE: { receiveStatusUpdate(*dynamic_cast(msg)); break; } case NtsMessageType::UE_CLI_COMMAND: { auto *w = dynamic_cast(msg); - UeCmdHandler::HandleCmd(*m_base, *w); + UeCmdHandler handler{m_base}; + handler.handleCmd(*w); + break; + } + case NtsMessageType::TIMER_EXPIRED: { + auto *w = dynamic_cast(msg); + if (w->timerId == SWITCH_OFF_TIMER_ID) + { + m_logger->info("UE device is switching off"); + m_base->ueController->performSwitchOff(m_base->ue); + } break; } default: @@ -105,14 +129,32 @@ void UeAppTask::receiveStatusUpdate(NwUeStatusUpdate &msg) { auto *session = msg.pduSession; - UeStatusInfo::UePduSessionInfo sessionInfo{}; + UePduSessionInfo sessionInfo{}; sessionInfo.type = nas::utils::EnumToString(session->sessionType); if (session->pduAddress.has_value()) sessionInfo.address = utils::OctetStringToIp(session->pduAddress->pduAddressInformation); - m_statusInfo.pduSessions[session->id] = std::move(sessionInfo); + m_pduSessions[session->id] = std::move(sessionInfo); setupTunInterface(session); + return; + } + + if (msg.what == NwUeStatusUpdate::SESSION_RELEASE) + { + if (m_tunTasks[msg.psi] != nullptr) + { + m_tunTasks[msg.psi]->quit(); + delete m_tunTasks[msg.psi]; + m_tunTasks[msg.psi] = nullptr; + } + + if (m_pduSessions[msg.psi].has_value()) + { + m_logger->info("PDU session[%d] released", msg.psi); + m_pduSessions[msg.psi] = {}; + } + return; } } diff --git a/src/ue/app/task.hpp b/src/ue/app/task.hpp index 6fca107d0..75a528b21 100644 --- a/src/ue/app/task.hpp +++ b/src/ue/app/task.hpp @@ -27,8 +27,8 @@ class UeAppTask : public NtsTask TaskBase *m_base; std::unique_ptr m_logger; - UeStatusInfo m_statusInfo; - TunTask *m_tunTasks[16]; + std::array, 16> m_pduSessions{}; + std::array m_tunTasks{}; friend class UeCmdHandler; diff --git a/src/ue/mm/base.cpp b/src/ue/mm/base.cpp index 453d826ff..fa5e59cd3 100644 --- a/src/ue/mm/base.cpp +++ b/src/ue/mm/base.cpp @@ -10,13 +10,14 @@ #include #include +#include #include #include namespace nr::ue { -NasMm::NasMm(TaskBase *base, NtsTask *nas, UeTimers *timers) : m_base{base}, m_nas{nas}, m_timers{timers}, m_sm{nullptr} +NasMm::NasMm(TaskBase *base, UeTimers *timers) : m_base{base}, m_timers{timers}, m_sm{nullptr} { m_logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "nas"); @@ -24,7 +25,8 @@ NasMm::NasMm(TaskBase *base, NtsTask *nas, UeTimers *timers) : m_base{base}, m_n m_cmState = ECmState::CM_IDLE; m_mmState = EMmState::MM_DEREGISTERED; m_mmSubState = EMmSubState::MM_DEREGISTERED_NA; - m_emulationMode = base->config->emulationMode; + m_uState = E5UState::U1_UPDATED; + m_autoBehaviour = base->config->autoBehaviour; m_validSim = base->config->supi.has_value(); } @@ -40,7 +42,7 @@ void NasMm::onQuit() void NasMm::triggerMmCycle() { - m_nas->push(new NwUeNasToNas(NwUeNasToNas::PERFORM_MM_CYCLE)); + m_base->nasTask->push(new NwUeNasToNas(NwUeNasToNas::PERFORM_MM_CYCLE)); } void NasMm::performMmCycle() @@ -79,7 +81,7 @@ void NasMm::performMmCycle() if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE) { - if (m_emulationMode && !m_timers->t3346.isRunning()) + if (m_autoBehaviour && !m_timers->t3346.isRunning()) sendRegistration(nas::ERegistrationType::INITIAL_REGISTRATION, nas::EFollowOnRequest::FOR_PENDING); return; } @@ -95,7 +97,7 @@ void NasMm::performMmCycle() if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NO_SUPI) return; - if (m_emulationMode) + if (m_autoBehaviour) { m_logger->err("unhandled UE MM state"); return; @@ -164,6 +166,25 @@ void NasMm::switchCmState(ECmState state) triggerMmCycle(); } +void NasMm::switchUState(E5UState state) +{ + E5UState oldState = m_uState; + m_uState = state; + + onSwitchUState(oldState, m_uState); + + if (m_base->nodeListener) + { + m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::U5, + ToJson(oldState).str(), ToJson(m_uState).str()); + } + + if (state != oldState) + m_logger->info("UE switches to state: %s", ToJson(state).str().c_str()); + + triggerMmCycle(); +} + void NasMm::onSwitchMmState(EMmState oldState, EMmState newState, EMmSubState oldSubState, EMmSubState newSubSate) { // The UE shall mark the 5G NAS security context on the USIM or in the non-volatile memory as invalid when the UE @@ -187,57 +208,95 @@ void NasMm::onSwitchRmState(ERmState oldState, ERmState newState) void NasMm::onSwitchCmState(ECmState oldState, ECmState newState) { + if (oldState == ECmState::CM_CONNECTED && newState == ECmState::CM_IDLE) + { + // 5.5.2.2.6 Abnormal cases in the UE (in de-registration) + if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) + { + // The de-registration procedure shall be aborted and the UE proceeds as follows: + // if the de-registration procedure was performed due to disabling of 5GS services, the UE shall enter the + // 5GMM-NULL state; + if (m_lastDeregDueToDisable5g) + switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + // if the de-registration type "normal de-registration" was requested for reasons other than disabling of + // 5GS services, the UE shall enter the 5GMM-DEREGISTERED state. + else if (m_lastDeregistrationRequest->deRegistrationType.switchOff == + nas::ESwitchOff::NORMAL_DE_REGISTRATION) + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + + m_lastDeregistrationRequest = nullptr; + m_lastDeregDueToDisable5g = false; + } + } } -void NasMm::receivePlmnSearchResponse(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); -} - -void NasMm::receivePlmnSearchFailure() -{ - 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::receiveRrcConnectionSetup() +void NasMm::onSwitchUState(E5UState oldState, E5UState newState) { - switchCmState(ECmState::CM_CONNECTED); } void NasMm::onTimerExpire(nas::NasTimer &timer) { switch (timer.getCode()) { + case 3346: { + if (m_autoBehaviour && m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE) + { + sendRegistration(nas::ERegistrationType::INITIAL_REGISTRATION, nas::EFollowOnRequest::FOR_PENDING); + } + break; + } case 3512: { - if (m_emulationMode && m_mmState == EMmState::MM_REGISTERED) + if (m_autoBehaviour && m_mmState == EMmState::MM_REGISTERED && m_cmState == ECmState::CM_CONNECTED) { sendRegistration(nas::ERegistrationType::PERIODIC_REGISTRATION_UPDATING, nas::EFollowOnRequest::FOR_PENDING); } break; } - case 3346: { - if (m_emulationMode && m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE) + case 3521: { + if (timer.getExpiryCount() == 5) { - sendRegistration(nas::ERegistrationType::INITIAL_REGISTRATION, nas::EFollowOnRequest::FOR_PENDING); + timer.resetExpiryCount(); + if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED && m_lastDeregistrationRequest != nullptr) + { + m_logger->debug("De-registration aborted"); + + if (m_lastDeregDueToDisable5g) + switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + else if (m_lastDeregistrationRequest->deRegistrationType.switchOff == + nas::ESwitchOff::NORMAL_DE_REGISTRATION) + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + } + } + else + { + if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED && m_lastDeregistrationRequest != nullptr) + { + m_logger->debug("Retrying de-registration request"); + + sendNasMessage(*m_lastDeregistrationRequest); + m_timers->t3521.start(false); + } } break; } } } +void NasMm::invalidateAcquiredParams() +{ + m_storedGuti = {}; + m_lastVisitedRegisteredTai = {}; + m_taiList = {}; + m_currentNsCtx = {}; + m_nonCurrentNsCtx = {}; +} + +void NasMm::invalidateSim() +{ + m_logger->warn("USIM is removed or invalidated"); + m_validSim = false; + invalidateAcquiredParams(); +} + } // namespace nr::ue diff --git a/src/ue/mm/dereg.cpp b/src/ue/mm/dereg.cpp index 099457f9b..29cb39d19 100644 --- a/src/ue/mm/dereg.cpp +++ b/src/ue/mm/dereg.cpp @@ -7,57 +7,139 @@ // #include "mm.hpp" +#include +#include +#include namespace nr::ue { -void NasMm::sendDeregistration(nas::ESwitchOff switchOff) +void NasMm::sendDeregistration(nas::ESwitchOff switchOff, bool dueToDisable5g) { - switchMmState(EMmState::MM_DEREGISTERED_INITIATED, EMmSubState::MM_DEREGISTERED_INITIATED_NA); + if (m_rmState != ERmState::RM_REGISTERED) + { + m_logger->warn("De-registration could not triggered. UE is already de-registered"); + return; + } - nas::DeRegistrationRequestUeOriginating request; - request.deRegistrationType.accessType = nas::EDeRegistrationAccessType::THREEGPP_ACCESS; - request.deRegistrationType.reRegistrationRequired = nas::EReRegistrationRequired::NOT_REQUIRED; - request.deRegistrationType.switchOff = switchOff; + m_logger->debug("Starting de-registration procedure. switch-off[%d] disable-5g[%d]", + switchOff == nas::ESwitchOff::SWITCH_OFF ? 1 : 0, (int)dueToDisable5g); + + auto request = std::make_unique(); + request->deRegistrationType.accessType = nas::EDeRegistrationAccessType::THREEGPP_ACCESS; + request->deRegistrationType.reRegistrationRequired = nas::EReRegistrationRequired::NOT_REQUIRED; + request->deRegistrationType.switchOff = switchOff; if (m_currentNsCtx.has_value()) { - request.ngKSI.tsc = m_currentNsCtx->tsc; - request.ngKSI.ksi = m_currentNsCtx->ngKsi; + request->ngKSI.tsc = m_currentNsCtx->tsc; + request->ngKSI.ksi = m_currentNsCtx->ngKsi; } - - if (m_storedGuti.type != nas::EIdentityType::NO_IDENTITY) - request.mobileIdentity = m_storedGuti; else - request.mobileIdentity = getOrGenerateSuci(); + { + request->ngKSI.ksi = nas::IENasKeySetIdentifier::NOT_AVAILABLE_OR_RESERVED; + request->ngKSI.tsc = nas::ETypeOfSecurityContext::NATIVE_SECURITY_CONTEXT; + } - sendNasMessage(request); + request->mobileIdentity = getOrGeneratePreferredId(); + + sendNasMessage(*request); + m_lastDeregistrationRequest = std::move(request); + m_lastDeregDueToDisable5g = dueToDisable5g; + m_timers->t3521.resetExpiryCount(); if (switchOff != nas::ESwitchOff::SWITCH_OFF) { - if (m_mmState == EMmState::MM_REGISTERED || m_mmState == EMmState::MM_DEREGISTERED_INITIATED) + if (m_mmState == EMmState::MM_REGISTERED || m_mmState == EMmState::MM_REGISTERED_INITIATED) m_timers->t3521.start(); } + + switchMmState(EMmState::MM_DEREGISTERED_INITIATED, EMmSubState::MM_DEREGISTERED_INITIATED_NA); + + m_sm->localReleaseAllSessions(); + + if (switchOff == nas::ESwitchOff::SWITCH_OFF) + m_base->appTask->push(new NwUeNasToApp(NwUeNasToApp::PERFORM_SWITCH_OFF)); } void NasMm::receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOriginating &msg) { + m_logger->debug("De-registration accept received"); + + if (m_mmSubState != EMmSubState::MM_DEREGISTERED_INITIATED_NA) + { + m_logger->warn("De-registration accept message ignored. UE is not in MM_DEREGISTERED_INITIATED"); + sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE); + return; + } + m_timers->t3521.stop(); m_timers->t3519.stop(); m_storedSuci = {}; - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); switchRmState(ERmState::RM_DEREGISTERED); + if (m_lastDeregDueToDisable5g) + switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + else + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + + m_lastDeregistrationRequest = nullptr; + m_lastDeregDueToDisable5g = false; + m_logger->info("De-registration is successful"); } void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTerminated &msg) { - // TODO: this function is not complete + if (m_rmState != ERmState::RM_REGISTERED) + { + m_logger->warn("De-registration message ignored. UE is already de-registered"); + return; + } + + if (msg.deRegistrationType.accessType == nas::EDeRegistrationAccessType::NON_THREEGPP_ACCESS) + { + m_logger->warn("De-registration message ignored. Access type mismatch"); + sendMmStatus(nas::EMmCause::SEMANTICALLY_INCORRECT_MESSAGE); + return; + } + + m_logger->debug("Network initiated de-registration request received"); + + bool forceIgnoreReregistration = false; - if (msg.deRegistrationType.reRegistrationRequired == nas::EReRegistrationRequired::REQUIRED) + // 5.5.2.2.6 Abnormal cases in the UE (de-registration collision) + if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED) + { + // De-registration containing de-registration type "switch off", If the UE receives a DEREGISTRATION REQUEST + // message before the UE-initiated de-registration procedure has been completed, this message shall be ignored + // and the UE-initiated de-registration procedure shall continue. + if (m_lastDeregistrationRequest->deRegistrationType.switchOff == nas::ESwitchOff::SWITCH_OFF) + { + m_logger->debug("Network-initiated de-registration request is ignored due to abnormal cases"); + return; + } + + // If the DEREGISTRATION REQUEST message received by the UE contains de-registration type "re-registration + // required", and the UE-initiated de-registration procedure is with de-registration type "normal + // de-registration", the UE need not initiate the registration procedure for initial registration. + if (msg.deRegistrationType.reRegistrationRequired == nas::EReRegistrationRequired::REQUIRED) + { + m_logger->debug( + "Network-initiated de-registration request received but re-registration-required is ignored"); + forceIgnoreReregistration = true; + } + } + + bool reRegistrationRequired = + msg.deRegistrationType.reRegistrationRequired == nas::EReRegistrationRequired::REQUIRED && + !forceIgnoreReregistration; + + m_sm->localReleaseAllSessions(); + + if (reRegistrationRequired) { m_timers->t3346.stop(); m_timers->t3396.stop(); @@ -66,8 +148,63 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi } sendNasMessage(nas::DeRegistrationAcceptUeTerminated{}); - switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); switchRmState(ERmState::RM_DEREGISTERED); + + // If the de-registration type indicates "re-registration not required", the UE shall take the actions depending on + // the received 5GMM cause value. Otherwise ignore the 5GMM cause value. + if (msg.deRegistrationType.reRegistrationRequired == nas::EReRegistrationRequired::NOT_REQUIRED) + { + if (msg.mmCause.has_value()) + { + switch (msg.mmCause->value) + { + case nas::EMmCause::ILLEGAL_UE: + case nas::EMmCause::ILLEGAL_ME: + case nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED: { + switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); + invalidateSim(); + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + break; + } + // case nas::EMmCause::PLMN_NOT_ALLOWED: { + // switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); + // invalidateAcquiredParams(); + // // TODO: add to forbidden plmn list, otherwise endless plmn search may occur. + // switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH); + // break; + //} + case nas::EMmCause::TA_NOT_ALLOWED: { + switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); + invalidateAcquiredParams(); + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE); + break; + } + case nas::EMmCause::N1_MODE_NOT_ALLOWED: { + switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); + invalidateAcquiredParams(); + switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA); + break; + } + case nas::EMmCause::CONGESTION: { + switchUState(E5UState::U2_NOT_UPDATED); + m_timers->t3346.stop(); + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION); + if (msg.t3346Value.has_value() && msg.t3346Value->value != 0) + m_timers->t3346.start(*msg.t3346Value); + break; + } + default: { + m_logger->err("Unhandled network-initiated de-registration cause[%s]", + nas::utils::EnumToString(msg.mmCause->value)); + + switchUState(E5UState::U3_ROAMING_NOT_ALLOWED); + invalidateSim(); + switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA); + break; + } + } + } + } } } // namespace nr::ue diff --git a/src/ue/mm/identity.cpp b/src/ue/mm/identity.cpp index f97851f84..402fd7230 100644 --- a/src/ue/mm/identity.cpp +++ b/src/ue/mm/identity.cpp @@ -91,4 +91,38 @@ nas::IE5gsMobileIdentity NasMm::generateSuci() return ret; } +nas::IE5gsMobileIdentity NasMm::getOrGeneratePreferredId() +{ + if (m_storedGuti.type != nas::EIdentityType::NO_IDENTITY) + return m_storedGuti; + else + { + auto suci = getOrGenerateSuci(); + if (suci.type != nas::EIdentityType::NO_IDENTITY) + { + return suci; + } + else if (m_base->config->imei.has_value()) + { + nas::IE5gsMobileIdentity res{}; + res.type = nas::EIdentityType::IMEI; + res.value = *m_base->config->imei; + return res; + } + else if (m_base->config->imeiSv.has_value()) + { + nas::IE5gsMobileIdentity res{}; + res.type = nas::EIdentityType::IMEISV; + res.value = *m_base->config->imeiSv; + return res; + } + else + { + nas::IE5gsMobileIdentity res{}; + res.type = nas::EIdentityType::NO_IDENTITY; + return res; + } + } +} + } // namespace nr::ue diff --git a/src/ue/mm/mm.hpp b/src/ue/mm/mm.hpp index c01ef6e33..d90495794 100644 --- a/src/ue/mm/mm.hpp +++ b/src/ue/mm/mm.hpp @@ -25,7 +25,6 @@ class NasMm { private: TaskBase *m_base; - NtsTask *m_nas; UeTimers *m_timers; std::unique_ptr m_logger; NasSm *m_sm; @@ -34,17 +33,23 @@ class NasMm ECmState m_cmState; EMmState m_mmState; EMmSubState m_mmSubState; + E5UState m_uState; - std::unique_ptr m_lastRegistrationRequest{}; nas::IE5gsMobileIdentity m_storedSuci{}; nas::IE5gsMobileIdentity m_storedGuti{}; + + std::unique_ptr m_lastRegistrationRequest{}; + + std::unique_ptr m_lastDeregistrationRequest{}; + bool m_lastDeregDueToDisable5g{}; + std::optional m_lastVisitedRegisteredTai{}; std::optional m_taiList{}; std::optional m_currentNsCtx; std::optional m_nonCurrentNsCtx; - bool m_emulationMode; + bool m_autoBehaviour; bool m_validSim; long m_lastPlmnSearchTrigger{}; OctetString m_sqn{}; @@ -52,7 +57,7 @@ class NasMm friend class UeCmdHandler; public: - NasMm(TaskBase *base, NtsTask *nas, UeTimers *timers); + NasMm(TaskBase *base, UeTimers *timers); public: /* Base */ @@ -61,22 +66,33 @@ class NasMm void triggerMmCycle(); void performMmCycle(); void onTimerExpire(nas::NasTimer &timer); - void receivePlmnSearchResponse(const std::string &gnbName); - void receivePlmnSearchFailure(); - void receiveRrcConnectionSetup(); + + /* Radio resource control */ + void handlePlmnSearchResponse(const std::string &gnbName); + void handlePlmnSearchFailure(); + void handleRrcConnectionSetup(); + void handleRrcConnectionRelease(); + void handleRadioLinkFailure(); /* Transport */ void sendNasMessage(const nas::PlainMmMessage &msg); void receiveNasMessage(const nas::NasMessage &msg); + /* De-registration */ + void sendDeregistration(nas::ESwitchOff switchOff, bool dueToDisable5g); + private: /* Base */ 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); void onSwitchRmState(ERmState oldState, ERmState newState); void onSwitchCmState(ECmState oldState, ECmState newState); + void onSwitchUState(E5UState oldState, E5UState newState); + void invalidateAcquiredParams(); + void invalidateSim(); /* Transport */ void sendMmStatus(nas::EMmCause cause); @@ -112,7 +128,6 @@ class NasMm void receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &msg); /* De-registration */ - void sendDeregistration(nas::ESwitchOff switchOff); void receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOriginating &msg); void receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTerminated &msg); @@ -120,6 +135,7 @@ class NasMm void receiveIdentityRequest(const nas::IdentityRequest &msg); nas::IE5gsMobileIdentity getOrGenerateSuci(); nas::IE5gsMobileIdentity generateSuci(); + nas::IE5gsMobileIdentity getOrGeneratePreferredId(); /* Service */ void receiveServiceAccept(const nas::ServiceAccept &msg); diff --git a/src/ue/mm/radio.cpp b/src/ue/mm/radio.cpp new file mode 100644 index 000000000..dd8373a90 --- /dev/null +++ b/src/ue/mm/radio.cpp @@ -0,0 +1,57 @@ +// +// 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 + +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); +} + +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(); +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/mm/register.cpp b/src/ue/mm/register.cpp index aed902876..76680343f 100644 --- a/src/ue/mm/register.cpp +++ b/src/ue/mm/register.cpp @@ -9,6 +9,7 @@ #include "mm.hpp" #include +#include namespace nr::ue { @@ -49,34 +50,7 @@ void NasMm::sendRegistration(nas::ERegistrationType registrationType, nas::EFoll request->mmCapability->lpp = nas::ELtePositioningProtocolCapability::NOT_SUPPORTED; } - if (m_storedGuti.type != nas::EIdentityType::NO_IDENTITY) - { - request->mobileIdentity = m_storedGuti; - } - else - { - auto suci = getOrGenerateSuci(); - if (suci.type != nas::EIdentityType::NO_IDENTITY) - { - request->mobileIdentity = suci; - if (!m_timers->t3519.isRunning()) - m_timers->t3519.start(); - } - else if (m_base->config->imei.has_value()) - { - request->mobileIdentity.type = nas::EIdentityType::IMEI; - request->mobileIdentity.value = *m_base->config->imei; - } - else if (m_base->config->imeiSv.has_value()) - { - request->mobileIdentity.type = nas::EIdentityType::IMEISV; - request->mobileIdentity.value = *m_base->config->imeiSv; - } - else - { - request->mobileIdentity.type = nas::EIdentityType::NO_IDENTITY; - } - } + request->mobileIdentity = getOrGeneratePreferredId(); if (m_lastVisitedRegisteredTai.has_value()) request->lastVisitedRegisteredTai = m_lastVisitedRegisteredTai.value(); @@ -127,7 +101,7 @@ void NasMm::receiveRegistrationAccept(const nas::RegistrationAccept &msg) if (regType == nas::ERegistrationType::INITIAL_REGISTRATION || regType == nas::ERegistrationType::EMERGENCY_REGISTRATION) { - m_nas->push(new NwUeNasToNas(NwUeNasToNas::ESTABLISH_INITIAL_SESSIONS)); + m_base->nasTask->push(new NwUeNasToNas(NwUeNasToNas::ESTABLISH_INITIAL_SESSIONS)); } } diff --git a/src/ue/mr/task.cpp b/src/ue/mr/task.cpp index d71051564..11885491b 100644 --- a/src/ue/mr/task.cpp +++ b/src/ue/mr/task.cpp @@ -68,7 +68,15 @@ void UeMrTask::onLoop() break; } case NwUeMrToMr::RLS_RELEASED: { - m_logger->warn("UE disconnected from gNB, RLS released [%s]", rls::CauseToString(w->cause)); + 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: { @@ -113,6 +121,11 @@ void UeMrTask::onLoop() m_rlsEntity->onUplinkDelivery(rls::EPayloadType::RRC, std::move(stream)); break; } + case NwUeRrcToMr::RRC_CONNECTION_RELEASE: { + m_rlsEntity->localReleaseConnection(rls::ECause::RRC_RELEASE); + m_rlsEntity->resetEntity(); + break; + } } break; } diff --git a/src/ue/nas/task.cpp b/src/ue/nas/task.cpp index 539197edb..9453dad65 100644 --- a/src/ue/nas/task.cpp +++ b/src/ue/nas/task.cpp @@ -21,8 +21,8 @@ NasTask::NasTask(TaskBase *base) : base{base}, timers{} { logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "nas"); - mm = new NasMm(base, this, &timers); - sm = new NasSm(base, this, &timers); + mm = new NasMm(base, &timers); + sm = new NasSm(base, &timers); } void NasTask::onStart() @@ -60,15 +60,15 @@ void NasTask::onLoop() switch (w->present) { case NwUeRrcToNas::RRC_CONNECTION_SETUP: { - mm->receiveRrcConnectionSetup(); + mm->handleRrcConnectionSetup(); break; } case NwUeRrcToNas::PLMN_SEARCH_RESPONSE: { - mm->receivePlmnSearchResponse(w->gnbName); + mm->handlePlmnSearchResponse(w->gnbName); break; } case NwUeRrcToNas::PLMN_SEARCH_FAILURE: { - mm->receivePlmnSearchFailure(); + mm->handlePlmnSearchFailure(); break; } case NwUeRrcToNas::NAS_DELIVERY: { @@ -78,6 +78,14 @@ void NasTask::onLoop() mm->receiveNasMessage(*nasMessage); break; } + case NwUeRrcToNas::RRC_CONNECTION_RELEASE: { + mm->handleRrcConnectionRelease(); + break; + } + case NwUeRrcToNas::RADIO_LINK_FAILURE: { + mm->handleRadioLinkFailure(); + break; + } } break; } @@ -133,6 +141,8 @@ void NasTask::onTimerExpire(nas::NasTimer &timer) void NasTask::performTick() { auto sendExpireMsg = [this](nas::NasTimer *timer) { + logger->debug("NAS timer[%d] expired [%d]", timer->getCode(), timer->getExpiryCount()); + auto *nw = new NwUeNasToNas(NwUeNasToNas::NAS_TIMER_EXPIRE); nw->timer = timer; push(nw); diff --git a/src/ue/nts.hpp b/src/ue/nts.hpp index 9ff7a6d12..018557d7c 100644 --- a/src/ue/nts.hpp +++ b/src/ue/nts.hpp @@ -66,6 +66,7 @@ struct NwUeMrToRrc : NtsMessage PLMN_SEARCH_RESPONSE, PLMN_SEARCH_FAILURE, RRC_PDU_DELIVERY, + RADIO_LINK_FAILURE } present; // PLMN_SEARCH_RESPONSE @@ -156,6 +157,8 @@ struct NwUeRrcToNas : NtsMessage PLMN_SEARCH_RESPONSE, PLMN_SEARCH_FAILURE, RRC_CONNECTION_SETUP, + RRC_CONNECTION_RELEASE, + RADIO_LINK_FAILURE, } present; // NAS_DELIVERY @@ -195,7 +198,8 @@ struct NwUeRrcToMr : NtsMessage enum PR { PLMN_SEARCH_REQUEST, - RRC_PDU_DELIVERY + RRC_PDU_DELIVERY, + RRC_CONNECTION_RELEASE, } present; // RRC_PDU_DELIVERY @@ -224,15 +228,31 @@ struct NwUeNasToNas : NtsMessage } }; +struct NwUeNasToApp : NtsMessage +{ + enum PR + { + PERFORM_SWITCH_OFF, + } present; + + explicit NwUeNasToApp(PR present) : NtsMessage(NtsMessageType::UE_NAS_TO_APP), present(present) + { + } +}; + struct NwUeStatusUpdate : NtsMessage { - static constexpr const int SESSION_ESTABLISHMENT = 5; + static constexpr const int SESSION_ESTABLISHMENT = 1; + static constexpr const int SESSION_RELEASE = 2; const int what{}; // SESSION_ESTABLISHMENT PduSession *pduSession{}; + // SESSION_RELEASE + int psi{}; + explicit NwUeStatusUpdate(const int what) : NtsMessage(NtsMessageType::UE_STATUS_UPDATE), what(what) { } @@ -242,21 +262,10 @@ struct NwUeCliCommand : NtsMessage { std::unique_ptr cmd; InetAddress address; - NtsTask *callbackTask; - - NwUeCliCommand(std::unique_ptr cmd, InetAddress address, NtsTask *callbackTask) - : NtsMessage(NtsMessageType::UE_CLI_COMMAND), cmd(std::move(cmd)), address(address), callbackTask(callbackTask) - { - } - - void sendResult(const std::string &output) const - { - callbackTask->push(new app::NwCliSendResponse(address, output, false)); - } - void sendError(const std::string &output) const + NwUeCliCommand(std::unique_ptr cmd, InetAddress address) + : NtsMessage(NtsMessageType::UE_CLI_COMMAND), cmd(std::move(cmd)), address(address) { - callbackTask->push(new app::NwCliSendResponse(address, output, true)); } }; diff --git a/src/ue/rrc/channel.cpp b/src/ue/rrc/channel.cpp index 48cd6261c..de4c70733 100644 --- a/src/ue/rrc/channel.cpp +++ b/src/ue/rrc/channel.cpp @@ -255,13 +255,15 @@ void UeRrcTask::receiveRrcMessage(ASN_RRC_DL_DCCH_Message *msg) auto &c1 = msg->message.choice.c1; switch (c1->present) { - case ASN_RRC_DL_DCCH_MessageType__c1_PR_dlInformationTransfer: { + case ASN_RRC_DL_DCCH_MessageType__c1_PR_dlInformationTransfer: receiveDownlinkInformationTransfer(*c1->choice.dlInformationTransfer); break; + case ASN_RRC_DL_DCCH_MessageType__c1_PR_rrcRelease: + receiveRrcRelease(*c1->choice.rrcRelease); + break; default: break; } - } } void UeRrcTask::receiveRrcMessage(ASN_RRC_PCCH_Message *msg) diff --git a/src/ue/rrc/handler.cpp b/src/ue/rrc/handler.cpp index d3eb4d96b..a4ce1c812 100644 --- a/src/ue/rrc/handler.cpp +++ b/src/ue/rrc/handler.cpp @@ -9,6 +9,7 @@ #include "task.hpp" #include #include +#include #include #include #include @@ -124,4 +125,13 @@ void UeRrcTask::receiveDownlinkInformationTransfer(const ASN_RRC_DLInformationTr m_base->nasTask->push(nw); } +void UeRrcTask::receiveRrcRelease(const ASN_RRC_RRCRelease &msg) +{ + m_logger->debug("RRC Release received"); + m_state = ERrcState::RRC_IDLE; + + m_base->mrTask->push(new NwUeRrcToMr(NwUeRrcToMr::RRC_CONNECTION_RELEASE)); + m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_RELEASE)); +} + } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/task.cpp b/src/ue/rrc/task.cpp index 2ceccf6df..0ad118d6c 100644 --- a/src/ue/rrc/task.cpp +++ b/src/ue/rrc/task.cpp @@ -63,6 +63,10 @@ void UeRrcTask::onLoop() handleDownlinkRrc(w->channel, w->pdu); break; } + case NwUeMrToRrc::RADIO_LINK_FAILURE: { + handleRadioLinkFailure(); + break; + } } break; } @@ -91,4 +95,9 @@ void UeRrcTask::onLoop() delete msg; } +void UeRrcTask::handleRadioLinkFailure() +{ + m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RADIO_LINK_FAILURE)); +} + } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/rrc/task.hpp b/src/ue/rrc/task.hpp index 0b0ff0dee..dd3be84fa 100644 --- a/src/ue/rrc/task.hpp +++ b/src/ue/rrc/task.hpp @@ -34,6 +34,7 @@ extern "C" struct ASN_RRC_ULInformationTransfer; struct ASN_RRC_RRCSetup; struct ASN_RRC_RRCReject; + struct ASN_RRC_RRCRelease; } namespace nr::ue @@ -64,14 +65,17 @@ class UeRrcTask : public NtsTask private: /* Handlers */ - void handleDownlinkRrc(rrc::RrcChannel channel, const OctetString& pdu); + void handleDownlinkRrc(rrc::RrcChannel channel, const OctetString &pdu); void deliverInitialNas(OctetString &&nasPdu, long establishmentCause); void deliverUplinkNas(OctetString &&nasPdu); void receiveRrcSetup(const ASN_RRC_RRCSetup &msg); void receiveRrcReject(const ASN_RRC_RRCReject &msg); + void receiveRrcRelease(const ASN_RRC_RRCRelease &msg); void receiveDownlinkInformationTransfer(const ASN_RRC_DLInformationTransfer &msg); + void handleRadioLinkFailure(); + /* RRC channel send message */ void sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg); void sendRrcMessage(ASN_RRC_BCCH_DL_SCH_Message *msg); diff --git a/src/ue/sm/allocation.cpp b/src/ue/sm/allocation.cpp new file mode 100644 index 000000000..5c8614616 --- /dev/null +++ b/src/ue/sm/allocation.cpp @@ -0,0 +1,87 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "sm.hpp" +#include + +namespace nr::ue +{ + +int NasSm::allocatePduSessionId(const SessionConfig &config) +{ + if (config.type != nas::EPduSessionType::IPV4) + { + m_logger->debug("PDU session type [%s] is not supported", nas::utils::EnumToString(config.type)); + return 0; + } + + auto &arr = m_pduSessions; + + int id = -1; + for (int i = PduSession::MIN_ID; i <= PduSession::MAX_ID; i++) + { + if (arr[i].id == 0) + { + id = i; + break; + } + } + + if (id == -1) + { + m_logger->err("PDU session allocation failed"); + return 0; + } + + arr[id] = {}; + arr[id].id = id; + arr[id].isEstablished = false; + arr[id].apn = config.apn; + arr[id].sessionType = config.type; + arr[id].sNssai = config.sNssai; + + return id; +} + +int NasSm::allocateProcedureTransactionId() +{ + auto &arr = m_procedureTransactions; + + int id = -1; + for (int i = ProcedureTransaction::MIN_ID; i <= ProcedureTransaction::MAX_ID; i++) + { + if (arr[i].id == 0) + { + id = i; + break; + } + } + + if (id == -1) + { + m_logger->err("PTI allocation failed"); + return 0; + } + + arr[id] = {}; + arr[id].id = id; + + return id; +} + +void NasSm::freeProcedureTransactionId(int pti) +{ + m_procedureTransactions[pti] = {}; +} + +void NasSm::freePduSessionId(int psi) +{ + m_pduSessions[psi] = {}; +} + +} // namespace nr::ue \ No newline at end of file diff --git a/src/ue/sm/base.cpp b/src/ue/sm/base.cpp index 70a13260a..956056f98 100644 --- a/src/ue/sm/base.cpp +++ b/src/ue/sm/base.cpp @@ -11,7 +11,7 @@ namespace nr::ue { -NasSm::NasSm(TaskBase *base, NtsTask *nas, UeTimers *timers) : m_base(base), m_nas(nas), m_timers(timers), m_mm(nullptr) +NasSm::NasSm(TaskBase *base, UeTimers *timers) : m_base(base), m_timers(timers), m_mm(nullptr) { m_logger = base->logBase->makeUniqueLogger(base->config->getLoggerPrefix() + "nas"); } diff --git a/src/ue/sm/resource.cpp b/src/ue/sm/resource.cpp index 452e75d38..88de99f88 100644 --- a/src/ue/sm/resource.cpp +++ b/src/ue/sm/resource.cpp @@ -7,82 +7,34 @@ // #include "sm.hpp" +#include #include +#include namespace nr::ue { -int NasSm::allocatePduSessionId(const SessionConfig &config) +void NasSm::localReleaseSession(int psi) { - if (config.type != nas::EPduSessionType::IPV4) - { - m_logger->debug("PDU session type [%s] is not supported", nas::utils::EnumToString(config.type)); - return 0; - } - - auto &arr = m_pduSessions; - - int id = -1; - for (int i = PduSession::MIN_ID; i <= PduSession::MAX_ID; i++) - { - if (arr[i].id == 0) - { - id = i; - break; - } - } - - if (id == -1) - { - m_logger->err("PDU session allocation failed"); - return 0; - } - - arr[id] = {}; - arr[id].id = id; - arr[id].isEstablished = false; - arr[id].apn = config.apn; - arr[id].sessionType = config.type; - arr[id].sNssai = config.sNssai; + m_logger->debug("Performing local release of PDU session[%d]", psi); - return id; -} - -int NasSm::allocateProcedureTransactionId() -{ - auto &arr = m_procedureTransactions; + bool isEstablished = m_pduSessions[psi].isEstablished; - int id = -1; - for (int i = ProcedureTransaction::MIN_ID; i <= ProcedureTransaction::MAX_ID; i++) - { - if (arr[i].id == 0) - { - id = i; - break; - } - } + freePduSessionId(psi); - if (id == -1) + if (isEstablished) { - m_logger->err("PTI allocation failed"); - return 0; + auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::SESSION_RELEASE); + statusUpdate->psi = psi; + m_base->appTask->push(statusUpdate); } - - arr[id] = {}; - arr[id].id = id; - - return id; -} - -void NasSm::releaseProcedureTransactionId(int pti) -{ - m_procedureTransactions[pti].id = 0; } -void NasSm::releasePduSession(int psi) +void NasSm::localReleaseAllSessions() { - m_pduSessions[psi].id = 0; - m_logger->info("PDU session[%d] released", psi); + for (auto &session : m_pduSessions) + if (session.id != 0) + localReleaseSession(session.id); } } // namespace nr::ue \ No newline at end of file diff --git a/src/ue/sm/session.cpp b/src/ue/sm/session.cpp index 1be384c66..bb26f129e 100644 --- a/src/ue/sm/session.cpp +++ b/src/ue/sm/session.cpp @@ -7,8 +7,8 @@ // #include "sm.hpp" -#include #include +#include #include namespace nr::ue @@ -29,7 +29,7 @@ void NasSm::sendEstablishmentRequest(const SessionConfig &config) if (pti == 0) { m_logger->err("PDU Session Establishment Request could not send"); - releasePduSession(psi); + freePduSessionId(psi); return; } @@ -71,7 +71,7 @@ void NasSm::receivePduSessionEstablishmentAccept(const nas::PduSessionEstablishm m_timers->t3580.stop(); - releaseProcedureTransactionId(msg.pti); + freeProcedureTransactionId(msg.pti); auto &pduSession = m_pduSessions[static_cast(msg.pduSessionId)]; if (pduSession.id == 0) diff --git a/src/ue/sm/sm.hpp b/src/ue/sm/sm.hpp index 5d714b5c4..30101398b 100644 --- a/src/ue/sm/sm.hpp +++ b/src/ue/sm/sm.hpp @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -23,18 +24,17 @@ class NasSm { private: TaskBase *m_base; - NtsTask *m_nas; UeTimers *m_timers; std::unique_ptr m_logger; NasMm *m_mm; - PduSession m_pduSessions[16]{}; + std::array m_pduSessions{}; ProcedureTransaction m_procedureTransactions[255]{}; friend class UeCmdHandler; public: - NasSm(TaskBase *base, NtsTask *nas, UeTimers *timers); + NasSm(TaskBase *base, UeTimers *timers); public: /* Base */ @@ -45,17 +45,21 @@ class NasSm /* Transport */ void receiveSmMessage(const nas::SmMessage &msg); + /* Resource */ + void localReleaseSession(int psi); + void localReleaseAllSessions(); + private: /* Transport */ void sendSmMessage(int psi, const nas::SmMessage &msg); void receiveSmStatus(const nas::FiveGSmStatus &msg); void receiveSmCause(const nas::IE5gSmCause &msg); - /* Resource */ + /* Allocation */ int allocatePduSessionId(const SessionConfig &config); int allocateProcedureTransactionId(); - void releaseProcedureTransactionId(int pti); - void releasePduSession(int psi); + void freeProcedureTransactionId(int pti); + void freePduSessionId(int psi); /* Session */ void sendEstablishmentRequest(const SessionConfig &config); diff --git a/src/ue/types.cpp b/src/ue/types.cpp index 2fd3bbd0e..29d81c0e4 100644 --- a/src/ue/types.cpp +++ b/src/ue/types.cpp @@ -134,14 +134,28 @@ Json ToJson(const UeConfig &v) Json ToJson(const UeTimers &v) { return Json::Obj({ - {"T3346", ToJson(v.t3346)}, {"T3396", ToJson(v.t3396)}, {"T3444", ToJson(v.t3444)}, - {"T3445", ToJson(v.t3445)}, {"T3502", ToJson(v.t3502)}, {"T3510", ToJson(v.t3510)}, - {"T3511", ToJson(v.t3511)}, {"T3512", ToJson(v.t3512)}, {"T3516", ToJson(v.t3516)}, - {"T3517", ToJson(v.t3517)}, {"T3519", ToJson(v.t3519)}, {"T3520", ToJson(v.t3520)}, - {"T3521", ToJson(v.t3521)}, {"T3525", ToJson(v.t3525)}, {"T3540", ToJson(v.t3540)}, - {"T3580", ToJson(v.t3580)}, {"T3581", ToJson(v.t3581)}, {"T3582", ToJson(v.t3582)}, - {"T3583", ToJson(v.t3583)}, {"T3584", ToJson(v.t3584)}, {"T3585", ToJson(v.t3585)}, + {"T3346", ToJson(v.t3346)}, {"T3396", ToJson(v.t3396)}, {"T3444", ToJson(v.t3444)}, {"T3445", ToJson(v.t3445)}, + {"T3502", ToJson(v.t3502)}, {"T3510", ToJson(v.t3510)}, {"T3511", ToJson(v.t3511)}, {"T3512", ToJson(v.t3512)}, + {"T3516", ToJson(v.t3516)}, {"T3517", ToJson(v.t3517)}, {"T3519", ToJson(v.t3519)}, {"T3520", ToJson(v.t3520)}, + {"T3521", ToJson(v.t3521)}, {"T3525", ToJson(v.t3525)}, {"T3540", ToJson(v.t3540)}, {"T3580", ToJson(v.t3580)}, + {"T3581", ToJson(v.t3581)}, {"T3582", ToJson(v.t3582)}, {"T3583", ToJson(v.t3583)}, {"T3584", ToJson(v.t3584)}, + {"T3585", ToJson(v.t3585)}, }); } +Json ToJson(const E5UState &state) +{ + switch (state) + { + case E5UState::U1_UPDATED: + return "5U1-UPDATED"; + case E5UState::U2_NOT_UPDATED: + return "5U2-NOT-UPDATED"; + case E5UState::U3_ROAMING_NOT_ALLOWED: + return "5U3-ROAMING-NOT-ALLOWED"; + default: + return "?"; + } +} + } // namespace nr::ue diff --git a/src/ue/types.hpp b/src/ue/types.hpp index f04fde875..c71c394f1 100644 --- a/src/ue/types.hpp +++ b/src/ue/types.hpp @@ -9,6 +9,8 @@ #pragma once #include +#include +#include #include #include #include @@ -24,6 +26,7 @@ class UeAppTask; class UeMrTask; class NasTask; class UeRrcTask; +class UserEquipment; struct SupportedAlgs { @@ -65,9 +68,9 @@ struct UeConfig std::vector initSessions{}; /* Assigned by program */ - bool emulationMode; - bool configureRouting; - bool prefixLogger; + bool autoBehaviour{}; + bool configureRouting{}; + bool prefixLogger{}; [[nodiscard]] std::string getNodeName() const { @@ -96,9 +99,12 @@ struct UeConfig struct TaskBase { + UserEquipment *ue{}; UeConfig *config{}; LogBase *logBase{}; + app::IUeController *ueController{}; app::INodeListener *nodeListener{}; + NtsTask *cliCallbackTask{}; UeAppTask *appTask{}; UeMrTask *mrTask{}; @@ -348,21 +354,17 @@ enum class EAutnValidationRes SYNCHRONISATION_FAILURE, }; -struct UeStatusInfo +struct UePduSessionInfo { - struct UePduSessionInfo - { - std::string type{}; - std::string address{}; - }; - - std::optional pduSessions[16]{}; + std::string type{}; + std::string address{}; }; Json ToJson(const ECmState &state); Json ToJson(const ERmState &state); Json ToJson(const EMmState &state); Json ToJson(const EMmSubState &state); +Json ToJson(const E5UState &state); Json ToJson(const UeConfig &v); Json ToJson(const UeTimers &v); diff --git a/src/ue/ue.cpp b/src/ue/ue.cpp index a5d2b41e4..0d9354569 100644 --- a/src/ue/ue.cpp +++ b/src/ue/ue.cpp @@ -16,12 +16,16 @@ namespace nr::ue { -UserEquipment::UserEquipment(UeConfig *config, app::INodeListener *nodeListener) +UserEquipment::UserEquipment(UeConfig *config, app::IUeController *ueController, app::INodeListener *nodeListener, + NtsTask *cliCallbackTask) { auto *base = new TaskBase(); + base->ue = this; base->config = config; base->logBase = new LogBase("logs/ue-" + config->getNodeName() + ".log"); + base->ueController = ueController; base->nodeListener = nodeListener; + base->cliCallbackTask = cliCallbackTask; base->nasTask = new NasTask(base); base->rrcTask = new UeRrcTask(base); @@ -56,10 +60,9 @@ void UserEquipment::start() taskBase->appTask->start(); } -void UserEquipment::pushCommand(std::unique_ptr cmd, const InetAddress &address, - NtsTask *callbackTask) +void UserEquipment::pushCommand(std::unique_ptr cmd, const InetAddress &address) { - taskBase->appTask->push(new NwUeCliCommand(std::move(cmd), address, callbackTask)); + taskBase->appTask->push(new NwUeCliCommand(std::move(cmd), address)); } } // namespace nr::ue diff --git a/src/ue/ue.hpp b/src/ue/ue.hpp index bbb5a81d5..be3d356b8 100644 --- a/src/ue/ue.hpp +++ b/src/ue/ue.hpp @@ -23,12 +23,13 @@ class UserEquipment TaskBase *taskBase; public: - UserEquipment(UeConfig *config, app::INodeListener *nodeListener); + UserEquipment(UeConfig *config, app::IUeController *ueController, app::INodeListener *nodeListener, + NtsTask *cliCallbackTask); virtual ~UserEquipment(); public: void start(); - void pushCommand(std::unique_ptr cmd, const InetAddress &address, NtsTask *callbackTask); + void pushCommand(std::unique_ptr cmd, const InetAddress &address); }; } // namespace nr::ue \ No newline at end of file diff --git a/src/urs/rls/gnb_entity.cpp b/src/urs/rls/gnb_entity.cpp index f0c39bb87..85a125ae3 100644 --- a/src/urs/rls/gnb_entity.cpp +++ b/src/urs/rls/gnb_entity.cpp @@ -70,6 +70,11 @@ void RlsGnbEntity::releaseConnection(int ue, ECause cause) removeUe(ue, cause); } +void RlsGnbEntity::localReleaseConnection(int ue, ECause cause) +{ + removeUe(ue, cause); +} + void RlsGnbEntity::sendRlsMessage(int ue, const RlsMessage &msg) { OctetString buf{}; @@ -107,6 +112,9 @@ void RlsGnbEntity::sendReleaseIndication(int ue, ECause cause) void RlsGnbEntity::removeUe(int ue, ECause cause) { + if (idUeMap.count(ue) == 0) + return; + uint64_t ueToken = idUeMap[ue]; ueIdMap.erase(ueToken); idUeMap.erase(ue); diff --git a/src/urs/rls/gnb_entity.hpp b/src/urs/rls/gnb_entity.hpp index d0942808f..5f3a85a4e 100644 --- a/src/urs/rls/gnb_entity.hpp +++ b/src/urs/rls/gnb_entity.hpp @@ -10,10 +10,10 @@ #include "rls.hpp" -#include #include #include #include +#include namespace rls { @@ -50,6 +50,7 @@ class RlsGnbEntity 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); diff --git a/src/urs/rls/rls.cpp b/src/urs/rls/rls.cpp index 121f1a655..0f4d06967 100644 --- a/src/urs/rls/rls.cpp +++ b/src/urs/rls/rls.cpp @@ -33,21 +33,13 @@ DecodeRes Decode(const OctetView &stream, RlsMessage &output, octet3 appVersion) output.msgType != EMessageType::RLS_PAYLOAD_TRANSPORT && output.msgType != EMessageType::RLS_SETUP_RESPONSE) return DecodeRes::FAILURE; output.ueToken = stream.read8UL(); - if (output.msgType != EMessageType::RLS_SETUP_REQUEST) - output.gnbToken = stream.read8UL(); - if (output.msgType == EMessageType::RLS_PAYLOAD_TRANSPORT) - { - output.payloadType = static_cast(stream.readI()); - uint16_t len = stream.read2US(); - output.payload = stream.readOctetString(len); - } - if (output.msgType == EMessageType::RLS_SETUP_FAILURE) - output.cause = static_cast(stream.readI()); - if (output.msgType == EMessageType::RLS_SETUP_RESPONSE || output.msgType == EMessageType::RLS_SETUP_COMPLETE) - { - uint16_t len = stream.read2US(); - output.str = stream.readUtf8String(len); - } + 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; } @@ -67,30 +59,23 @@ bool Encode(const RlsMessage &msg, OctetString &stream) } if (msg.msgCls == EMessageClass::NORMAL_MESSAGE) { - stream.appendOctet3(msg.appVersion); - stream.appendOctet(static_cast(msg.msgType)); - stream.appendOctet8(msg.ueToken); 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; - if (msg.msgType != EMessageType::RLS_SETUP_REQUEST) - stream.appendOctet8(msg.gnbToken); - if (msg.msgType == EMessageType::RLS_SETUP_FAILURE) - stream.appendOctet(static_cast(msg.cause)); - if (msg.msgType == EMessageType::RLS_PAYLOAD_TRANSPORT) - { - stream.appendOctet(static_cast(msg.payloadType)); - stream.appendOctet2(msg.payload.length()); - stream.append(msg.payload); - } - if (msg.msgType == EMessageType::RLS_SETUP_RESPONSE || msg.msgType == EMessageType::RLS_SETUP_COMPLETE) - { - stream.appendOctet2(msg.str.length()); - for (char c : msg.str) - stream.appendOctet(c); - } + + 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; @@ -101,15 +86,17 @@ const char *CauseToString(ECause cause) switch (cause) { case ECause::UNSPECIFIED: - return "RLS_UNSPECIFIED"; + return "RLS-UNSPECIFIED"; case ECause::TOKEN_CONFLICT: - return "RLS_TOKEN_CONFLICT"; + return "RLS-TOKEN-CONFLICT"; case ECause::EMPTY_SEARCH_LIST: - return "RLS_EMPTY_SEARCH_LIST"; + return "RLS-EMPTY-SEARCH-LIST"; case ECause::SETUP_TIMEOUT: - return "RLS_SETUP_TIMEOUT"; + return "RLS-SETUP-TIMEOUT"; case ECause::HEARTBEAT_TIMEOUT: - return "RLS_HEARTBEAT_TIMEOUT"; + return "RLS-HEARTBEAT-TIMEOUT"; + case ECause::RRC_RELEASE: + return "RLS-RRC-RELEASE"; default: return "?"; } diff --git a/src/urs/rls/rls.hpp b/src/urs/rls/rls.hpp index 0fea6796f..73de84170 100644 --- a/src/urs/rls/rls.hpp +++ b/src/urs/rls/rls.hpp @@ -45,13 +45,23 @@ enum class EMessageType : uint8_t enum class ECause : uint8_t { + // Error causes (treated as radio link failure) UNSPECIFIED = 0, TOKEN_CONFLICT, EMPTY_SEARCH_LIST, SETUP_TIMEOUT, - HEARTBEAT_TIMEOUT + HEARTBEAT_TIMEOUT, + + // Successful causes + RRC_RELEASE, }; +// Checks if the cause treated as radio link failure +inline bool IsRlf(ECause cause) +{ + return cause != ECause::RRC_RELEASE; +} + enum class EPayloadType : uint8_t { RRC, diff --git a/src/urs/rls/ue_entity.cpp b/src/urs/rls/ue_entity.cpp index 1b46a2a24..d3cf2c8d3 100644 --- a/src/urs/rls/ue_entity.cpp +++ b/src/urs/rls/ue_entity.cpp @@ -8,9 +8,9 @@ #include "ue_entity.hpp" +#include #include #include -#include static const octet3 AppVersion = octet3{cons::Major, cons::Minor, cons::Patch}; @@ -226,13 +226,7 @@ void RlsUeEntity::onReceive(const InetAddress &address, const OctetString &pdu) void RlsUeEntity::releaseConnection(ECause cause) { sendReleaseIndication(cause); - state = EUeState::RELEASED; - nextSearch = 0; - ueToken = 0; - gnbToken = 0; - lastGnbHeartbeat = 0; - lastError = ECause::UNSPECIFIED; - onRelease(cause); + localReleaseConnection(cause); } void RlsUeEntity::resetEntity() @@ -293,4 +287,15 @@ void RlsUeEntity::sendRlsMessage(const InetAddress &address, const RlsMessage &m 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 index 21396f1a9..11bb4a5fd 100644 --- a/src/urs/rls/ue_entity.hpp +++ b/src/urs/rls/ue_entity.hpp @@ -51,6 +51,7 @@ class RlsUeEntity void onUplinkDelivery(EPayloadType type, OctetString &&payload); void startGnbSearch(); void releaseConnection(ECause cause); + void localReleaseConnection(ECause cause); void resetEntity(); private: diff --git a/src/utils/common_types.hpp b/src/utils/common_types.hpp index bc22a6090..e1929540f 100644 --- a/src/utils/common_types.hpp +++ b/src/utils/common_types.hpp @@ -25,9 +25,9 @@ enum class EPagingDrx struct Plmn { - int mcc; - int mnc; - bool isLongMnc; + int mcc{}; + int mnc{}; + bool isLongMnc{}; }; struct SliceSupport @@ -47,32 +47,16 @@ enum class PduSessionType struct PlmnSupport { - Plmn plmn; - std::vector> sliceSupportList; -}; - -struct TmsiMobileIdentity -{ - int amfSetId : 10; - int amfPointer : 6; - octet4 tmsi; - - TmsiMobileIdentity() : amfSetId{}, amfPointer{}, tmsi{} - { - } - - TmsiMobileIdentity(int amfSetId, int amfPointer, const octet4 &tmsi) - : amfSetId(amfSetId), amfPointer(amfPointer), tmsi(tmsi) - { - } + Plmn plmn{}; + std::vector> sliceSupportList{}; }; struct GutiMobileIdentity { Plmn plmn; // Not used in TMSI octet amfRegionId; // Not used in TMSI - int amfSetId : 10; - int amfPointer : 6; + int amfSetId; // 10-bit + int amfPointer; // 6-bit octet4 tmsi; GutiMobileIdentity() : plmn{}, amfRegionId{}, amfSetId{}, amfPointer{}, tmsi{} @@ -89,7 +73,7 @@ struct ImsiMobileIdentity { Plmn plmn; std::string routingIndicator; - int protectionSchemaId : 4; + int protectionSchemaId; // 4-bit octet homeNetworkPublicKeyIdentifier; std::string schemeOutput; diff --git a/src/utils/concurrent_map.cpp b/src/utils/concurrent_map.cpp new file mode 100644 index 000000000..87e7f35e7 --- /dev/null +++ b/src/utils/concurrent_map.cpp @@ -0,0 +1,9 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "concurrent_map.hpp" diff --git a/src/utils/concurrent_map.hpp b/src/utils/concurrent_map.hpp new file mode 100644 index 000000000..6cf5b23ad --- /dev/null +++ b/src/utils/concurrent_map.hpp @@ -0,0 +1,59 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include +#include + +#pragma once + +template +class ConcurrentMap +{ + private: + std::unordered_map m_map{}; + mutable std::recursive_mutex m_mutex{}; + + public: + ConcurrentMap() = default; + + public: + TValue getOrDefault(const TKey &key) + { + std::lock_guard lk(m_mutex); + if (m_map.count(key) != 0) + return m_map[key]; + return TValue{}; + } + + void put(const TKey &key, const TValue &value) + { + std::lock_guard lk(m_mutex); + m_map[key] = value; + } + + template + void invokeForeach(const Fun &fun) const + { + std::lock_guard lk(m_mutex); + for (auto i : m_map) + fun(i); + } + + void remove(const TKey &key) + { + std::lock_guard lk(m_mutex); + m_map.erase(key); + } + + size_t removeAndGetSize(const TKey &key) + { + std::lock_guard lk(m_mutex); + m_map.erase(key); + return m_map.size(); + } +}; \ No newline at end of file diff --git a/src/utils/constants.hpp b/src/utils/constants.hpp index 8644c33cc..299b06986 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 = 0; + static constexpr const uint8_t Patch = 1; static constexpr const char *Project = "UERANSIM"; - static constexpr const char *Tag = "v3.1.0"; - static constexpr const char *Name = "UERANSIM v3.1.0"; + static constexpr const char *Tag = "v3.1.1"; + static constexpr const char *Name = "UERANSIM v3.1.1"; static constexpr const char *Owner = "ALİ GÜNGÖR"; // Some port values diff --git a/src/utils/nts.cpp b/src/utils/nts.cpp index 7b3db4ca1..d401d85b5 100644 --- a/src/utils/nts.cpp +++ b/src/utils/nts.cpp @@ -227,6 +227,8 @@ void NtsTask::quit() while (!isQuiting.compare_exchange_weak(expected, true, std::memory_order_relaxed, std::memory_order_relaxed)) return; + cv.notify_one(); + if (thread.joinable()) thread.join(); diff --git a/src/utils/nts.hpp b/src/utils/nts.hpp index a347cbf37..f2ab32313 100644 --- a/src/utils/nts.hpp +++ b/src/utils/nts.hpp @@ -29,6 +29,7 @@ enum class NtsMessageType GNB_CLI_COMMAND, UE_STATUS_UPDATE, UE_CLI_COMMAND, + UE_CTL_COMMAND, UDP_SERVER_RECEIVE, CLI_SEND_RESPONSE, @@ -52,7 +53,8 @@ enum class NtsMessageType UE_RRC_TO_NAS, UE_NAS_TO_RRC, UE_RRC_TO_MR, - UE_NAS_TO_NAS, + UE_NAS_TO_NAS, + UE_NAS_TO_APP, }; struct NtsMessage diff --git a/src/utils/octet_string.cpp b/src/utils/octet_string.cpp index 328ee717e..186f16168 100644 --- a/src/utils/octet_string.cpp +++ b/src/utils/octet_string.cpp @@ -15,6 +15,11 @@ void OctetString::append(const OctetString &v) m_data.insert(m_data.end(), v.m_data.begin(), v.m_data.end()); } +void OctetString::appendUtf8(const std::string &v) +{ + m_data.insert(m_data.end(), v.begin(), v.end()); +} + void OctetString::appendOctet(uint8_t v) { m_data.push_back(v); diff --git a/src/utils/octet_string.hpp b/src/utils/octet_string.hpp index 4a75b5232..2ab6a6420 100644 --- a/src/utils/octet_string.hpp +++ b/src/utils/octet_string.hpp @@ -35,6 +35,7 @@ class OctetString public: void append(const OctetString &v); + void appendUtf8(const std::string &v); void appendOctet(uint8_t v); void appendOctet(int v); void appendOctet(int bigHalf, int littleHalf); diff --git a/src/utils/options.cpp b/src/utils/options.cpp index 2910a1d55..0cf1d2216 100644 --- a/src/utils/options.cpp +++ b/src/utils/options.cpp @@ -269,10 +269,15 @@ void opt::OptionsResult::showHelp() const ostream << " " << m_description.programName << " " << usage << std::endl; ostream << std::endl; - ostream << "Options:" << std::endl; std::vector items = m_description.items; - items.emplace_back('h', "help", "Show this help message and exit", std::nullopt); - items.emplace_back('v', "version", "Show version information and exit", std::nullopt); + if (!m_description.hideDefaultOptionsInUsage) + { + items.emplace_back('h', "help", "Show this help message and exit", std::nullopt); + items.emplace_back('v', "version", "Show version information and exit", std::nullopt); + } + + if (!items.empty()) + ostream << "Options:" << std::endl; size_t maxLengthOfItemName = 0; for (auto &item : items) diff --git a/src/utils/options.hpp b/src/utils/options.hpp index fbf2bad1d..bbc0a2098 100644 --- a/src/utils/options.hpp +++ b/src/utils/options.hpp @@ -73,12 +73,14 @@ struct OptionsDescription std::vector items{}; std::vector usages{}; bool helpIfEmpty{}; + bool hideDefaultOptionsInUsage{}; OptionsDescription(std::string projectName, std::string version, std::string appDescription, std::string copyright, - std::string programName, std::vector usages, bool helpIfEmpty) + std::string programName, std::vector usages, bool helpIfEmpty, + bool hideDefaultOptionsInUsage) : projectName(std::move(projectName)), version(std::move(version)), appDescription(std::move(appDescription)), copyright(std::move(copyright)), programName(std::move(programName)), usages(std::move(usages)), - helpIfEmpty(helpIfEmpty) + helpIfEmpty(helpIfEmpty), hideDefaultOptionsInUsage(hideDefaultOptionsInUsage) { } };