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)
{
}
};