Unverified Commit 7a4a2464 authored by Ali Güngör's avatar Ali Güngör Committed by GitHub

Merge pull request #259 from aligungr/dev

v3.1.1
parents 4f1f5a9a 5a95b1c5
......@@ -2,7 +2,7 @@
<a href="https://github.com/aligungr/UERANSIM"><img src="/.github/logo.png" width="75" title="UERANSIM"></a>
</p>
<p align="center">
<img src="https://img.shields.io/badge/UERANSIM-v3.1.0-blue" />
<img src="https://img.shields.io/badge/UERANSIM-v3.1.1-blue" />
<img src="https://img.shields.io/badge/3GPP-R15-orange" />
<img src="https://img.shields.io/badge/License-GPL--3.0-green"/>
</p>
......@@ -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
......
......@@ -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<std::string, std::string> g_gnbCmdToDescription = {
};
static std::map<std::string, std::string> g_gnbCmdToUsage = {
{"status", "[option...]"}, {"info", "[option...]"},
{"amf-list", "[option...]"}, {"amf-info", "<amf-id> [option...]"},
{"ue-list", "[option...]"}, {"ue-count", "[option...]"},
{"status", ""}, {"info", ""}, {"amf-list", ""}, {"amf-info", "<amf-id>"}, {"ue-list", ""}, {"ue-count", ""},
};
static std::map<std::string, bool> g_gnbCmdToHelpIfEmpty = {{"status", false}, {"info", false},
......@@ -84,18 +82,21 @@ static std::map<std::string, std::string> 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<std::string, std::string> g_ueCmdToUsage = {
{"info", "[option...]"},
{"status", "[option...]"},
{"timers", "[option...]"},
{"info", ""},
{"status", ""},
{"timers", ""},
{"deregister", "<normal|disable-5g|switch-off>"},
};
static std::map<std::string, bool> g_ueCmdToHelpIfEmpty = {
{"info", false},
{"status", false},
{"timers", false},
{"deregister", true},
};
std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error,
......@@ -224,6 +225,22 @@ std::unique_ptr<UeCliCommand> ParseUeCliCommand(std::vector<std::string> &&token
{
return std::make_unique<UeCliCommand>(UeCliCommand::TIMERS);
}
else if (subCmd == "deregister")
{
auto cmd = std::make_unique<UeCliCommand>(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;
}
......
......@@ -42,8 +42,13 @@ struct UeCliCommand
INFO,
STATUS,
TIMERS,
DE_REGISTER,
} present;
// DE_REGISTER
bool isSwitchOff{};
bool dueToDisable5g{};
explicit UeCliCommand(PR present) : present(present)
{
}
......
......@@ -35,6 +35,7 @@ enum class StateType
MM_SUB,
RM,
CM,
U5,
};
class INodeListener
......
......@@ -10,6 +10,7 @@
#include <string>
#include <unordered_map>
#include <utils/concurrent_map.hpp>
#include <vector>
namespace app
......@@ -41,4 +42,12 @@ inline void CreateProcTable(const std::unordered_map<std::string, T> &nodeMap, i
CreateProcTable(nodes, cmdPort);
}
template <typename T>
inline void CreateProcTable(const ConcurrentMap<std::string, T> &nodeMap, int cmdPort)
{
std::vector<std::string> nodes{};
nodeMap.invokeForeach([&nodes](const auto &node) { nodes.push_back(node.first); });
CreateProcTable(nodes, cmdPort);
}
} // namespace app
//
// 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"
//
// 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
......@@ -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:
......
......@@ -133,7 +133,7 @@ static void ReadOptions(int argc, char **argv)
{
opt::OptionsDescription desc{"UERANSIM", cons::Tag, "Command Line Interface",
cons::Owner, "nr-cli", {"<node-name> [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",
......
......@@ -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 <config-file> [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();
}
......
......@@ -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;
}
}
......
......@@ -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
......@@ -42,7 +42,8 @@ void GnbAppTask::onLoop()
}
case NtsMessageType::GNB_CLI_COMMAND: {
auto *w = dynamic_cast<NwGnbCliCommand *>(msg);
GnbCmdHandler::HandleCmd(*m_base, *w);
GnbCmdHandler handler{m_base};
handler.handleCmd(*w);
break;
}
default:
......
......@@ -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<app::GnbCliCommand> cmd, const InetAddress &address, NtsTask *callbackTask)
void GNodeB::pushCommand(std::unique_ptr<app::GnbCliCommand> 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
......@@ -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<app::GnbCliCommand> cmd, const InetAddress &address, NtsTask *callbackTask);
void pushCommand(std::unique_ptr<app::GnbCliCommand> cmd, const InetAddress &address);
};
} // namespace nr::gnb
\ No newline at end of file
......@@ -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<uint64_t> 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();
......
......@@ -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);
......
......@@ -57,6 +57,16 @@ void PduSessionTree::remove(uint64_t session, uint32_t downTeid)
}
}
void PduSessionTree::enumerateByUe(int ue, std::vector<uint64_t> &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)
......
......@@ -11,6 +11,7 @@
#include <gnb/types.hpp>
#include <memory>
#include <unordered_map>
#include <vector>
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<uint64_t>& output);
};
class TokenBucket
......
......@@ -8,9 +8,9 @@
#include "task.hpp"
#include "rls.hpp"
#include <gnb/gtp/task.hpp>
#include <gnb/nts.hpp>
#include <gnb/rrc/task.hpp>
#include <gnb/gtp/task.hpp>
#include <utils/constants.hpp>
#include <utils/libc_error.hpp>
......@@ -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());
}
......
......@@ -9,6 +9,9 @@
#include "task.hpp"
#include "utils.hpp"
#include <gnb/gtp/task.hpp>
#include <gnb/rrc/task.hpp>
#include <asn/ngap/ASN_NGAP_AMF-UE-NGAP-ID.h>
#include <asn/ngap/ASN_NGAP_InitialContextSetupRequest.h>
#include <asn/ngap/ASN_NGAP_InitialContextSetupResponse.h>
......@@ -22,8 +25,8 @@
#include <asn/ngap/ASN_NGAP_UEContextModificationResponse.h>
#include <asn/ngap/ASN_NGAP_UEContextReleaseCommand.h>
#include <asn/ngap/ASN_NGAP_UEContextReleaseComplete.h>
#include <asn/ngap/ASN_NGAP_UEContextReleaseRequest.h>
#include <asn/ngap/ASN_NGAP_UESecurityCapabilities.h>
#include <gnb/gtp/task.hpp>
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<ASN_NGAP_UEContextReleaseComplete>({});
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<ASN_NGAP_UEContextReleaseRequest_IEs>();
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<ASN_NGAP_UEContextReleaseRequest>({ieCause});
sendNgapUeAssociated(ueId, pdu);
}
} // namespace nr::gnb
\ No newline at end of file
......@@ -11,8 +11,8 @@
#include <algorithm>
#include <gnb/app/task.hpp>
#include <gnb/sctp/task.hpp>
#include <gnb/rrc/task.hpp>
#include <gnb/sctp/task.hpp>
#include <asn/ngap/ASN_NGAP_AMFConfigurationUpdate.h>
#include <asn/ngap/ASN_NGAP_AMFConfigurationUpdateFailure.h>
......@@ -28,6 +28,7 @@
#include <asn/ngap/ASN_NGAP_ServedGUAMIItem.h>
#include <asn/ngap/ASN_NGAP_SliceSupportItem.h>
#include <asn/ngap/ASN_NGAP_SupportedTAItem.h>
#include <asn/ngap/ASN_NGAP_OverloadStartNSSAIItem.h>
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<int>(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
//
// 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 <gnb/gtp/task.hpp>
#include <gnb/rrc/task.hpp>
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
......@@ -60,6 +60,10 @@ void NgapTask::onLoop()
handleUplinkNasTransport(w->ueId, w->pdu);
break;
}
case NwGnbRrcToNgap::RADIO_LINK_FAILURE: {
handleRadioLinkFailure(w->ueId);
break;
}
}
break;
}
......
......@@ -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
......@@ -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;
......
......@@ -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<app::GnbCliCommand> cmd;
InetAddress address;
NtsTask *callbackTask;
NwGnbCliCommand(std::unique_ptr<app::GnbCliCommand> 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<app::GnbCliCommand> cmd, InetAddress address)
: NtsMessage(NtsMessageType::GNB_CLI_COMMAND), cmd(std::move(cmd)), address(address)
{
callbackTask->push(new app::NwCliSendResponse(address, output, true));
}
};
......
......@@ -7,6 +7,11 @@
//
#include "task.hpp"
#include <gnb/mr/task.hpp>
#include <gnb/ngap/task.hpp>
#include <rrc/encode.hpp>
#include <asn/rrc/ASN_RRC_BCCH-BCH-Message.h>
#include <asn/rrc/ASN_RRC_BCCH-DL-SCH-Message.h>
#include <asn/rrc/ASN_RRC_CellGroupConfig.h>
......@@ -15,6 +20,8 @@
#include <asn/rrc/ASN_RRC_DLInformationTransfer-IEs.h>
#include <asn/rrc/ASN_RRC_DLInformationTransfer.h>
#include <asn/rrc/ASN_RRC_PCCH-Message.h>
#include <asn/rrc/ASN_RRC_RRCRelease-IEs.h>
#include <asn/rrc/ASN_RRC_RRCRelease.h>
#include <asn/rrc/ASN_RRC_RRCSetup-IEs.h>
#include <asn/rrc/ASN_RRC_RRCSetup.h>
#include <asn/rrc/ASN_RRC_RRCSetupComplete-IEs.h>
......@@ -25,8 +32,6 @@
#include <asn/rrc/ASN_RRC_UL-DCCH-Message.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer-IEs.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer.h>
#include <gnb/ngap/task.hpp>
#include <rrc/encode.hpp>
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<ASN_RRC_DL_DCCH_Message>();
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<ASN_RRC_RRCRelease>();
rrcRelease->rrc_TransactionIdentifier = getNextTid();
rrcRelease->criticalExtensions.present = ASN_RRC_RRCRelease__criticalExtensions_PR_rrcRelease;
rrcRelease->criticalExtensions.choice.rrcRelease = asn::New<ASN_RRC_RRCRelease_IEs>();
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
......@@ -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;
}
......
......@@ -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);
......
......@@ -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<ServedGuami *> servedGuamiList;
std::vector<PlmnSupport *> 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<ServedGuami *> servedGuamiList{};
std::vector<PlmnSupport *> 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<int64_t> amfUeNgapId;
std::optional<int64_t> ranUeNgapId;
std::optional<int64_t> amfUeNgapId{};
std::optional<int64_t> ranUeNgapId{};
NgapIdPair() : amfUeNgapId{}, ranUeNgapId{}
{
......@@ -209,7 +242,7 @@ struct PduSessionResource
PduSessionType sessionType = PduSessionType::UNSTRUCTURED;
GtpTunnel upTunnel{};
GtpTunnel downTunnel{};
asn::Unique<ASN_NGAP_QosFlowSetupRequestList> qosFlows;
asn::Unique<ASN_NGAP_QosFlowSetupRequestList> 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<SliceSupport> nssais;
std::vector<GnbAmfConfig> 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<SliceSupport> nssais{};
std::vector<GnbAmfConfig> 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);
......
......@@ -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);
......
......@@ -152,7 +152,7 @@ struct IES1UeNetworkCapability : InformationElement4
struct IEGprsTimer3 : InformationElement4
{
int timerValue : 5;
int timerValue; // 5-bit
EGprsTimerValueUnit3 unit;
IEGprsTimer3();
......
......@@ -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<int>(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();
......
......@@ -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);
......
......@@ -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<std::unique_ptr<VQoSFlowParameter>> parameterList;
......@@ -216,7 +216,7 @@ struct VPartialTrackingAreaIdentityList
struct VPduSessionReactivationResultErrorCause
{
int pduSessionId : 4;
int pduSessionId;
EMmCause causeValue;
VPduSessionReactivationResultErrorCause(int pduSessionId, EMmCause causeValue);
......
......@@ -10,18 +10,20 @@
#include <app/cli_base.hpp>
#include <app/cli_cmd.hpp>
#include <app/proc_table.hpp>
#include <app/ue_ctl.hpp>
#include <iostream>
#include <ue/ue.hpp>
#include <unistd.h>
#include <utils/common.hpp>
#include <utils/concurrent_map.hpp>
#include <utils/constants.hpp>
#include <utils/options.hpp>
#include <utils/yaml_utils.hpp>
#include <yaml-cpp/yaml.h>
static app::CliServer *g_cliServer = nullptr;
nr::ue::UeConfig *g_refConfig = nullptr;
static std::unordered_map<std::string, nr::ue::UserEquipment *> g_ueMap{};
static nr::ue::UeConfig *g_refConfig = nullptr;
static ConcurrentMap<std::string, nr::ue::UserEquipment *> 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<NwUeControllerCmd *>(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 <config-file> [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();
......
......@@ -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<Json> 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;
}
}
......
......@@ -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
......@@ -14,10 +14,13 @@
#include <utils/common.hpp>
#include <utils/constants.hpp>
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<NwUeNasToApp *>(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<NwUeStatusUpdate *>(msg));
break;
}
case NtsMessageType::UE_CLI_COMMAND: {
auto *w = dynamic_cast<NwUeCliCommand *>(msg);
UeCmdHandler::HandleCmd(*m_base, *w);
UeCmdHandler handler{m_base};
handler.handleCmd(*w);
break;
}
case NtsMessageType::TIMER_EXPIRED: {
auto *w = dynamic_cast<NwTimerExpired *>(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;
}
}
......
......@@ -27,8 +27,8 @@ class UeAppTask : public NtsTask
TaskBase *m_base;
std::unique_ptr<Logger> m_logger;
UeStatusInfo m_statusInfo;
TunTask *m_tunTasks[16];
std::array<std::optional<UePduSessionInfo>, 16> m_pduSessions{};
std::array<TunTask *, 16> m_tunTasks{};
friend class UeCmdHandler;
......
......@@ -10,13 +10,14 @@
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
#include <ue/nas/task.hpp>
#include <ue/rrc/task.hpp>
#include <utils/common.hpp>
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
......@@ -7,57 +7,139 @@
//
#include "mm.hpp"
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
#include <ue/sm/sm.hpp>
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<nas::DeRegistrationRequestUeOriginating>();
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
......@@ -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
......@@ -25,7 +25,6 @@ class NasMm
{
private:
TaskBase *m_base;
NtsTask *m_nas;
UeTimers *m_timers;
std::unique_ptr<Logger> 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<nas::RegistrationRequest> m_lastRegistrationRequest{};
nas::IE5gsMobileIdentity m_storedSuci{};
nas::IE5gsMobileIdentity m_storedGuti{};
std::unique_ptr<nas::RegistrationRequest> m_lastRegistrationRequest{};
std::unique_ptr<nas::DeRegistrationRequestUeOriginating> m_lastDeregistrationRequest{};
bool m_lastDeregDueToDisable5g{};
std::optional<nas::IE5gsTrackingAreaIdentity> m_lastVisitedRegisteredTai{};
std::optional<nas::IE5gsTrackingAreaIdentityList> m_taiList{};
std::optional<NasSecurityContext> m_currentNsCtx;
std::optional<NasSecurityContext> 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);
......
//
// 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 <nas/utils.hpp>
#include <ue/app/task.hpp>
#include <ue/sm/sm.hpp>
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
......@@ -9,6 +9,7 @@
#include "mm.hpp"
#include <nas/utils.hpp>
#include <ue/nas/task.hpp>
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));
}
}
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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<app::UeCliCommand> cmd;
InetAddress address;
NtsTask *callbackTask;
NwUeCliCommand(std::unique_ptr<app::UeCliCommand> 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<app::UeCliCommand> cmd, InetAddress address)
: NtsMessage(NtsMessageType::UE_CLI_COMMAND), cmd(std::move(cmd)), address(address)
{
callbackTask->push(new app::NwCliSendResponse(address, output, true));
}
};
......
......@@ -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)
......
......@@ -9,6 +9,7 @@
#include "task.hpp"
#include <asn/utils/utils.hpp>
#include <rrc/encode.hpp>
#include <ue/mr/task.hpp>
#include <ue/nas/task.hpp>
#include <ue/nts.hpp>
#include <utils/common.hpp>
......@@ -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
......@@ -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
......@@ -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);
......
//
// 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 <nas/utils.hpp>
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
......@@ -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");
}
......
......@@ -7,82 +7,34 @@
//
#include "sm.hpp"
#include <nas/proto_conf.hpp>
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
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
......@@ -7,8 +7,8 @@
//
#include "sm.hpp"
#include <nas/utils.hpp>
#include <nas/proto_conf.hpp>
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
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<int>(msg.pduSessionId)];
if (pduSession.id == 0)
......
......@@ -8,6 +8,7 @@
#pragma once
#include <array>
#include <nas/nas.hpp>
#include <nas/timer.hpp>
#include <ue/nts.hpp>
......@@ -23,18 +24,17 @@ class NasSm
{
private:
TaskBase *m_base;
NtsTask *m_nas;
UeTimers *m_timers;
std::unique_ptr<Logger> m_logger;
NasMm *m_mm;
PduSession m_pduSessions[16]{};
std::array<PduSession, 16> 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);
......
......@@ -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
......@@ -9,6 +9,8 @@
#pragma once
#include <app/monitor.hpp>
#include <app/ue_ctl.hpp>
#include <array>
#include <nas/nas.hpp>
#include <nas/timer.hpp>
#include <utils/common_types.hpp>
......@@ -24,6 +26,7 @@ class UeAppTask;
class UeMrTask;
class NasTask;
class UeRrcTask;
class UserEquipment;
struct SupportedAlgs
{
......@@ -65,9 +68,9 @@ struct UeConfig
std::vector<SessionConfig> 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<UePduSessionInfo> 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);
......
......@@ -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<app::UeCliCommand> cmd, const InetAddress &address,
NtsTask *callbackTask)
void UserEquipment::pushCommand(std::unique_ptr<app::UeCliCommand> 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
......@@ -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<app::UeCliCommand> cmd, const InetAddress &address, NtsTask *callbackTask);
void pushCommand(std::unique_ptr<app::UeCliCommand> cmd, const InetAddress &address);
};
} // namespace nr::ue
\ No newline at end of file
......@@ -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);
......
......@@ -10,10 +10,10 @@
#include "rls.hpp"
#include <utils/network.hpp>
#include <optional>
#include <set>
#include <unordered_map>
#include <utils/network.hpp>
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);
......
......@@ -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<EPayloadType>(stream.readI());
uint16_t len = stream.read2US();
output.payload = stream.readOctetString(len);
}
if (output.msgType == EMessageType::RLS_SETUP_FAILURE)
output.cause = static_cast<ECause>(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<EPayloadType>(stream.readI());
uint16_t len = stream.read2US();
output.payload = stream.readOctetString(len);
output.cause = static_cast<ECause>(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<int>(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<int>(msg.cause));
if (msg.msgType == EMessageType::RLS_PAYLOAD_TRANSPORT)
{
stream.appendOctet(static_cast<int>(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<int>(msg.msgType));
stream.appendOctet8(msg.ueToken);
stream.appendOctet8(msg.gnbToken);
stream.appendOctet(static_cast<int>(msg.payloadType));
stream.appendOctet2(msg.payload.length());
stream.append(msg.payload);
stream.appendOctet(static_cast<int>(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 "?";
}
......
......@@ -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,
......
......@@ -8,9 +8,9 @@
#include "ue_entity.hpp"
#include <utility>
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utility>
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
......@@ -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:
......
......@@ -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<std::unique_ptr<SliceSupport>> 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<std::unique_ptr<SliceSupport>> 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;
......
//
// 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"
//
// 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 <mutex>
#include <unordered_map>
#pragma once
template <typename TKey, typename TValue>
class ConcurrentMap
{
private:
std::unordered_map<TKey, TValue> 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 <typename Fun>
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
......@@ -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
......
......@@ -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();
......
......@@ -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
......
......@@ -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);
......
......@@ -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);
......
......@@ -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<OptionItem> 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)
......
......@@ -73,12 +73,14 @@ struct OptionsDescription
std::vector<OptionItem> items{};
std::vector<std::string> usages{};
bool helpIfEmpty{};
bool hideDefaultOptionsInUsage{};
OptionsDescription(std::string projectName, std::string version, std::string appDescription, std::string copyright,
std::string programName, std::vector<std::string> usages, bool helpIfEmpty)
std::string programName, std::vector<std::string> 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)
{
}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment