init-version-3.27

This commit is contained in:
lai
2025-08-12 14:15:27 +08:00
parent 85f22d2098
commit a2d7a353d8
4264 changed files with 625612 additions and 1 deletions

11
src/gnb/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,11 @@
file(GLOB_RECURSE HDR_FILES *.hpp)
file(GLOB_RECURSE SRC_FILES *.cpp)
add_library(gnb ${HDR_FILES} ${SRC_FILES})
target_compile_options(gnb PRIVATE -Wall -Wextra -pedantic -Wno-unused-parameter)
target_link_libraries(gnb asn-ngap)
target_link_libraries(gnb asn-rrc)
target_link_libraries(gnb common-lib)

160
src/gnb/app/cmd_handler.cpp Normal file
View File

@@ -0,0 +1,160 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "cmd_handler.hpp"
#include <gnb/app/task.hpp>
#include <gnb/gtp/task.hpp>
#include <gnb/ngap/task.hpp>
#include <gnb/rls/task.hpp>
#include <gnb/rrc/task.hpp>
#include <gnb/sctp/task.hpp>
#include <utils/common.hpp>
#include <utils/printer.hpp>
#define PAUSE_CONFIRM_TIMEOUT 3000
#define PAUSE_POLLING 10
namespace nr::gnb
{
void GnbCmdHandler::sendResult(const InetAddress &address, const std::string &output)
{
m_base->cliCallbackTask->push(std::make_unique<app::NwCliSendResponse>(address, output, false));
}
void GnbCmdHandler::sendError(const InetAddress &address, const std::string &output)
{
m_base->cliCallbackTask->push(std::make_unique<app::NwCliSendResponse>(address, output, true));
}
void GnbCmdHandler::pauseTasks()
{
m_base->gtpTask->requestPause();
m_base->rlsTask->requestPause();
m_base->ngapTask->requestPause();
m_base->rrcTask->requestPause();
m_base->sctpTask->requestPause();
}
void GnbCmdHandler::unpauseTasks()
{
m_base->gtpTask->requestUnpause();
m_base->rlsTask->requestUnpause();
m_base->ngapTask->requestUnpause();
m_base->rrcTask->requestUnpause();
m_base->sctpTask->requestUnpause();
}
bool GnbCmdHandler::isAllPaused()
{
if (!m_base->gtpTask->isPauseConfirmed())
return false;
if (!m_base->rlsTask->isPauseConfirmed())
return false;
if (!m_base->ngapTask->isPauseConfirmed())
return false;
if (!m_base->rrcTask->isPauseConfirmed())
return false;
if (!m_base->sctpTask->isPauseConfirmed())
return false;
return true;
}
void GnbCmdHandler::handleCmd(NmGnbCliCommand &msg)
{
pauseTasks();
uint64_t currentTime = utils::CurrentTimeMillis();
uint64_t endTime = currentTime + PAUSE_CONFIRM_TIMEOUT;
bool isPaused = false;
while (currentTime < endTime)
{
currentTime = utils::CurrentTimeMillis();
if (isAllPaused())
{
isPaused = true;
break;
}
utils::Sleep(PAUSE_POLLING);
}
if (!isPaused)
{
sendError(msg.address, "gNB is unable process command due to pausing timeout");
}
else
{
handleCmdImpl(msg);
}
unpauseTasks();
}
void GnbCmdHandler::handleCmdImpl(NmGnbCliCommand &msg)
{
switch (msg.cmd->present)
{
case app::GnbCliCommand::STATUS: {
sendResult(msg.address, ToJson(m_base->appTask->m_statusInfo).dumpYaml());
break;
}
case app::GnbCliCommand::INFO: {
sendResult(msg.address, ToJson(*m_base->config).dumpYaml());
break;
}
case app::GnbCliCommand::AMF_LIST: {
Json json = Json::Arr({});
for (auto &amf : m_base->ngapTask->m_amfCtx)
json.push(Json::Obj({{"id", amf.first}}));
sendResult(msg.address, json.dumpYaml());
break;
}
case app::GnbCliCommand::AMF_INFO: {
if (m_base->ngapTask->m_amfCtx.count(msg.cmd->amfId) == 0)
sendError(msg.address, "AMF not found with given ID");
else
{
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 : m_base->ngapTask->m_ueCtx)
{
json.push(Json::Obj({
{"ue-id", ue.first},
{"ran-ngap-id", ue.second->ranUeNgapId},
{"amf-ngap-id", ue.second->amfUeNgapId},
}));
}
sendResult(msg.address, json.dumpYaml());
break;
}
case app::GnbCliCommand::UE_COUNT: {
sendResult(msg.address, std::to_string(m_base->ngapTask->m_ueCtx.size()));
break;
}
case app::GnbCliCommand::UE_RELEASE_REQ: {
if (m_base->ngapTask->m_ueCtx.count(msg.cmd->ueId) == 0)
sendError(msg.address, "UE not found with given ID");
else
{
auto ue = m_base->ngapTask->m_ueCtx[msg.cmd->ueId];
m_base->ngapTask->sendContextRelease(ue->ctxId, NgapCause::RadioNetwork_unspecified);
sendResult(msg.address, "Requesting UE context release");
}
break;
}
}
}
} // namespace nr::gnb

42
src/gnb/app/cmd_handler.hpp vendored Normal file
View File

@@ -0,0 +1,42 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <gnb/nts.hpp>
#include <gnb/types.hpp>
namespace nr::gnb
{
class GnbCmdHandler
{
private:
TaskBase *m_base;
public:
explicit GnbCmdHandler(TaskBase *base) : m_base(base)
{
}
void handleCmd(NmGnbCliCommand &msg);
private:
void pauseTasks();
void unpauseTasks();
bool isAllPaused();
private:
void handleCmdImpl(NmGnbCliCommand &msg);
private:
void sendResult(const InetAddress &address, const std::string &output);
void sendError(const InetAddress &address, const std::string &output);
};
} // namespace nr::gnb

60
src/gnb/app/task.cpp Normal file
View File

@@ -0,0 +1,60 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include "cmd_handler.hpp"
#include <gnb/nts.hpp>
namespace nr::gnb
{
GnbAppTask::GnbAppTask(TaskBase *base) : m_base{base}, m_statusInfo{}
{
m_logger = m_base->logBase->makeUniqueLogger("app");
}
void GnbAppTask::onStart()
{
}
void GnbAppTask::onLoop()
{
auto msg = take();
if (!msg)
return;
switch (msg->msgType)
{
case NtsMessageType::GNB_STATUS_UPDATE: {
auto& w = dynamic_cast<NmGnbStatusUpdate &>(*msg);
switch (w.what)
{
case NmGnbStatusUpdate::NGAP_IS_UP:
m_statusInfo.isNgapUp = w.isNgapUp;
break;
}
break;
}
case NtsMessageType::GNB_CLI_COMMAND: {
auto& w = dynamic_cast<NmGnbCliCommand &>(*msg);
GnbCmdHandler handler{m_base};
handler.handleCmd(w);
break;
}
default:
m_logger->unhandledNts(*msg);
break;
}
}
void GnbAppTask::onQuit()
{
}
} // namespace nr::gnb

43
src/gnb/app/task.hpp vendored Normal file
View File

@@ -0,0 +1,43 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <memory>
#include <thread>
#include <unordered_map>
#include <vector>
#include <gnb/types.hpp>
#include <utils/logger.hpp>
#include <utils/nts.hpp>
namespace nr::gnb
{
class GnbAppTask : public NtsTask
{
private:
TaskBase *m_base;
std::unique_ptr<Logger> m_logger;
GnbStatusInfo m_statusInfo;
friend class GnbCmdHandler;
public:
explicit GnbAppTask(TaskBase *base);
~GnbAppTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
};
} // namespace nr::gnb

76
src/gnb/gnb.cpp Normal file
View File

@@ -0,0 +1,76 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "gnb.hpp"
#include "app/task.hpp"
#include "gtp/task.hpp"
#include "ngap/task.hpp"
#include "rls/task.hpp"
#include "rrc/task.hpp"
#include "sctp/task.hpp"
#include <lib/app/cli_base.hpp>
namespace nr::gnb
{
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);
base->ngapTask = new NgapTask(base);
base->rrcTask = new GnbRrcTask(base);
base->gtpTask = new GtpTask(base);
base->rlsTask = new GnbRlsTask(base);
taskBase = base;
}
GNodeB::~GNodeB()
{
taskBase->appTask->quit();
taskBase->sctpTask->quit();
taskBase->ngapTask->quit();
taskBase->rrcTask->quit();
taskBase->gtpTask->quit();
taskBase->rlsTask->quit();
delete taskBase->appTask;
delete taskBase->sctpTask;
delete taskBase->ngapTask;
delete taskBase->rrcTask;
delete taskBase->gtpTask;
delete taskBase->rlsTask;
delete taskBase->logBase;
delete taskBase;
}
void GNodeB::start()
{
taskBase->appTask->start();
taskBase->sctpTask->start();
taskBase->ngapTask->start();
taskBase->rrcTask->start();
taskBase->rlsTask->start();
taskBase->gtpTask->start();
}
void GNodeB::pushCommand(std::unique_ptr<app::GnbCliCommand> cmd, const InetAddress &address)
{
taskBase->appTask->push(std::make_unique<NmGnbCliCommand>(std::move(cmd), address));
}
} // namespace nr::gnb

36
src/gnb/gnb.hpp vendored Normal file
View File

@@ -0,0 +1,36 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include "types.hpp"
#include <lib/app/cli_cmd.hpp>
#include <lib/app/monitor.hpp>
#include <utils/logger.hpp>
#include <utils/network.hpp>
#include <utils/nts.hpp>
namespace nr::gnb
{
class GNodeB
{
private:
TaskBase *taskBase;
public:
GNodeB(GnbConfig *config, app::INodeListener *nodeListener, NtsTask *cliCallbackTask);
virtual ~GNodeB();
public:
void start();
void pushCommand(std::unique_ptr<app::GnbCliCommand> cmd, const InetAddress &address);
};
} // namespace nr::gnb

416
src/gnb/gtp/proto.cpp Normal file
View File

@@ -0,0 +1,416 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "proto.hpp"
namespace gtp
{
static bool EncodeExtensionHeader(const GtpExtHeader &header, OctetString &stream)
{
if (header.type == ExtHeaderType::PduSessionContainerExtHeader)
{
stream.appendOctet(0b10000101);
OctetString inner;
auto &pduSession = ((const PduSessionContainerExtHeader &)header).pduSessionInformation;
if (!PduSessionInformation::Encode(*pduSession, inner))
return false;
stream.appendOctet((inner.length() + 2) / 4);
stream.append(inner);
}
else if (header.type == ExtHeaderType::PdcpPduNumberExtHeader)
{
stream.appendOctet(0b11000000);
stream.appendOctet(1);
stream.appendOctet2(((const PdcpPduNumberExtHeader &)header).pdcpPduNumber);
}
else if (header.type == ExtHeaderType::UdpPortExtHeader)
{
stream.appendOctet(0b01000000);
stream.appendOctet(1);
stream.appendOctet2(((const UdpPortExtHeader &)header).port);
}
else if (header.type == ExtHeaderType::LongPdcpPduNumberExtHeader)
{
int num = ((const LongPdcpPduNumberExtHeader &)header).pdcpPduNumber;
stream.appendOctet(0b10000010);
stream.appendOctet(2);
stream.appendOctet(num >> 16 & 0b11);
stream.appendOctet(num >> 8 & 0xFF);
stream.appendOctet(num & 0xFF);
}
else if (header.type == ExtHeaderType::NrRanContainerExtHeader)
{
// TODO: See 38.425, NrRanContainerExtHeader not implemented yet
return false;
}
return true;
}
bool EncodeGtpMessage(const GtpMessage &gtp, OctetString &stream)
{
int initialLength = stream.length();
bool pn = gtp.nPduNum.has_value();
bool s = gtp.seq.has_value();
bool e = !gtp.extHeaders.empty();
bool any = pn || s || e;
int pt = 1;
int version = 1;
stream.appendOctet(bits::Ranged8({{3, version}, {1, pt}, {1, 0}, {1, e}, {1, s}, {1, pn}}));
stream.appendOctet(gtp.msgType);
stream.appendOctet2(0); // Dummy length for now.
stream.appendOctet4(gtp.teid);
if (any)
{
stream.appendOctet2(!gtp.seq.has_value() ? 0 : gtp.seq.value());
stream.appendOctet(!gtp.nPduNum.has_value() ? 0 : gtp.nPduNum.value());
for (auto &header : gtp.extHeaders)
if (!EncodeExtensionHeader(*header, stream))
return false;
stream.appendOctet(0); // no more extension headers.
}
stream.append(gtp.payload);
// assigning length field
int length = stream.length() - initialLength - 8;
stream.data()[initialLength + 2] = (uint8_t)(length >> 8 & 0xFF);
stream.data()[initialLength + 3] = (uint8_t)(length & 0xFF);
return true; // success
}
static std::unique_ptr<UdpPortExtHeader> DecodeUdpPortExtHeader(int len, const OctetView &stream)
{
if (len != 1)
return nullptr; // length must be 1 for UdpPortExtHeader
auto res = std::make_unique<UdpPortExtHeader>();
res->port = stream.read2US();
return res;
}
static std::unique_ptr<LongPdcpPduNumberExtHeader> DecodeLongPdcpPduNumberExtHeader(int len, const OctetView &stream)
{
if (len != 2)
return nullptr; // length must be 2 for LongPdcpPduNumberExtHeader
int num = stream.readI() & 0b11;
num <<= 8;
num |= stream.readI();
num <<= 8;
num |= stream.readI();
auto res = std::make_unique<LongPdcpPduNumberExtHeader>();
res->pdcpPduNumber = num;
return res;
}
static std::unique_ptr<NrRanContainerExtHeader> DecodeNrRanContainerExtHeader(int len, const OctetView &stream)
{
// obtain actual length in octets. (but not used)
len = 4 * len - 2;
// TODO: See 38.425, NrRanContainerExtHeader not implemented yet
return nullptr;
}
static std::unique_ptr<PduSessionContainerExtHeader> DecodePduSessionContainerExtHeader(int len,
const OctetView &stream)
{
// obtain actual length in octets. (but not used)
len = 4 * len - 2;
auto res = std::make_unique<PduSessionContainerExtHeader>();
res->pduSessionInformation = PduSessionInformation::Decode(stream);
return res;
}
static std::unique_ptr<PdcpPduNumberExtHeader> DecodePdcpPduNumberExtHeader(int len, const OctetView &stream)
{
if (len != 1)
return nullptr; // length must be 1 for PdcpPduNumberExtHeader
auto res = std::make_unique<PdcpPduNumberExtHeader>();
res->pdcpPduNumber = stream.read2US();
return res;
}
std::unique_ptr<GtpMessage> DecodeGtpMessage(const OctetView &stream)
{
auto res = std::make_unique<GtpMessage>();
size_t fistIndex = stream.currentIndex();
uint8_t flags = stream.read();
int version = bits::BitRange8<5, 7>(flags);
if (version != 1)
{
// "GTP-U version not implemented"
return nullptr;
}
int protocolType = bits::BitAt<4>(flags);
if (protocolType != 1)
{
// GTP' not implemented
return nullptr;
}
bool nextExtensionHeaderPresent = bits::BitAt<2>(flags);
bool sequenceNumberPresent = bits::BitAt<1>(flags);
bool nPduNumberPresent = bits::BitAt<0>(flags);
res->msgType = stream.read();
int gtpLen = stream.read2I();
res->teid = stream.read4UI();
if (sequenceNumberPresent || nPduNumberPresent || nextExtensionHeaderPresent)
{
auto seq = stream.read2I();
auto nPduNum = stream.read();
auto nextExtHeaderType = stream.readI();
if (sequenceNumberPresent)
res->seq = seq;
if (nPduNumberPresent)
res->nPduNum = nPduNum;
if (nextExtensionHeaderPresent)
{
while (nextExtHeaderType != 0)
{
int len = stream.readI(); // NOTE: len is actually 4 times length
std::unique_ptr<GtpExtHeader> header = nullptr;
switch (nextExtHeaderType)
{
case 0b01000000:
header = DecodeUdpPortExtHeader(len, stream);
break;
case 0b10000010:
header = DecodeLongPdcpPduNumberExtHeader(len, stream);
break;
case 0b10000100:
header = DecodeNrRanContainerExtHeader(len, stream);
break;
case 0b10000101:
header = DecodePduSessionContainerExtHeader(len, stream);
break;
case 0b11000000:
header = DecodePdcpPduNumberExtHeader(len, stream);
break;
case 0b10000001: // Not used in gNB
case 0b10000011: // Not used in gNB
break;
default:
// GTP next extension header type is invalid
return nullptr;
}
if (header != nullptr)
res->extHeaders.push_back(std::move(header));
nextExtHeaderType = stream.readI();
}
}
}
size_t read = stream.currentIndex() - fistIndex;
size_t rem = gtpLen - (read - 8);
res->payload = stream.readOctetString(rem);
return res;
}
std::unique_ptr<PduSessionInformation> PduSessionInformation::Decode(const OctetView &stream)
{
size_t startIndex = stream.currentIndex();
uint8_t octet = stream.read();
int type = bits::BitRange8<4, 7>(octet);
if (type != 0 && type != 1)
return nullptr;
if (type == 0)
{
auto res = std::make_unique<DlPduSessionInformation>();
auto snp = bits::BitAt<2>(octet);
res->qmp = bits::BitAt<3>(octet);
octet = stream.read();
res->qfi = bits::BitRange8<0, 5>(octet);
res->rqi = bits::BitAt<6>(octet);
auto ppp = bits::BitAt<7>(octet);
if (ppp)
{
octet = stream.read();
res->ppi = bits::BitRange8<5, 7>(octet);
}
if (res->qmp)
res->dlSendingTs = stream.read8L();
if (snp)
res->dlQfiSeq = stream.read3I();
// Consuming padding if any. See 5.5.3.5
size_t read = stream.currentIndex() - startIndex;
if ((read - 2) % 4 != 0)
{
size_t padding = 4 - ((read - 2) % 4);
stream.readOctetString(padding);
}
return res;
}
else
{
auto res = std::make_unique<UlPduSessionInformation>();
auto snp = bits::BitAt<0>(octet);
auto ulDelay = bits::BitAt<1>(octet);
auto dlDelay = bits::BitAt<2>(octet);
res->qmp = bits::BitAt<3>(octet);
octet = stream.read();
res->qfi = bits::BitRange8<5, 7>(octet);
if (res->qmp)
{
res->dlSendingTsRepeated = stream.read8L();
res->dlReceivedTs = stream.read8L();
res->ulSendingTs = stream.read8L();
}
if (dlDelay)
res->dlDelayResult = stream.read4UI();
if (ulDelay)
res->ulDelayResult = stream.read4UI();
if (snp)
res->ulQfiSeq = stream.read3I();
// Consuming padding if any. See 5.5.3.5
size_t read = stream.currentIndex() - startIndex;
if ((read - 2) % 4 != 0)
{
size_t padding = 4 - ((read - 2) % 4);
stream.readOctetString(padding);
}
return res;
}
}
bool PduSessionInformation::Encode(const PduSessionInformation &pdu, OctetString &stream)
{
if (pdu.pduType != 0 && pdu.pduType != 1)
return false;
int initialLength = stream.length();
if (pdu.pduType == 0)
{
auto &dl = dynamic_cast<const DlPduSessionInformation &>(pdu);
auto snp = dl.dlQfiSeq.has_value();
stream.appendOctet(bits::Ranged8({
{4, pdu.pduType},
{1, dl.qmp},
{1, snp},
{2, 0},
}));
stream.appendOctet(bits::Ranged8({
{1, dl.ppi.has_value()},
{1, dl.rqi},
{6, dl.qfi},
}));
if (dl.ppi.has_value())
{
stream.appendOctet(bits::Ranged8({
{3, dl.ppi.value()},
{5, 0},
}));
}
if (dl.qmp)
stream.appendOctet8(dl.dlSendingTs.has_value() ? dl.dlSendingTs.value() : 0L);
if (snp)
stream.appendOctet3(dl.dlQfiSeq.value());
}
else
{
auto &ul = dynamic_cast<const UlPduSessionInformation &>(pdu);
auto snp = ul.ulQfiSeq.has_value();
stream.appendOctet(bits::Ranged8({
{4, pdu.pduType},
{1, ul.qmp},
{1, ul.dlDelayResult.has_value()},
{1, ul.ulDelayResult.has_value()},
{1, snp},
}));
stream.appendOctet(bits::Ranged8({
{2, 0},
{6, ul.qfi},
}));
if (ul.qmp)
{
stream.appendOctet8(!ul.dlSendingTsRepeated.has_value() ? 0 : ul.dlSendingTsRepeated.value());
stream.appendOctet8(!ul.dlReceivedTs.has_value() ? 0 : ul.dlReceivedTs.value());
stream.appendOctet8(!ul.ulSendingTs.has_value() ? 0 : ul.ulSendingTs.value());
}
if (ul.dlDelayResult.has_value())
stream.appendOctet4(ul.dlDelayResult.value());
if (ul.ulDelayResult.has_value())
stream.appendOctet4(ul.ulDelayResult.value());
if (snp)
stream.appendOctet3(ul.ulQfiSeq.value());
}
// Adjusting padding. See 5.5.3.5
int written = stream.length() - initialLength;
if ((written - 2) % 4 != 0)
{
int padding = 4 - ((written - 2) % 4);
stream.appendPadding(padding);
}
return true;
}
} // namespace gtp

159
src/gnb/gtp/proto.hpp vendored Normal file
View File

@@ -0,0 +1,159 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <cstdint>
#include <memory>
#include <optional>
#include <vector>
#include <utils/octet_string.hpp>
#include <utils/octet_view.hpp>
namespace gtp
{
// See 3GPP 38.415
struct PduSessionInformation
{
static constexpr const int PDU_TYPE_DL = 0;
static constexpr const int PDU_TYPE_UL = 1;
const int pduType;
explicit PduSessionInformation(int pduType) : pduType(pduType)
{
}
virtual ~PduSessionInformation() = default;
static std::unique_ptr<PduSessionInformation> Decode(const OctetView &stream);
static bool Encode(const PduSessionInformation &pdu, OctetString &stream);
};
struct DlPduSessionInformation : PduSessionInformation
{
bool qmp{}; // (Mandatory) QoS Monitoring Packet, See 5.5.3.8
int qfi{}; // (Mandatory) 6-bit, QOS Flow Identifier, See 5.5.3.3
bool rqi{}; // (Mandatory) Reflective QOS Indicator, See 5.5.3.4
std::optional<int> ppi{}; // (Optional, may be null) Paging Policy Indicator, See 5.5.3.7
std::optional<int64_t> dlSendingTs{}; // (Optional, may be null) DL Sending Time Stamp, See 5.5.3.9
std::optional<int> dlQfiSeq{}; // (Optional, may be null) 3-octet, DL QFI Sequence Number, See 5.5.3.18
DlPduSessionInformation() : PduSessionInformation(PDU_TYPE_DL)
{
}
~DlPduSessionInformation() override = default;
};
struct UlPduSessionInformation : PduSessionInformation
{
bool qmp{}; // (Mandatory) QoS Monitoring Packet, See 5.5.3.8
int qfi{}; // (Mandatory) 6-bit, QOS Flow Identifier, See 5.5.3.3
std::optional<int64_t>
dlSendingTsRepeated{}; // (Optional, may be null) DL Sending Time Stamp Repeated, See 5.5.3.10
std::optional<int64_t> dlReceivedTs{}; // (Optional, may be null) DL Received Time Stamp, See 5.5.3.11
std::optional<int64_t> ulSendingTs{}; // (Optional, may be null) UL Sending Time Stamp, See 5.5.3.12
std::optional<uint32_t> dlDelayResult{}; // (Optional, may be null) DL Delay Result, See 5.5.3.14
std::optional<uint32_t> ulDelayResult{}; // (Optional, may be null) UL Delay Result, See 5.5.3.16
std::optional<int> ulQfiSeq{}; // (Optional, may be null) 3-octet, UL QFI Sequence Number, See 5.5.3.19
UlPduSessionInformation() : PduSessionInformation(PDU_TYPE_UL)
{
}
~UlPduSessionInformation() override = default;
};
enum class ExtHeaderType
{
LongPdcpPduNumberExtHeader,
NrRanContainerExtHeader,
PdcpPduNumberExtHeader,
PduSessionContainerExtHeader,
UdpPortExtHeader,
};
struct GtpExtHeader
{
const ExtHeaderType type;
explicit GtpExtHeader(ExtHeaderType type) : type(type)
{
}
virtual ~GtpExtHeader() = default;
};
struct LongPdcpPduNumberExtHeader : GtpExtHeader
{
int pdcpPduNumber{}; // 18-bit
LongPdcpPduNumberExtHeader() : GtpExtHeader(ExtHeaderType::LongPdcpPduNumberExtHeader)
{
}
};
struct NrRanContainerExtHeader : GtpExtHeader
{
NrRanContainerExtHeader() : GtpExtHeader(ExtHeaderType::NrRanContainerExtHeader)
{
}
};
struct PdcpPduNumberExtHeader : GtpExtHeader
{
uint16_t pdcpPduNumber{};
PdcpPduNumberExtHeader() : GtpExtHeader(ExtHeaderType::PdcpPduNumberExtHeader)
{
}
};
struct UdpPortExtHeader : GtpExtHeader
{
uint16_t port{};
UdpPortExtHeader() : GtpExtHeader(ExtHeaderType::UdpPortExtHeader)
{
}
};
struct PduSessionContainerExtHeader : GtpExtHeader
{
std::unique_ptr<PduSessionInformation> pduSessionInformation{};
PduSessionContainerExtHeader() : GtpExtHeader(ExtHeaderType::PduSessionContainerExtHeader)
{
}
};
struct GtpMessage
{
// GTP Message Types. (Only GTP-U included)
static constexpr const uint8_t MT_ECHO_REQUEST = 1;
static constexpr const uint8_t MT_ECHO_RESPONSE = 2;
static constexpr const uint8_t MT_ERROR_INDICATION = 26;
static constexpr const uint8_t MT_SUPPORTED_EXT_HEADERS_NOTIFICATION = 31;
static constexpr const uint8_t MT_END_MARKER = 254;
static constexpr const uint8_t MT_G_PDU = 255;
uint8_t msgType;
uint32_t teid;
std::optional<uint16_t> seq;
std::optional<uint8_t> nPduNum;
std::vector<std::unique_ptr<GtpExtHeader>> extHeaders;
OctetString payload;
};
bool EncodeGtpMessage(const GtpMessage &msg, OctetString &stream);
std::unique_ptr<GtpMessage> DecodeGtpMessage(const OctetView &stream);
} // namespace gtp

287
src/gnb/gtp/task.cpp Normal file
View File

@@ -0,0 +1,287 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/gtp/proto.hpp>
#include <gnb/rls/task.hpp>
#include <utils/constants.hpp>
#include <utils/libc_error.hpp>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestItem.h>
namespace nr::gnb
{
GtpTask::GtpTask(TaskBase *base)
: m_base{base}, m_udpServer{}, m_ueContexts{}, m_rateLimiter(std::make_unique<RateLimiter>()), m_pduSessions{},
m_sessionTree{}
{
m_logger = m_base->logBase->makeUniqueLogger("gtp");
}
void GtpTask::onStart()
{
try
{
m_udpServer = new udp::UdpServerTask(m_base->config->gtpIp, cons::GtpPort, this);
m_udpServer->start();
}
catch (const LibError &e)
{
m_logger->err("GTP/UDP task could not be created. %s", e.what());
}
}
void GtpTask::onQuit()
{
m_udpServer->quit();
delete m_udpServer;
m_ueContexts.clear();
}
void GtpTask::onLoop()
{
auto msg = take();
if (!msg)
return;
switch (msg->msgType)
{
case NtsMessageType::GNB_NGAP_TO_GTP: {
auto &w = dynamic_cast<NmGnbNgapToGtp &>(*msg);
switch (w.present)
{
case NmGnbNgapToGtp::UE_CONTEXT_UPDATE: {
handleUeContextUpdate(*w.update);
break;
}
case NmGnbNgapToGtp::UE_CONTEXT_RELEASE: {
handleUeContextDelete(w.ueId);
break;
}
case NmGnbNgapToGtp::SESSION_CREATE: {
handleSessionCreate(w.resource);
break;
}
case NmGnbNgapToGtp::SESSION_RELEASE: {
handleSessionRelease(w.ueId, w.psi);
break;
}
}
break;
}
case NtsMessageType::GNB_RLS_TO_GTP: {
auto &w = dynamic_cast<NmGnbRlsToGtp &>(*msg);
switch (w.present)
{
case NmGnbRlsToGtp::DATA_PDU_DELIVERY: {
handleUplinkData(w.ueId, w.psi, std::move(w.pdu));
break;
}
}
break;
}
case NtsMessageType::UDP_SERVER_RECEIVE:
handleUdpReceive(dynamic_cast<udp::NwUdpServerReceive &>(*msg));
break;
default:
m_logger->unhandledNts(*msg);
break;
}
}
void GtpTask::handleUeContextUpdate(const GtpUeContextUpdate &msg)
{
if (!m_ueContexts.count(msg.ueId))
m_ueContexts[msg.ueId] = std::make_unique<GtpUeContext>(msg.ueId);
auto &ue = m_ueContexts[msg.ueId];
ue->ueAmbr = msg.ueAmbr;
updateAmbrForUe(ue->ueId);
}
void GtpTask::handleSessionCreate(PduSessionResource *session)
{
if (!m_ueContexts.count(session->ueId))
{
m_logger->err("PDU session resource could not be created, UE context with ID[%d] not found", session->ueId);
return;
}
uint64_t sessionInd = MakeSessionResInd(session->ueId, session->psi);
m_pduSessions[sessionInd] = std::unique_ptr<PduSessionResource>(session);
m_sessionTree.insert(sessionInd, session->downTunnel.teid);
updateAmbrForUe(session->ueId);
updateAmbrForSession(sessionInd);
}
void GtpTask::handleSessionRelease(int ueId, int psi)
{
if (!m_ueContexts.count(ueId))
{
m_logger->err("PDU session resource could not be released, UE context with ID[%d] not found", ueId);
return;
}
uint64_t sessionInd = MakeSessionResInd(ueId, psi);
// Remove all session information from rate limiter
m_rateLimiter->updateSessionUplinkLimit(sessionInd, 0);
m_rateLimiter->updateUeDownlinkLimit(ueId, 0);
// And remove from PDU session table
if (m_pduSessions.count(sessionInd))
{
uint32_t teid = m_pduSessions[sessionInd]->downTunnel.teid;
m_pduSessions.erase(sessionInd);
// And remove from the tree
m_sessionTree.remove(sessionInd, teid);
}
}
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(ueId, 0);
// And remove from PDU session table
uint32_t 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();
// ignore non IPv4 packets
if ((data[0] >> 4 & 0xF) != 4)
return;
uint64_t sessionInd = MakeSessionResInd(ueId, psi);
if (!m_pduSessions.count(sessionInd))
{
m_logger->err("Uplink data failure, PDU session not found. UE[%d] PSI[%d]", ueId, psi);
return;
}
auto &pduSession = m_pduSessions[sessionInd];
if (m_rateLimiter->allowUplinkPacket(sessionInd, static_cast<int64_t>(pdu.length())))
{
gtp::GtpMessage gtp{};
gtp.payload = std::move(pdu);
gtp.msgType = gtp::GtpMessage::MT_G_PDU;
gtp.teid = pduSession->upTunnel.teid;
auto ul = std::make_unique<gtp::UlPduSessionInformation>();
// TODO: currently using first QSI
ul->qfi = static_cast<int>(pduSession->qosFlows->list.array[0]->qosFlowIdentifier);
auto cont = std::make_unique<gtp::PduSessionContainerExtHeader>();
cont->pduSessionInformation = std::move(ul);
gtp.extHeaders.push_back(std::move(cont));
OctetString gtpPdu;
if (!gtp::EncodeGtpMessage(gtp, gtpPdu))
m_logger->err("Uplink data failure, GTP encoding failed");
else
m_udpServer->send(InetAddress(pduSession->upTunnel.address, cons::GtpPort), gtpPdu);
}
}
void GtpTask::handleUdpReceive(const udp::NwUdpServerReceive &msg)
{
OctetView buffer{msg.packet};
auto gtp = gtp::DecodeGtpMessage(buffer);
switch (gtp->msgType)
{
case gtp::GtpMessage::MT_G_PDU: {
auto sessionInd = m_sessionTree.findByDownTeid(gtp->teid);
if (sessionInd == 0)
{
m_logger->err("TEID %d not found on GTP-U Downlink", gtp->teid);
return;
}
if (m_rateLimiter->allowDownlinkPacket(sessionInd, gtp->payload.length()))
{
auto w = std::make_unique<NmGnbGtpToRls>(NmGnbGtpToRls::DATA_PDU_DELIVERY);
w->ueId = GetUeId(sessionInd);
w->psi = GetPsi(sessionInd);
w->pdu = std::move(gtp->payload);
m_base->rlsTask->push(std::move(w));
}
return;
}
case gtp::GtpMessage::MT_ECHO_REQUEST: {
gtp::GtpMessage gtpResponse{};
gtpResponse.msgType = gtp::GtpMessage::MT_ECHO_RESPONSE;
gtpResponse.seq = gtp->seq;
gtpResponse.payload = OctetString::FromOctet2({14, 0});
OctetString gtpPdu;
if (gtp::EncodeGtpMessage(gtpResponse, gtpPdu))
m_udpServer->send(msg.fromAddress, gtpPdu);
else
m_logger->err("Uplink data failure, GTP encoding failed");
return;
}
default: {
m_logger->err("Unhandled GTP-U message type: %d", gtp->msgType);
return;
}
}
}
void GtpTask::updateAmbrForUe(int ueId)
{
if (!m_ueContexts.count(ueId))
return;
auto &ue = m_ueContexts[ueId];
m_rateLimiter->updateUeUplinkLimit(ueId, ue->ueAmbr.ulAmbr);
m_rateLimiter->updateUeDownlinkLimit(ueId, ue->ueAmbr.dlAmbr);
}
void GtpTask::updateAmbrForSession(uint64_t pduSession)
{
if (!m_pduSessions.count(pduSession))
return;
auto &sess = m_pduSessions[pduSession];
m_rateLimiter->updateSessionUplinkLimit(pduSession, sess->sessionAmbr.ulAmbr);
m_rateLimiter->updateSessionDownlinkLimit(pduSession, sess->sessionAmbr.dlAmbr);
}
} // namespace nr::gnb

61
src/gnb/gtp/task.hpp vendored Normal file
View File

@@ -0,0 +1,61 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include "utils.hpp"
#include <memory>
#include <thread>
#include <unordered_map>
#include <vector>
#include <gnb/nts.hpp>
#include <lib/udp/server_task.hpp>
#include <utils/logger.hpp>
#include <utils/nts.hpp>
namespace nr::gnb
{
class GtpTask : public NtsTask
{
private:
TaskBase *m_base;
std::unique_ptr<Logger> m_logger;
udp::UdpServerTask *m_udpServer;
std::unordered_map<int, std::unique_ptr<GtpUeContext>> m_ueContexts;
std::unique_ptr<IRateLimiter> m_rateLimiter;
std::unordered_map<uint64_t, std::unique_ptr<PduSessionResource>> m_pduSessions;
PduSessionTree m_sessionTree;
friend class GnbCmdHandler;
public:
explicit GtpTask(TaskBase *base);
~GtpTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
private:
void handleUdpReceive(const udp::NwUdpServerReceive &msg);
void handleUeContextUpdate(const GtpUeContextUpdate &msg);
void handleSessionCreate(PduSessionResource *session);
void handleSessionRelease(int ueId, int psi);
void handleUeContextDelete(int ueId);
void handleUplinkData(int ueId, int psi, OctetString &&data);
void updateAmbrForUe(int ueId);
void updateAmbrForSession(uint64_t pduSession);
};
} // namespace nr::gnb

235
src/gnb/gtp/utils.cpp Normal file
View File

@@ -0,0 +1,235 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "utils.hpp"
#include <utils/common.hpp>
namespace nr::gnb
{
PduSessionTree::PduSessionTree() : mapByDownTeid{}, mapByUeId{}
{
}
void PduSessionTree::insert(uint64_t session, uint32_t downTeid)
{
mapByDownTeid[downTeid] = session;
}
uint64_t PduSessionTree::findByDownTeid(uint32_t teid)
{
if (mapByDownTeid.count(teid))
return mapByDownTeid[teid];
return {};
}
uint64_t PduSessionTree::findBySessionId(int ue, int psi)
{
if (!mapByUeId.count(ue))
return {};
auto &map = mapByUeId[ue];
if (!map.count(psi))
return {};
return map[psi];
}
void PduSessionTree::remove(uint64_t session, uint32_t downTeid)
{
int ueId = GetUeId(session);
int psi = GetPsi(session);
mapByDownTeid.erase(downTeid);
if (mapByUeId.count(ueId))
{
auto &map = mapByUeId[ueId];
if (map.count(psi))
map.erase(psi);
if (map.empty())
mapByUeId.erase(ueId);
}
}
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(int64_t byteCapacity) : byteCapacity(byteCapacity)
{
if (byteCapacity > 0)
{
this->refillTokensPerOneMillis = (double)byteCapacity / (double)REFILL_PERIOD;
this->availableTokens = static_cast<double>(byteCapacity);
this->lastRefillTimestamp = utils::CurrentTimeMillis();
}
}
bool TokenBucket::tryConsume(uint64_t numberOfTokens)
{
if (byteCapacity > 0)
{
refill();
if (availableTokens < static_cast<double>(numberOfTokens))
return false;
else
{
availableTokens -= static_cast<double>(numberOfTokens);
return true;
}
}
else
return true;
}
void TokenBucket::updateCapacity(uint64_t newByteCapacity)
{
byteCapacity = newByteCapacity;
if (newByteCapacity > 0)
refillTokensPerOneMillis = (double)byteCapacity / (double)REFILL_PERIOD;
}
void TokenBucket::refill()
{
int64_t currentTimeMillis = utils::CurrentTimeMillis();
if (currentTimeMillis > lastRefillTimestamp)
{
int64_t millisSinceLastRefill = currentTimeMillis - lastRefillTimestamp;
double refill = static_cast<double>(millisSinceLastRefill) * refillTokensPerOneMillis;
availableTokens = std::min(static_cast<double>(byteCapacity), availableTokens + refill);
lastRefillTimestamp = currentTimeMillis;
}
}
bool RateLimiter::allowDownlinkPacket(uint64_t pduSession, uint64_t packetSize)
{
int ueId = GetUeId(pduSession);
if (downlinkByUe.count(ueId))
{
auto &bucket = downlinkByUe[ueId];
if (!bucket->tryConsume(packetSize))
return false;
}
if (downlinkBySession.count(pduSession))
{
auto &bucket = downlinkBySession[pduSession];
if (!bucket->tryConsume(packetSize))
return false;
}
return true;
}
bool RateLimiter::allowUplinkPacket(uint64_t pduSession, uint64_t packetSize)
{
int ueId = GetUeId(pduSession);
if (uplinkByUe.count(ueId))
{
auto &bucket = uplinkByUe[ueId];
if (!bucket->tryConsume(packetSize))
return false;
}
if (uplinkBySession.count(pduSession))
{
auto &bucket = uplinkBySession[pduSession];
if (!bucket->tryConsume(packetSize))
return false;
}
return true;
}
void RateLimiter::updateUeUplinkLimit(int ueId, uint64_t limit)
{
if (limit <= 0)
{
uplinkByUe.erase(ueId);
return;
}
if (uplinkByUe.count(ueId))
{
auto &bucket = uplinkByUe[ueId];
bucket->updateCapacity(limit);
}
else
{
uplinkByUe[ueId] = std::make_unique<TokenBucket>(limit);
}
}
void RateLimiter::updateUeDownlinkLimit(int ueId, uint64_t limit)
{
if (limit <= 0)
{
downlinkByUe.erase(ueId);
return;
}
if (downlinkByUe.count(ueId))
{
auto &bucket = downlinkByUe[ueId];
bucket->updateCapacity(limit);
}
else
{
downlinkByUe[ueId] = std::make_unique<TokenBucket>(limit);
}
}
void RateLimiter::updateSessionUplinkLimit(uint64_t pduSession, uint64_t limit)
{
if (limit <= 0)
{
uplinkBySession.erase(pduSession);
return;
}
if (uplinkBySession.count(pduSession))
{
auto &bucket = uplinkBySession[pduSession];
bucket->updateCapacity(limit);
}
else
{
uplinkBySession[pduSession] = std::make_unique<TokenBucket>(limit);
}
}
void RateLimiter::updateSessionDownlinkLimit(uint64_t pduSession, uint64_t limit)
{
if (limit <= 0)
{
downlinkBySession.erase(pduSession);
return;
}
if (downlinkBySession.count(pduSession))
{
auto &bucket = downlinkBySession[pduSession];
bucket->updateCapacity(limit);
}
else
{
downlinkBySession[pduSession] = std::make_unique<TokenBucket>(limit);
}
}
} // namespace nr::gnb

95
src/gnb/gtp/utils.hpp vendored Normal file
View File

@@ -0,0 +1,95 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <memory>
#include <unordered_map>
#include <vector>
#include <gnb/types.hpp>
namespace nr::gnb
{
inline uint64_t MakeSessionResInd(int ueId, int psi)
{
return (static_cast<int64_t>(ueId) << 32LL) | static_cast<int64_t>(psi);
}
inline int GetUeId(uint64_t sessionResInd)
{
return static_cast<int>((sessionResInd >> 32uLL) & 0xFFFFFFFFuLL);
}
inline int GetPsi(uint64_t sessionResInd)
{
return static_cast<int>(sessionResInd & 0xFFFFFFFFuLL);
}
class PduSessionTree
{
std::unordered_map<uint32_t, uint64_t> mapByDownTeid;
std::unordered_map<int, std::unordered_map<int, uint64_t>> mapByUeId;
public:
PduSessionTree();
void insert(uint64_t session, uint32_t downTeid);
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
{
static constexpr const int64_t REFILL_PERIOD = 1000L;
uint64_t byteCapacity;
double refillTokensPerOneMillis;
double availableTokens;
int64_t lastRefillTimestamp;
public:
explicit TokenBucket(int64_t byteCapacity);
bool tryConsume(uint64_t numberOfTokens);
void updateCapacity(uint64_t newByteCapacity);
private:
void refill();
};
class IRateLimiter
{
public:
virtual bool allowDownlinkPacket(uint64_t pduSession, uint64_t packetSize) = 0;
virtual bool allowUplinkPacket(uint64_t pduSession, uint64_t packetSize) = 0;
virtual void updateUeUplinkLimit(int ueId, uint64_t limit) = 0;
virtual void updateUeDownlinkLimit(int ueId, uint64_t limit) = 0;
virtual void updateSessionUplinkLimit(uint64_t pduSession, uint64_t limit) = 0;
virtual void updateSessionDownlinkLimit(uint64_t pduSession, uint64_t limit) = 0;
};
class RateLimiter : public IRateLimiter
{
std::unordered_map<int, std::unique_ptr<TokenBucket>> downlinkByUe;
std::unordered_map<int, std::unique_ptr<TokenBucket>> uplinkByUe;
std::unordered_map<uint64_t, std::unique_ptr<TokenBucket>> downlinkBySession;
std::unordered_map<uint64_t, std::unique_ptr<TokenBucket>> uplinkBySession;
public:
bool allowDownlinkPacket(uint64_t pduSession, uint64_t packetSize) override;
bool allowUplinkPacket(uint64_t pduSession, uint64_t packetSize) override;
void updateUeUplinkLimit(int ueId, uint64_t limit) override;
void updateUeDownlinkLimit(int ueId, uint64_t limit) override;
void updateSessionUplinkLimit(uint64_t pduSession, uint64_t limit) override;
void updateSessionDownlinkLimit(uint64_t pduSession, uint64_t limit) override;
};
} // namespace nr::gnb

324
src/gnb/ngap/context.cpp Normal file
View File

@@ -0,0 +1,324 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "encode.hpp"
#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_AssociatedQosFlowItem.h>
#include <asn/ngap/ASN_NGAP_AssociatedQosFlowList.h>
#include <asn/ngap/ASN_NGAP_GTPTunnel.h>
#include <asn/ngap/ASN_NGAP_InitialContextSetupRequest.h>
#include <asn/ngap/ASN_NGAP_InitialContextSetupResponse.h>
#include <asn/ngap/ASN_NGAP_NGAP-PDU.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceFailedToSetupItemCxtRes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceFailedToSetupItemSURes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceItemCxtRelReq.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleaseCommand.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleaseResponse.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleaseResponseTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleasedItemRelRes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupItemCxtReq.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupItemCxtRes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupItemSUReq.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupItemSURes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupListCxtReq.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupRequest.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupRequestTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupResponse.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupResponseTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceToReleaseItemRelCmd.h>
#include <asn/ngap/ASN_NGAP_ProtocolIE-Field.h>
#include <asn/ngap/ASN_NGAP_QosFlowPerTNLInformationItem.h>
#include <asn/ngap/ASN_NGAP_QosFlowPerTNLInformationList.h>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestItem.h>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestList.h>
#include <asn/ngap/ASN_NGAP_SuccessfulOutcome.h>
#include <asn/ngap/ASN_NGAP_UE-NGAP-ID-pair.h>
#include <asn/ngap/ASN_NGAP_UE-NGAP-IDs.h>
#include <asn/ngap/ASN_NGAP_UEAggregateMaximumBitRate.h>
#include <asn/ngap/ASN_NGAP_UEContextModificationRequest.h>
#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>
namespace nr::gnb
{
void NgapTask::receiveInitialContextSetup(int amfId, ASN_NGAP_InitialContextSetupRequest *msg)
{
m_logger->debug("Initial Context Setup Request received");
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPair(msg));
if (ue == nullptr)
return;
auto w = std::make_unique<NmGnbNgapToGtp>(NmGnbNgapToGtp::UE_CONTEXT_UPDATE);
w->update = std::make_unique<GtpUeContextUpdate>(true, ue->ctxId, ue->ueAmbr);
m_base->gtpTask->push(std::move(w));
auto *reqIe = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_UEAggregateMaximumBitRate);
if (reqIe)
{
ue->ueAmbr.dlAmbr = asn::GetUnsigned64(reqIe->UEAggregateMaximumBitRate.uEAggregateMaximumBitRateDL) / 8ull;
ue->ueAmbr.ulAmbr = asn::GetUnsigned64(reqIe->UEAggregateMaximumBitRate.uEAggregateMaximumBitRateUL) / 8ull;
}
std::vector<ASN_NGAP_PDUSessionResourceSetupItemCxtRes *> successList;
std::vector<ASN_NGAP_PDUSessionResourceFailedToSetupItemCxtRes *> failedList;
reqIe = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceSetupListCxtReq);
if (reqIe)
{
auto &list = reqIe->PDUSessionResourceSetupListCxtReq.list;
for (int i = 0; i < list.count; i++)
{
auto &item = list.array[i];
auto *transfer = ngap_encode::Decode<ASN_NGAP_PDUSessionResourceSetupRequestTransfer>(
asn_DEF_ASN_NGAP_PDUSessionResourceSetupRequestTransfer, item->pDUSessionResourceSetupRequestTransfer);
if (transfer == nullptr)
{
m_logger->err(
"Unable to decode a PDU session resource setup request transfer. Ignoring the relevant item");
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupRequestTransfer, transfer);
continue;
}
auto *resource = new PduSessionResource(ue->ctxId, static_cast<int>(item->pDUSessionID));
auto *ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_PDUSessionAggregateMaximumBitRate);
if (ie)
{
resource->sessionAmbr.dlAmbr =
asn::GetUnsigned64(ie->PDUSessionAggregateMaximumBitRate.pDUSessionAggregateMaximumBitRateDL) /
8ull;
resource->sessionAmbr.ulAmbr =
asn::GetUnsigned64(ie->PDUSessionAggregateMaximumBitRate.pDUSessionAggregateMaximumBitRateUL) /
8ull;
}
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_DataForwardingNotPossible);
if (ie)
resource->dataForwardingNotPossible = true;
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_PDUSessionType);
if (ie)
resource->sessionType = ngap_utils::PduSessionTypeFromAsn(ie->PDUSessionType);
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_UL_NGU_UP_TNLInformation);
if (ie)
{
resource->upTunnel.teid =
(uint32_t)asn::GetOctet4(ie->UPTransportLayerInformation.choice.gTPTunnel->gTP_TEID);
resource->upTunnel.address =
asn::GetOctetString(ie->UPTransportLayerInformation.choice.gTPTunnel->transportLayerAddress);
}
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_QosFlowSetupRequestList);
if (ie)
{
auto *ptr = asn::New<ASN_NGAP_QosFlowSetupRequestList>();
asn::DeepCopy(asn_DEF_ASN_NGAP_QosFlowSetupRequestList, ie->QosFlowSetupRequestList, ptr);
resource->qosFlows = asn::WrapUnique(ptr, asn_DEF_ASN_NGAP_QosFlowSetupRequestList);
}
auto error = setupPduSessionResource(ue, resource);
if (error.has_value())
{
auto *tr = asn::New<ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer>();
ngap_utils::ToCauseAsn_Ref(error.value(), tr->cause);
OctetString encodedTr =
ngap_encode::EncodeS(asn_DEF_ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer, tr);
if (encodedTr.length() == 0)
throw std::runtime_error("PDUSessionResourceSetupUnsuccessfulTransfer encoding failed");
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer, tr);
auto *res = asn::New<ASN_NGAP_PDUSessionResourceFailedToSetupItemCxtRes>();
res->pDUSessionID = resource->psi;
asn::SetOctetString(res->pDUSessionResourceSetupUnsuccessfulTransfer, encodedTr);
failedList.push_back(res);
}
else
{
auto *tr = asn::New<ASN_NGAP_PDUSessionResourceSetupResponseTransfer>();
auto &qosList = resource->qosFlows->list;
for (int iQos = 0; iQos < qosList.count; iQos++)
{
auto *associatedQosFlowItem = asn::New<ASN_NGAP_AssociatedQosFlowItem>();
associatedQosFlowItem->qosFlowIdentifier = qosList.array[iQos]->qosFlowIdentifier;
asn::SequenceAdd(tr->dLQosFlowPerTNLInformation.associatedQosFlowList, associatedQosFlowItem);
}
auto &upInfo = tr->dLQosFlowPerTNLInformation.uPTransportLayerInformation;
upInfo.present = ASN_NGAP_UPTransportLayerInformation_PR_gTPTunnel;
upInfo.choice.gTPTunnel = asn::New<ASN_NGAP_GTPTunnel>();
asn::SetBitString(upInfo.choice.gTPTunnel->transportLayerAddress, resource->downTunnel.address);
asn::SetOctetString4(upInfo.choice.gTPTunnel->gTP_TEID, (octet4)resource->downTunnel.teid);
OctetString encodedTr =
ngap_encode::EncodeS(asn_DEF_ASN_NGAP_PDUSessionResourceSetupResponseTransfer, tr);
if (encodedTr.length() == 0)
throw std::runtime_error("PDUSessionResourceSetupResponseTransfer encoding failed");
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupResponseTransfer, tr);
auto *res = asn::New<ASN_NGAP_PDUSessionResourceSetupItemCxtRes>();
res->pDUSessionID = resource->psi;
asn::SetOctetString(res->pDUSessionResourceSetupResponseTransfer, encodedTr);
successList.push_back(res);
}
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupRequestTransfer, transfer);
}
}
reqIe = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_NAS_PDU);
if (reqIe)
deliverDownlinkNas(ue->ctxId, asn::GetOctetString(reqIe->NAS_PDU));
std::vector<ASN_NGAP_InitialContextSetupResponseIEs *> responseIes;
if (!successList.empty())
{
auto *ie = asn::New<ASN_NGAP_InitialContextSetupResponseIEs>();
ie->id = ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceSetupListCxtRes;
ie->criticality = ASN_NGAP_Criticality_ignore;
ie->value.present = ASN_NGAP_InitialContextSetupResponseIEs__value_PR_PDUSessionResourceSetupListCxtRes;
for (auto &item : successList)
asn::SequenceAdd(ie->value.choice.PDUSessionResourceSetupListCxtRes, item);
responseIes.push_back(ie);
}
if (!failedList.empty())
{
auto *ie = asn::New<ASN_NGAP_InitialContextSetupResponseIEs>();
ie->id = ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceFailedToSetupListCxtRes;
ie->criticality = ASN_NGAP_Criticality_ignore;
ie->value.present = ASN_NGAP_InitialContextSetupResponseIEs__value_PR_PDUSessionResourceFailedToSetupListCxtRes;
for (auto &item : failedList)
asn::SequenceAdd(ie->value.choice.PDUSessionResourceFailedToSetupListCxtRes, item);
responseIes.push_back(ie);
}
auto *response = asn::ngap::NewMessagePdu<ASN_NGAP_InitialContextSetupResponse>(responseIes);
sendNgapUeAssociated(ue->ctxId, response);
}
void NgapTask::receiveContextRelease(int amfId, ASN_NGAP_UEContextReleaseCommand *msg)
{
m_logger->debug("UE Context Release Command received");
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPairFromUeNgapIds(msg));
if (ue == nullptr)
return;
// Notify RRC task
auto w1 = std::make_unique<NmGnbNgapToRrc>(NmGnbNgapToRrc::AN_RELEASE);
w1->ueId = ue->ctxId;
m_base->rrcTask->push(std::move(w1));
// Notify GTP task
auto w2 = std::make_unique<NmGnbNgapToGtp>(NmGnbNgapToGtp::UE_CONTEXT_RELEASE);
w2->ueId = ue->ctxId;
m_base->gtpTask->push(std::move(w2));
auto *response = asn::ngap::NewMessagePdu<ASN_NGAP_UEContextReleaseComplete>({});
sendNgapUeAssociated(ue->ctxId, response);
deleteUeContext(ue->ctxId);
}
void NgapTask::receiveContextModification(int amfId, ASN_NGAP_UEContextModificationRequest *msg)
{
m_logger->debug("UE Context Modification Request received");
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPair(msg));
if (ue == nullptr)
return;
auto *ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_UEAggregateMaximumBitRate);
if (ie)
{
ue->ueAmbr.dlAmbr = asn::GetUnsigned64(ie->UEAggregateMaximumBitRate.uEAggregateMaximumBitRateDL);
ue->ueAmbr.ulAmbr = asn::GetUnsigned64(ie->UEAggregateMaximumBitRate.uEAggregateMaximumBitRateUL);
}
ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_NewAMF_UE_NGAP_ID);
if (ie)
{
int64_t old = ue->amfUeNgapId;
ue->amfUeNgapId = asn::GetSigned64(ie->AMF_UE_NGAP_ID_1);
m_logger->debug("AMF-UE-NGAP-ID changed from %ld to %ld", old, ue->amfUeNgapId);
}
auto *response = asn::ngap::NewMessagePdu<ASN_NGAP_UEContextModificationResponse>({});
sendNgapUeAssociated(ue->ctxId, response);
auto w = std::make_unique<NmGnbNgapToGtp>(NmGnbNgapToGtp::UE_CONTEXT_UPDATE);
w->update = std::make_unique<GtpUeContextUpdate>(false, ue->ctxId, ue->ueAmbr);
m_base->gtpTask->push(std::move(w));
}
void NgapTask::sendContextRelease(int ueId, NgapCause cause)
{
m_logger->debug("Sending UE Context release request (NG-RAN node initiated)");
auto *ue = findUeContext(ueId);
if (ue == nullptr)
return;
std::vector<ASN_NGAP_UEContextReleaseRequest_IEs *> ies;
if (!ue->pduSessions.empty())
{
auto *ieSessionList = asn::New<ASN_NGAP_UEContextReleaseRequest_IEs>();
ieSessionList->id = ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceListCxtRelReq;
ieSessionList->criticality = ASN_NGAP_Criticality_reject;
ieSessionList->value.present = ASN_NGAP_UEContextReleaseRequest_IEs__value_PR_PDUSessionResourceListCxtRelReq;
for (int psi : ue->pduSessions)
{
auto *sessionItem = asn::New<ASN_NGAP_PDUSessionResourceItemCxtRelReq>();
sessionItem->pDUSessionID = static_cast<ASN_NGAP_PDUSessionID_t>(psi);
asn::SequenceAdd(ieSessionList->value.choice.PDUSessionResourceListCxtRelReq, sessionItem);
}
ies.push_back(ieSessionList);
}
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);
ies.push_back(ieCause);
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_UEContextReleaseRequest>(ies);
sendNgapUeAssociated(ueId, pdu);
}
} // namespace nr::gnb

14
src/gnb/ngap/encode.cpp Normal file
View File

@@ -0,0 +1,14 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "encode.hpp"
namespace nr::gnb::ngap_encode
{
}

96
src/gnb/ngap/encode.hpp vendored Normal file
View File

@@ -0,0 +1,96 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <asn_application.h>
#include <lib/asn/utils.hpp>
#include <utils/octet_string.hpp>
namespace nr::gnb::ngap_encode
{
template <typename T>
inline std::string EncodeXer(const asn_TYPE_descriptor_t &desc, T *pdu)
{
auto res = asn_encode_to_new_buffer(nullptr, ATS_CANONICAL_XER, &desc, pdu);
if (res.buffer == nullptr || res.result.encoded < 0)
return {};
std::string s{reinterpret_cast<char *>(res.buffer), reinterpret_cast<char *>(res.buffer) + res.result.encoded};
free(res.buffer);
return s;
}
template <typename T>
inline bool Encode(const asn_TYPE_descriptor_t &desc, T *pdu, ssize_t &encoded, uint8_t *&buffer)
{
auto res = asn_encode_to_new_buffer(nullptr, ATS_ALIGNED_CANONICAL_PER, &desc, pdu);
if (res.buffer == nullptr || res.result.encoded < 0)
return false;
encoded = res.result.encoded;
buffer = new uint8_t[encoded];
std::memcpy(buffer, res.buffer, encoded);
free(res.buffer);
return true;
}
template <typename T>
inline OctetString EncodeS(const asn_TYPE_descriptor_t &desc, T *pdu)
{
uint8_t *buffer;
ssize_t encoded;
if (Encode(desc, pdu, encoded, buffer))
{
std::vector<uint8_t> v(encoded);
memcpy(v.data(), buffer, encoded);
delete[] buffer;
return OctetString{std::move(v)};
}
return OctetString{};
}
template <typename T>
inline T *Decode(asn_TYPE_descriptor_t &desc, const uint8_t *buffer, size_t size)
{
auto *pdu = asn::New<T>();
auto res = aper_decode(nullptr, &desc, reinterpret_cast<void **>(&pdu), buffer, size, 0, 0);
if (res.code != RC_OK)
{
asn::Free(desc, pdu);
return nullptr;
}
return pdu;
}
template <typename T>
inline bool DecodeInPlace(asn_TYPE_descriptor_t &desc, uint8_t *buffer, size_t size, T **outPdu)
{
auto res = aper_decode(nullptr, &desc, reinterpret_cast<void **>(outPdu), buffer, size, 0, 0);
return res.code == RC_OK;
}
template <typename T>
inline bool DecodeInPlace(asn_TYPE_descriptor_t &desc, const OCTET_STRING_t &octetString, T **outPdu)
{
return DecodeInPlace(desc, octetString.buf, octetString.size, outPdu);
}
template <typename T>
inline T *Decode(asn_TYPE_descriptor_t &desc, const OCTET_STRING_t &octetString)
{
return Decode<T>(desc, octetString.buf, octetString.size);
}
} // namespace nr::gnb::ngap_encode

362
src/gnb/ngap/interface.cpp Normal file
View File

@@ -0,0 +1,362 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include "utils.hpp"
#include <algorithm>
#include <gnb/app/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>
#include <asn/ngap/ASN_NGAP_AMFName.h>
#include <asn/ngap/ASN_NGAP_BroadcastPLMNItem.h>
#include <asn/ngap/ASN_NGAP_ErrorIndication.h>
#include <asn/ngap/ASN_NGAP_GlobalGNB-ID.h>
#include <asn/ngap/ASN_NGAP_InitiatingMessage.h>
#include <asn/ngap/ASN_NGAP_NGAP-PDU.h>
#include <asn/ngap/ASN_NGAP_NGSetupRequest.h>
#include <asn/ngap/ASN_NGAP_OverloadStartNSSAIItem.h>
#include <asn/ngap/ASN_NGAP_PLMNSupportItem.h>
#include <asn/ngap/ASN_NGAP_ProtocolIE-Field.h>
#include <asn/ngap/ASN_NGAP_ServedGUAMIItem.h>
#include <asn/ngap/ASN_NGAP_SliceSupportItem.h>
#include <asn/ngap/ASN_NGAP_SupportedTAItem.h>
namespace nr::gnb
{
template <typename T>
static void AssignDefaultAmfConfigs(NgapAmfContext *amf, T *msg)
{
auto *ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMFName);
if (ie)
amf->amfName = asn::GetPrintableString(ie->AMFName);
ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_RelativeAMFCapacity);
if (ie)
amf->relativeCapacity = ie->RelativeAMFCapacity;
ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_ServedGUAMIList);
if (ie)
{
utils::ClearAndDelete(amf->servedGuamiList);
asn::ForeachItem(ie->ServedGUAMIList, [amf](ASN_NGAP_ServedGUAMIItem &item) {
auto servedGuami = new ServedGuami();
if (item.backupAMFName)
servedGuami->backupAmfName = asn::GetPrintableString(*item.backupAMFName);
ngap_utils::GuamiFromAsn_Ref(item.gUAMI, servedGuami->guami);
amf->servedGuamiList.push_back(servedGuami);
});
}
ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_PLMNSupportList);
if (ie)
{
utils::ClearAndDelete(amf->plmnSupportList);
asn::ForeachItem(ie->PLMNSupportList, [amf](ASN_NGAP_PLMNSupportItem &item) {
auto plmnSupport = new PlmnSupport();
ngap_utils::PlmnFromAsn_Ref(item.pLMNIdentity, plmnSupport->plmn);
asn::ForeachItem(item.sliceSupportList, [plmnSupport](ASN_NGAP_SliceSupportItem &ssItem) {
plmnSupport->sliceSupportList.slices.push_back(ngap_utils::SliceSupportFromAsn(ssItem));
});
amf->plmnSupportList.push_back(plmnSupport);
});
}
}
void NgapTask::handleAssociationSetup(int amfId, int ascId, int inCount, int outCount)
{
auto *amf = findAmfContext(amfId);
if (amf != nullptr)
{
amf->association.associationId = amfId;
amf->association.inStreams = inCount;
amf->association.outStreams = outCount;
sendNgSetupRequest(amf->ctxId);
}
}
void NgapTask::handleAssociationShutdown(int amfId)
{
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
return;
m_logger->err("Association terminated for AMF[%d]", amfId);
m_logger->debug("Removing AMF context[%d]", amfId);
amf->state = EAmfState::NOT_CONNECTED;
auto w = std::make_unique<NmGnbSctp>(NmGnbSctp::CONNECTION_CLOSE);
w->clientId = amfId;
m_base->sctpTask->push(std::move(w));
deleteAmfContext(amfId);
}
void NgapTask::sendNgSetupRequest(int amfId)
{
m_logger->debug("Sending NG Setup Request");
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
return;
amf->state = EAmfState::WAITING_NG_SETUP;
// TODO: this procedure also re-initialises the NGAP UE-related contexts (if any)
// and erases all related signalling connections in the two nodes like an NG Reset procedure would do.
// More on 38.413 8.7.1.1
auto *globalGnbId = asn::New<ASN_NGAP_GlobalGNB_ID>();
globalGnbId->gNB_ID.present = ASN_NGAP_GNB_ID_PR_gNB_ID;
auto gnbIdLength = m_base->config->gnbIdLength;
auto bitsToShift = 32 - gnbIdLength;
asn::SetBitString(globalGnbId->gNB_ID.choice.gNB_ID, octet4{m_base->config->getGnbId() << bitsToShift},
static_cast<size_t>(gnbIdLength));
asn::SetOctetString3(globalGnbId->pLMNIdentity, ngap_utils::PlmnToOctet3(m_base->config->plmn));
auto *ieGlobalGnbId = asn::New<ASN_NGAP_NGSetupRequestIEs>();
ieGlobalGnbId->id = ASN_NGAP_ProtocolIE_ID_id_GlobalRANNodeID;
ieGlobalGnbId->criticality = ASN_NGAP_Criticality_reject;
ieGlobalGnbId->value.present = ASN_NGAP_NGSetupRequestIEs__value_PR_GlobalRANNodeID;
ieGlobalGnbId->value.choice.GlobalRANNodeID.present = ASN_NGAP_GlobalRANNodeID_PR_globalGNB_ID;
ieGlobalGnbId->value.choice.GlobalRANNodeID.choice.globalGNB_ID = globalGnbId;
auto *ieRanNodeName = asn::New<ASN_NGAP_NGSetupRequestIEs>();
ieRanNodeName->id = ASN_NGAP_ProtocolIE_ID_id_RANNodeName;
ieRanNodeName->criticality = ASN_NGAP_Criticality_ignore;
ieRanNodeName->value.present = ASN_NGAP_NGSetupRequestIEs__value_PR_RANNodeName;
asn::SetPrintableString(ieRanNodeName->value.choice.RANNodeName, m_base->config->name);
auto *broadcastPlmn = asn::New<ASN_NGAP_BroadcastPLMNItem>();
asn::SetOctetString3(broadcastPlmn->pLMNIdentity, ngap_utils::PlmnToOctet3(m_base->config->plmn));
for (auto &nssai : m_base->config->nssai.slices)
{
auto *item = asn::New<ASN_NGAP_SliceSupportItem>();
asn::SetOctetString1(item->s_NSSAI.sST, static_cast<uint8_t>(nssai.sst));
if (nssai.sd.has_value())
{
item->s_NSSAI.sD = asn::New<ASN_NGAP_SD_t>();
asn::SetOctetString3(*item->s_NSSAI.sD, octet3{nssai.sd.value()});
}
asn::SequenceAdd(broadcastPlmn->tAISliceSupportList, item);
}
auto *supportedTa = asn::New<ASN_NGAP_SupportedTAItem>();
asn::SetOctetString3(supportedTa->tAC, octet3{m_base->config->tac});
asn::SequenceAdd(supportedTa->broadcastPLMNList, broadcastPlmn);
auto *ieSupportedTaList = asn::New<ASN_NGAP_NGSetupRequestIEs>();
ieSupportedTaList->id = ASN_NGAP_ProtocolIE_ID_id_SupportedTAList;
ieSupportedTaList->criticality = ASN_NGAP_Criticality_reject;
ieSupportedTaList->value.present = ASN_NGAP_NGSetupRequestIEs__value_PR_SupportedTAList;
asn::SequenceAdd(ieSupportedTaList->value.choice.SupportedTAList, supportedTa);
auto *iePagingDrx = asn::New<ASN_NGAP_NGSetupRequestIEs>();
iePagingDrx->id = ASN_NGAP_ProtocolIE_ID_id_DefaultPagingDRX;
iePagingDrx->criticality = ASN_NGAP_Criticality_ignore;
iePagingDrx->value.present = ASN_NGAP_NGSetupRequestIEs__value_PR_PagingDRX;
iePagingDrx->value.choice.PagingDRX = ngap_utils::PagingDrxToAsn(m_base->config->pagingDrx);
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_NGSetupRequest>(
{ieGlobalGnbId, ieRanNodeName, ieSupportedTaList, iePagingDrx});
sendNgapNonUe(amfId, pdu);
}
void NgapTask::receiveNgSetupResponse(int amfId, ASN_NGAP_NGSetupResponse *msg)
{
m_logger->debug("NG Setup Response received");
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
return;
AssignDefaultAmfConfigs(amf, msg);
amf->state = EAmfState::CONNECTED;
m_logger->info("NG Setup procedure is successful");
if (!m_isInitialized && std::all_of(m_amfCtx.begin(), m_amfCtx.end(),
[](auto &amfCtx) { return amfCtx.second->state == EAmfState::CONNECTED; }))
{
m_isInitialized = true;
auto update = std::make_unique<NmGnbStatusUpdate>(NmGnbStatusUpdate::NGAP_IS_UP);
update->isNgapUp = true;
m_base->appTask->push(std::move(update));
m_base->rrcTask->push(std::make_unique<NmGnbNgapToRrc>(NmGnbNgapToRrc::RADIO_POWER_ON));
}
}
void NgapTask::receiveNgSetupFailure(int amfId, ASN_NGAP_NGSetupFailure *msg)
{
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
return;
amf->state = EAmfState::WAITING_NG_SETUP;
auto *ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_Cause);
if (ie)
m_logger->err("NG Setup procedure is failed. Cause: %s", ngap_utils::CauseToString(ie->Cause).c_str());
else
m_logger->err("NG Setup procedure is failed.");
}
void NgapTask::receiveErrorIndication(int amfId, ASN_NGAP_ErrorIndication *msg)
{
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
{
m_logger->err("Error indication received with not found AMF context");
return;
}
auto *ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_Cause);
if (ie)
m_logger->err("Error indication received. Cause: %s", ngap_utils::CauseToString(ie->Cause).c_str());
else
m_logger->err("Error indication received.");
}
void NgapTask::sendErrorIndication(int amfId, NgapCause cause, int ueId)
{
auto ieCause = asn::New<ASN_NGAP_ErrorIndicationIEs>();
ieCause->id = ASN_NGAP_ProtocolIE_ID_id_Cause;
ieCause->criticality = ASN_NGAP_Criticality_ignore;
ieCause->value.present = ASN_NGAP_ErrorIndicationIEs__value_PR_Cause;
ngap_utils::ToCauseAsn_Ref(cause, ieCause->value.choice.Cause);
m_logger->warn("Sending an error indication with cause: %s",
ngap_utils::CauseToString(ieCause->value.choice.Cause).c_str());
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_ErrorIndication>({ieCause});
if (ueId > 0)
sendNgapUeAssociated(ueId, pdu);
else
sendNgapNonUe(amfId, pdu);
}
void NgapTask::receiveAmfConfigurationUpdate(int amfId, ASN_NGAP_AMFConfigurationUpdate *msg)
{
m_logger->debug("AMF configuration update received");
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
return;
bool tnlModified = false;
auto *ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMF_TNLAssociationToAddList);
if (ie && ie->AMF_TNLAssociationToAddList.list.count > 0)
tnlModified = true;
ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMF_TNLAssociationToRemoveList);
if (ie && ie->AMF_TNLAssociationToRemoveList.list.count > 0)
tnlModified = true;
ie = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMF_TNLAssociationToUpdateList);
if (ie && ie->AMF_TNLAssociationToUpdateList.list.count > 0)
tnlModified = true;
// TODO: AMF TNL modification is not supported
if (tnlModified)
{
m_logger->err("TNL modification is not supported, rejecting AMF configuration update");
auto *ieCause = asn::New<ASN_NGAP_AMFConfigurationUpdateFailureIEs>();
ieCause->id = ASN_NGAP_ProtocolIE_ID_id_Cause;
ieCause->criticality = ASN_NGAP_Criticality_ignore;
ieCause->value.present = ASN_NGAP_AMFConfigurationUpdateFailureIEs__value_PR_Cause;
ngap_utils::ToCauseAsn_Ref(NgapCause::Transport_unspecified, ieCause->value.choice.Cause);
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_AMFConfigurationUpdateFailure>({ieCause});
sendNgapNonUe(amfId, pdu);
}
else
{
AssignDefaultAmfConfigs(amf, msg);
auto *ieList = asn::New<ASN_NGAP_AMFConfigurationUpdateAcknowledgeIEs>();
ieList->id = ASN_NGAP_ProtocolIE_ID_id_AMF_TNLAssociationSetupList;
ieList->criticality = ASN_NGAP_Criticality_ignore;
ieList->value.present = ASN_NGAP_AMFConfigurationUpdateAcknowledgeIEs__value_PR_AMF_TNLAssociationSetupList;
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_AMFConfigurationUpdateAcknowledge>({ieList});
sendNgapNonUe(amfId, pdu);
}
}
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

157
src/gnb/ngap/management.cpp Normal file
View File

@@ -0,0 +1,157 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <utils/common.hpp>
namespace nr::gnb
{
NgapAmfContext *NgapTask::findAmfContext(int ctxId)
{
NgapAmfContext *ctx = nullptr;
if (m_amfCtx.count(ctxId))
ctx = m_amfCtx[ctxId];
if (ctx == nullptr)
m_logger->err("AMF context not found with id: %d", ctxId);
return ctx;
}
void NgapTask::createAmfContext(const GnbAmfConfig &conf)
{
auto *ctx = new NgapAmfContext();
ctx->ctxId = utils::NextId();
ctx->state = EAmfState::NOT_CONNECTED;
ctx->address = conf.address;
ctx->port = conf.port;
m_amfCtx[ctx->ctxId] = ctx;
}
void NgapTask::createUeContext(int ueId, int32_t &requestedSliceType)
{
auto *ctx = new NgapUeContext(ueId);
ctx->amfUeNgapId = -1;
ctx->ranUeNgapId = ++m_ueNgapIdCounter;
m_ueCtx[ctx->ctxId] = ctx;
// Perform AMF selection
auto *amf = selectAmf(ueId, requestedSliceType);
if (amf == nullptr)
m_logger->err("AMF selection for UE[%d] failed. Could not find a suitable AMF.", ueId);
else
ctx->associatedAmfId = amf->ctxId;
}
NgapUeContext *NgapTask::findUeContext(int ctxId)
{
NgapUeContext *ctx = nullptr;
if (m_ueCtx.count(ctxId))
ctx = m_ueCtx[ctxId];
if (ctx == nullptr)
m_logger->err("UE context not found with id: %d", ctxId);
return ctx;
}
NgapUeContext *NgapTask::findUeByRanId(int64_t ranUeNgapId)
{
if (ranUeNgapId <= 0)
return nullptr;
// TODO: optimize
for (auto &ue : m_ueCtx)
if (ue.second->ranUeNgapId == ranUeNgapId)
return ue.second;
return nullptr;
}
NgapUeContext *NgapTask::findUeByAmfId(int64_t amfUeNgapId)
{
if (amfUeNgapId <= 0)
return nullptr;
// TODO: optimize
for (auto &ue : m_ueCtx)
if (ue.second->amfUeNgapId == amfUeNgapId)
return ue.second;
return nullptr;
}
NgapUeContext *NgapTask::findUeByNgapIdPair(int amfCtxId, const NgapIdPair &idPair)
{
auto &amfId = idPair.amfUeNgapId;
auto &ranId = idPair.ranUeNgapId;
if (!amfId.has_value() && !ranId.has_value())
{
sendErrorIndication(amfCtxId, NgapCause::Protocol_abstract_syntax_error_falsely_constructed_message);
return nullptr;
}
if (!amfId.has_value())
{
auto ue = findUeByRanId(ranId.value());
if (ue == nullptr)
{
sendErrorIndication(amfCtxId, NgapCause::RadioNetwork_unknown_local_UE_NGAP_ID);
return nullptr;
}
return ue;
}
if (!ranId.has_value())
{
auto ue = findUeByAmfId(amfId.value());
if (ue == nullptr)
{
sendErrorIndication(amfCtxId, NgapCause::RadioNetwork_inconsistent_remote_UE_NGAP_ID);
return nullptr;
}
return ue;
}
auto ue = findUeByRanId(ranId.value());
if (ue == nullptr)
{
sendErrorIndication(amfCtxId, NgapCause::RadioNetwork_unknown_local_UE_NGAP_ID);
return nullptr;
}
if (ue->amfUeNgapId == -1)
ue->amfUeNgapId = amfId.value();
else if (ue->amfUeNgapId != amfId.value())
{
sendErrorIndication(amfCtxId, NgapCause::RadioNetwork_inconsistent_remote_UE_NGAP_ID);
return nullptr;
}
return ue;
}
void NgapTask::deleteUeContext(int ueId)
{
auto *ue = m_ueCtx[ueId];
if (ue)
{
delete ue;
m_ueCtx.erase(ueId);
}
}
void NgapTask::deleteAmfContext(int amfId)
{
auto *amf = m_amfCtx[amfId];
if (amf)
{
delete amf;
m_amfCtx.erase(amfId);
}
}
} // namespace nr::gnb

248
src/gnb/ngap/nas.cpp Normal file
View File

@@ -0,0 +1,248 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "encode.hpp"
#include "task.hpp"
#include "utils.hpp"
#include <gnb/rrc/task.hpp>
#include <asn/ngap/ASN_NGAP_DownlinkNASTransport.h>
#include <asn/ngap/ASN_NGAP_InitialUEMessage.h>
#include <asn/ngap/ASN_NGAP_InitiatingMessage.h>
#include <asn/ngap/ASN_NGAP_NASNonDeliveryIndication.h>
#include <asn/ngap/ASN_NGAP_NGAP-PDU.h>
#include <asn/ngap/ASN_NGAP_ProtocolIE-Field.h>
#include <asn/ngap/ASN_NGAP_RerouteNASRequest.h>
#include <asn/ngap/ASN_NGAP_UplinkNASTransport.h>
#include <ue/nas/enc.hpp>
#include "encode.hpp"
#include <stdexcept>
namespace nr::gnb
{
int32_t extractSliceInfoAndModifyPdu(OctetString &nasPdu) {
nas::RegistrationRequest *regRequest = nullptr;
int32_t requestedSliceType = -1;
const uint8_t *m_data = nasPdu.data();
size_t m_dataLength = nasPdu.length();
OctetView octetView(m_data, m_dataLength);
auto nasMessage = nas::DecodeNasMessage(octetView);
if (nasMessage->epd == nas::EExtendedProtocolDiscriminator::MOBILITY_MANAGEMENT_MESSAGES)
{
nas::MmMessage *mmMessage = dynamic_cast<nas::MmMessage *>(nasMessage.get());
if (mmMessage)
{
nas::PlainMmMessage *plainMmMessage = dynamic_cast<nas::PlainMmMessage *>(mmMessage);
if (plainMmMessage)
{
regRequest = dynamic_cast<nas::RegistrationRequest *>(plainMmMessage);
if (regRequest)
{
auto sz = regRequest->requestedNSSAI->sNssais.size();
if (sz > 0) {
requestedSliceType = static_cast<uint8_t>(regRequest->requestedNSSAI->sNssais[0].sst);
}
}
}
}
}
if (regRequest && regRequest->requestedNSSAI)
regRequest->requestedNSSAI = std::nullopt;
OctetString modifiedNasPdu;
nas::EncodeNasMessage(*nasMessage, modifiedNasPdu);
nasPdu = std::move(modifiedNasPdu);
return requestedSliceType;
}
void NgapTask::handleInitialNasTransport(int ueId, OctetString &nasPdu, int64_t rrcEstablishmentCause,
const std::optional<GutiMobileIdentity> &sTmsi)
{
int32_t requestedSliceType = extractSliceInfoAndModifyPdu(nasPdu);
m_logger->debug("Initial NAS message received from UE[%d]", ueId);
if (m_ueCtx.count(ueId))
{
m_logger->err("UE context[%d] already exists", ueId);
return;
}
createUeContext(ueId, requestedSliceType);
auto *ueCtx = findUeContext(ueId);
if (ueCtx == nullptr)
return;
auto *amfCtx = findAmfContext(ueCtx->associatedAmfId);
if (amfCtx == nullptr)
return;
if (amfCtx->state != EAmfState::CONNECTED)
{
m_logger->err("Initial NAS transport failure. AMF is not in connected state.");
return;
}
amfCtx->nextStream = (amfCtx->nextStream + 1) % amfCtx->association.outStreams;
if ((amfCtx->nextStream == 0) && (amfCtx->association.outStreams > 1))
amfCtx->nextStream += 1;
ueCtx->uplinkStream = amfCtx->nextStream;
std::vector<ASN_NGAP_InitialUEMessage_IEs *> ies;
auto *ieEstablishmentCause = asn::New<ASN_NGAP_InitialUEMessage_IEs>();
ieEstablishmentCause->id = ASN_NGAP_ProtocolIE_ID_id_RRCEstablishmentCause;
ieEstablishmentCause->criticality = ASN_NGAP_Criticality_ignore;
ieEstablishmentCause->value.present = ASN_NGAP_InitialUEMessage_IEs__value_PR_RRCEstablishmentCause;
ieEstablishmentCause->value.choice.RRCEstablishmentCause = rrcEstablishmentCause;
ies.push_back(ieEstablishmentCause);
auto *ieCtxRequest = asn::New<ASN_NGAP_InitialUEMessage_IEs>();
ieCtxRequest->id = ASN_NGAP_ProtocolIE_ID_id_UEContextRequest;
ieCtxRequest->criticality = ASN_NGAP_Criticality_ignore;
ieCtxRequest->value.present = ASN_NGAP_InitialUEMessage_IEs__value_PR_UEContextRequest;
ieCtxRequest->value.choice.UEContextRequest = ASN_NGAP_UEContextRequest_requested;
ies.push_back(ieCtxRequest);
auto *ieNasPdu = asn::New<ASN_NGAP_InitialUEMessage_IEs>();
ieNasPdu->id = ASN_NGAP_ProtocolIE_ID_id_NAS_PDU;
ieNasPdu->criticality = ASN_NGAP_Criticality_reject;
ieNasPdu->value.present = ASN_NGAP_InitialUEMessage_IEs__value_PR_NAS_PDU;
asn::SetOctetString(ieNasPdu->value.choice.NAS_PDU, nasPdu);
ies.push_back(ieNasPdu);
if (sTmsi)
{
auto *ieTmsi = asn::New<ASN_NGAP_InitialUEMessage_IEs>();
ieTmsi->id = ASN_NGAP_ProtocolIE_ID_id_FiveG_S_TMSI;
ieTmsi->criticality = ASN_NGAP_Criticality_reject;
ieTmsi->value.present = ASN_NGAP_InitialUEMessage_IEs__value_PR_FiveG_S_TMSI;
asn::SetBitStringInt<10>(sTmsi->amfSetId, ieTmsi->value.choice.FiveG_S_TMSI.aMFSetID);
asn::SetBitStringInt<6>(sTmsi->amfPointer, ieTmsi->value.choice.FiveG_S_TMSI.aMFPointer);
asn::SetOctetString4(ieTmsi->value.choice.FiveG_S_TMSI.fiveG_TMSI, sTmsi->tmsi);
ies.push_back(ieTmsi);
}
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_InitialUEMessage>(ies);
sendNgapUeAssociated(ueId, pdu);
}
void NgapTask::deliverDownlinkNas(int ueId, OctetString &&nasPdu)
{
auto w = std::make_unique<NmGnbNgapToRrc>(NmGnbNgapToRrc::NAS_DELIVERY);
w->ueId = ueId;
w->pdu = std::move(nasPdu);
m_base->rrcTask->push(std::move(w));
}
void NgapTask::handleUplinkNasTransport(int ueId, const OctetString &nasPdu)
{
auto *ue = findUeContext(ueId);
if (ue == nullptr)
return;
auto *ieNasPdu = asn::New<ASN_NGAP_UplinkNASTransport_IEs>();
ieNasPdu->id = ASN_NGAP_ProtocolIE_ID_id_NAS_PDU;
ieNasPdu->criticality = ASN_NGAP_Criticality_reject;
ieNasPdu->value.present = ASN_NGAP_UplinkNASTransport_IEs__value_PR_NAS_PDU;
asn::SetOctetString(ieNasPdu->value.choice.NAS_PDU, nasPdu);
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_UplinkNASTransport>({ieNasPdu});
sendNgapUeAssociated(ueId, pdu);
}
void NgapTask::sendNasNonDeliveryIndication(int ueId, const OctetString &nasPdu, NgapCause cause)
{
m_logger->debug("Sending non-delivery indication for UE[%d]", ueId);
auto *ieNasPdu = asn::New<ASN_NGAP_NASNonDeliveryIndication_IEs>();
ieNasPdu->id = ASN_NGAP_ProtocolIE_ID_id_NAS_PDU;
ieNasPdu->criticality = ASN_NGAP_Criticality_ignore;
ieNasPdu->value.present = ASN_NGAP_NASNonDeliveryIndication_IEs__value_PR_NAS_PDU;
asn::SetOctetString(ieNasPdu->value.choice.NAS_PDU, nasPdu);
auto *ieCause = asn::New<ASN_NGAP_NASNonDeliveryIndication_IEs>();
ieCause->id = ASN_NGAP_ProtocolIE_ID_id_Cause;
ieCause->criticality = ASN_NGAP_Criticality_ignore;
ieCause->value.present = ASN_NGAP_NASNonDeliveryIndication_IEs__value_PR_Cause;
ngap_utils::ToCauseAsn_Ref(cause, ieCause->value.choice.Cause);
auto *pdu = asn::ngap::NewMessagePdu<ASN_NGAP_NASNonDeliveryIndication>({ieNasPdu, ieCause});
sendNgapUeAssociated(ueId, pdu);
}
void NgapTask::receiveDownlinkNasTransport(int amfId, ASN_NGAP_DownlinkNASTransport *msg)
{
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPair(msg));
if (ue == nullptr)
return;
auto *ieNasPdu = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_NAS_PDU);
if (ieNasPdu)
deliverDownlinkNas(ue->ctxId, asn::GetOctetString(ieNasPdu->NAS_PDU));
}
void NgapTask::receiveRerouteNasRequest(int amfId, ASN_NGAP_RerouteNASRequest *msg)
{
m_logger->debug("Reroute NAS request received");
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPair(msg));
if (ue == nullptr)
return;
auto *ieNgapMessage = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_NGAP_Message);
auto *ieAmfSetId = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMFSetID);
auto *ieAllowedNssai = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AllowedNSSAI);
auto ngapPdu = asn::New<ASN_NGAP_NGAP_PDU>();
ngapPdu->present = ASN_NGAP_NGAP_PDU_PR_initiatingMessage;
ngapPdu->choice.initiatingMessage = asn::New<ASN_NGAP_InitiatingMessage>();
ngapPdu->choice.initiatingMessage->procedureCode = ASN_NGAP_ProcedureCode_id_InitialUEMessage;
ngapPdu->choice.initiatingMessage->criticality = ASN_NGAP_Criticality_ignore;
ngapPdu->choice.initiatingMessage->value.present = ASN_NGAP_InitiatingMessage__value_PR_InitialUEMessage;
auto *initialUeMessage = &ngapPdu->choice.initiatingMessage->value.choice.InitialUEMessage;
if (!ngap_encode::DecodeInPlace(asn_DEF_ASN_NGAP_InitialUEMessage, ieNgapMessage->OCTET_STRING, &initialUeMessage))
{
m_logger->err("APER decoding failed in Reroute NAS Request");
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, ngapPdu);
sendErrorIndication(amfId, NgapCause::Protocol_transfer_syntax_error);
return;
}
if (ieAllowedNssai)
{
auto *oldAllowedNssai = asn::ngap::GetProtocolIe(initialUeMessage, ASN_NGAP_ProtocolIE_ID_id_AllowedNSSAI);
if (oldAllowedNssai)
asn::DeepCopy(asn_DEF_ASN_NGAP_AllowedNSSAI, ieAllowedNssai->AllowedNSSAI, &oldAllowedNssai->AllowedNSSAI);
else
{
auto *newAllowedNssai = asn::New<ASN_NGAP_InitialUEMessage_IEs>();
newAllowedNssai->id = ASN_NGAP_ProtocolIE_ID_id_AllowedNSSAI;
newAllowedNssai->criticality = ASN_NGAP_Criticality_reject;
newAllowedNssai->value.present = ASN_NGAP_InitialUEMessage_IEs__value_PR_AllowedNSSAI;
asn::ngap::AddProtocolIe(*initialUeMessage, newAllowedNssai);
}
}
auto *newAmf = selectNewAmfForReAllocation(ue->ctxId, amfId, asn::GetBitStringInt<10>(ieAmfSetId->AMFSetID));
if (newAmf == nullptr)
{
m_logger->err("AMF selection for re-allocation failed. Could not find a suitable AMF.");
return;
}
sendNgapUeAssociated(ue->ctxId, ngapPdu);
}
} // namespace nr::gnb

35
src/gnb/ngap/nnsf.cpp Normal file
View File

@@ -0,0 +1,35 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
namespace nr::gnb
{
NgapAmfContext *NgapTask::selectAmf(int ueId, int32_t &requestedSliceType)
{
for (auto &amf : m_amfCtx) {
for (const auto &plmnSupport : amf.second->plmnSupportList) {
for (const auto &singleSlice : plmnSupport->sliceSupportList.slices) {
int32_t supportedSliceType = static_cast<int32_t>(singleSlice.sst);
if (supportedSliceType == requestedSliceType) {
return amf.second;
}
}
}
}
return nullptr;
}
NgapAmfContext *NgapTask::selectNewAmfForReAllocation(int ueId, int initiatedAmfId, int amfSetId)
{
// TODO an arbitrary AMF is selected for now
return findAmfContext(initiatedAmfId);
}
} // namespace nr::gnb

59
src/gnb/ngap/radio.cpp Normal file
View File

@@ -0,0 +1,59 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "encode.hpp"
#include "task.hpp"
#include "utils.hpp"
#include <gnb/gtp/task.hpp>
#include <gnb/rrc/task.hpp>
#include <asn/ngap/ASN_NGAP_FiveG-S-TMSI.h>
#include <asn/ngap/ASN_NGAP_Paging.h>
namespace nr::gnb
{
void NgapTask::handleRadioLinkFailure(int ueId)
{
// Notify GTP task
auto w = std::make_unique<NmGnbNgapToGtp>(NmGnbNgapToGtp::UE_CONTEXT_RELEASE);
w->ueId = ueId;
m_base->gtpTask->push(std::move(w));
// Notify AMF
sendContextRelease(ueId, NgapCause::RadioNetwork_radio_connection_with_ue_lost);
}
void NgapTask::receivePaging(int amfId, ASN_NGAP_Paging *msg)
{
m_logger->debug("Paging received");
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
return;
auto *ieUePagingIdentity = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_UEPagingIdentity);
auto *ieTaiListForPaging = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_TAIListForPaging);
if (ieUePagingIdentity == nullptr || ieTaiListForPaging == nullptr ||
ieUePagingIdentity->UEPagingIdentity.present != ASN_NGAP_UEPagingIdentity_PR_fiveG_S_TMSI)
{
m_logger->err("Invalid parameters received in Paging message");
return;
}
auto w = std::make_unique<NmGnbNgapToRrc>(NmGnbNgapToRrc::PAGING);
w->uePagingTmsi =
asn::UniqueCopy(*ieUePagingIdentity->UEPagingIdentity.choice.fiveG_S_TMSI, asn_DEF_ASN_NGAP_FiveG_S_TMSI);
w->taiListForPaging = asn::UniqueCopy(ieTaiListForPaging->TAIListForPaging, asn_DEF_ASN_NGAP_TAIListForPaging);
m_base->rrcTask->push(std::move(w));
}
} // namespace nr::gnb

316
src/gnb/ngap/session.cpp Normal file
View File

@@ -0,0 +1,316 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "encode.hpp"
#include "task.hpp"
#include "utils.hpp"
#include <set>
#include <stdexcept>
#include <gnb/gtp/task.hpp>
#include <asn/ngap/ASN_NGAP_AssociatedQosFlowItem.h>
#include <asn/ngap/ASN_NGAP_AssociatedQosFlowList.h>
#include <asn/ngap/ASN_NGAP_GTPTunnel.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceFailedToSetupItemSURes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleaseCommand.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleaseResponse.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleaseResponseTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceReleasedItemRelRes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupItemSUReq.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupItemSURes.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupRequest.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupRequestTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupResponse.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupResponseTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer.h>
#include <asn/ngap/ASN_NGAP_PDUSessionResourceToReleaseItemRelCmd.h>
#include <asn/ngap/ASN_NGAP_ProtocolIE-Field.h>
#include <asn/ngap/ASN_NGAP_QosFlowPerTNLInformationItem.h>
#include <asn/ngap/ASN_NGAP_QosFlowPerTNLInformationList.h>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestItem.h>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestList.h>
namespace nr::gnb
{
void NgapTask::receiveSessionResourceSetupRequest(int amfId, ASN_NGAP_PDUSessionResourceSetupRequest *msg)
{
std::vector<ASN_NGAP_PDUSessionResourceSetupItemSURes *> successList;
std::vector<ASN_NGAP_PDUSessionResourceFailedToSetupItemSURes *> failedList;
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPair(msg));
if (ue == nullptr)
return;
auto *ieList = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceSetupListSUReq);
if (ieList)
{
auto &list = ieList->PDUSessionResourceSetupListSUReq.list;
for (int i = 0; i < list.count; i++)
{
auto &item = list.array[i];
auto *transfer = ngap_encode::Decode<ASN_NGAP_PDUSessionResourceSetupRequestTransfer>(
asn_DEF_ASN_NGAP_PDUSessionResourceSetupRequestTransfer, item->pDUSessionResourceSetupRequestTransfer);
if (transfer == nullptr)
{
m_logger->err(
"Unable to decode a PDU session resource setup request transfer. Ignoring the relevant item");
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupRequestTransfer, transfer);
continue;
}
auto *resource = new PduSessionResource(ue->ctxId, static_cast<int>(item->pDUSessionID));
auto *ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_PDUSessionAggregateMaximumBitRate);
if (ie)
{
resource->sessionAmbr.dlAmbr =
asn::GetUnsigned64(ie->PDUSessionAggregateMaximumBitRate.pDUSessionAggregateMaximumBitRateDL) /
8ull;
resource->sessionAmbr.ulAmbr =
asn::GetUnsigned64(ie->PDUSessionAggregateMaximumBitRate.pDUSessionAggregateMaximumBitRateUL) /
8ull;
}
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_DataForwardingNotPossible);
if (ie)
resource->dataForwardingNotPossible = true;
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_PDUSessionType);
if (ie)
resource->sessionType = ngap_utils::PduSessionTypeFromAsn(ie->PDUSessionType);
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_UL_NGU_UP_TNLInformation);
if (ie)
{
resource->upTunnel.teid =
(uint32_t)asn::GetOctet4(ie->UPTransportLayerInformation.choice.gTPTunnel->gTP_TEID);
resource->upTunnel.address =
asn::GetOctetString(ie->UPTransportLayerInformation.choice.gTPTunnel->transportLayerAddress);
}
ie = asn::ngap::GetProtocolIe(transfer, ASN_NGAP_ProtocolIE_ID_id_QosFlowSetupRequestList);
if (ie)
{
auto *ptr = asn::New<ASN_NGAP_QosFlowSetupRequestList>();
asn::DeepCopy(asn_DEF_ASN_NGAP_QosFlowSetupRequestList, ie->QosFlowSetupRequestList, ptr);
resource->qosFlows = asn::WrapUnique(ptr, asn_DEF_ASN_NGAP_QosFlowSetupRequestList);
}
auto error = setupPduSessionResource(ue, resource);
if (error.has_value())
{
auto *tr = asn::New<ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer>();
ngap_utils::ToCauseAsn_Ref(error.value(), tr->cause);
OctetString encodedTr =
ngap_encode::EncodeS(asn_DEF_ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer, tr);
if (encodedTr.length() == 0)
throw std::runtime_error("PDUSessionResourceSetupUnsuccessfulTransfer encoding failed");
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupUnsuccessfulTransfer, tr);
auto *res = asn::New<ASN_NGAP_PDUSessionResourceFailedToSetupItemSURes>();
res->pDUSessionID = resource->psi;
asn::SetOctetString(res->pDUSessionResourceSetupUnsuccessfulTransfer, encodedTr);
failedList.push_back(res);
}
else
{
if (item->pDUSessionNAS_PDU)
deliverDownlinkNas(ue->ctxId, asn::GetOctetString(*item->pDUSessionNAS_PDU));
auto *tr = asn::New<ASN_NGAP_PDUSessionResourceSetupResponseTransfer>();
auto &qosList = resource->qosFlows->list;
for (int iQos = 0; iQos < qosList.count; iQos++)
{
auto *associatedQosFlowItem = asn::New<ASN_NGAP_AssociatedQosFlowItem>();
associatedQosFlowItem->qosFlowIdentifier = qosList.array[iQos]->qosFlowIdentifier;
asn::SequenceAdd(tr->dLQosFlowPerTNLInformation.associatedQosFlowList, associatedQosFlowItem);
}
auto &upInfo = tr->dLQosFlowPerTNLInformation.uPTransportLayerInformation;
upInfo.present = ASN_NGAP_UPTransportLayerInformation_PR_gTPTunnel;
upInfo.choice.gTPTunnel = asn::New<ASN_NGAP_GTPTunnel>();
asn::SetBitString(upInfo.choice.gTPTunnel->transportLayerAddress, resource->downTunnel.address);
asn::SetOctetString4(upInfo.choice.gTPTunnel->gTP_TEID, (octet4)resource->downTunnel.teid);
OctetString encodedTr =
ngap_encode::EncodeS(asn_DEF_ASN_NGAP_PDUSessionResourceSetupResponseTransfer, tr);
if (encodedTr.length() == 0)
throw std::runtime_error("PDUSessionResourceSetupResponseTransfer encoding failed");
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupResponseTransfer, tr);
auto *res = asn::New<ASN_NGAP_PDUSessionResourceSetupItemSURes>();
res->pDUSessionID = resource->psi;
asn::SetOctetString(res->pDUSessionResourceSetupResponseTransfer, encodedTr);
successList.push_back(res);
}
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceSetupRequestTransfer, transfer);
}
}
auto *ieNasPdu = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_NAS_PDU);
if (ieNasPdu)
deliverDownlinkNas(ue->ctxId, asn::GetOctetString(ieNasPdu->NAS_PDU));
std::vector<ASN_NGAP_PDUSessionResourceSetupResponseIEs *> responseIes;
if (!successList.empty())
{
auto *ie = asn::New<ASN_NGAP_PDUSessionResourceSetupResponseIEs>();
ie->id = ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceSetupListSURes;
ie->criticality = ASN_NGAP_Criticality_ignore;
ie->value.present = ASN_NGAP_PDUSessionResourceSetupResponseIEs__value_PR_PDUSessionResourceSetupListSURes;
for (auto &item : successList)
asn::SequenceAdd(ie->value.choice.PDUSessionResourceSetupListSURes, item);
responseIes.push_back(ie);
}
if (!failedList.empty())
{
auto *ie = asn::New<ASN_NGAP_PDUSessionResourceSetupResponseIEs>();
ie->id = ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceFailedToSetupListSURes;
ie->criticality = ASN_NGAP_Criticality_ignore;
ie->value.present =
ASN_NGAP_PDUSessionResourceSetupResponseIEs__value_PR_PDUSessionResourceFailedToSetupListSURes;
for (auto &item : failedList)
asn::SequenceAdd(ie->value.choice.PDUSessionResourceFailedToSetupListSURes, item);
responseIes.push_back(ie);
}
auto *respPdu = asn::ngap::NewMessagePdu<ASN_NGAP_PDUSessionResourceSetupResponse>(responseIes);
sendNgapUeAssociated(ue->ctxId, respPdu);
if (failedList.empty())
m_logger->info("PDU session resource(s) setup for UE[%d] count[%d]", ue->ctxId,
static_cast<int>(successList.size()));
else if (successList.empty())
m_logger->err("PDU session resource(s) setup was failed for UE[%d] count[%d]", ue->ctxId,
static_cast<int>(failedList.size()));
else
m_logger->err("PDU session establishment is partially successful for UE[%d], success[%d], failed[%d]",
static_cast<int>(successList.size()), static_cast<int>(failedList.size()));
}
std::optional<NgapCause> NgapTask::setupPduSessionResource(NgapUeContext *ue, PduSessionResource *resource)
{
if (resource->sessionType != PduSessionType::IPv4)
{
m_logger->err("PDU session resource could not setup: Only IPv4 is supported");
return NgapCause::RadioNetwork_unspecified;
}
if (resource->upTunnel.address.length() == 0)
{
m_logger->err("PDU session resource could not setup: Uplink TNL information is missing");
return NgapCause::Protocol_transfer_syntax_error;
}
if (resource->qosFlows == nullptr || resource->qosFlows->list.count == 0)
{
m_logger->err("PDU session resource could not setup: QoS flow list is null or empty");
return NgapCause::Protocol_semantic_error;
}
std::string gtpIp = m_base->config->gtpAdvertiseIp.value_or(m_base->config->gtpIp);
resource->downTunnel.address = utils::IpToOctetString(gtpIp);
resource->downTunnel.teid = ++m_downlinkTeidCounter;
auto w = std::make_unique<NmGnbNgapToGtp>(NmGnbNgapToGtp::SESSION_CREATE);
w->resource = resource;
m_base->gtpTask->push(std::move(w));
ue->pduSessions.insert(resource->psi);
return {};
}
void NgapTask::receiveSessionResourceReleaseCommand(int amfId, ASN_NGAP_PDUSessionResourceReleaseCommand *msg)
{
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPair(msg));
if (ue == nullptr)
return;
std::set<int> psIds{};
auto *ieReq = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceToReleaseListRelCmd);
if (ieReq)
{
auto &list = ieReq->PDUSessionResourceToReleaseListRelCmd.list;
for (int i = 0; i < list.count; i++)
{
auto &item = list.array[i];
if (item)
psIds.insert(static_cast<int>(item->pDUSessionID));
}
}
ieReq = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_NAS_PDU);
if (ieReq)
deliverDownlinkNas(ue->ctxId, asn::GetOctetString(ieReq->NAS_PDU));
auto *ieResp = asn::New<ASN_NGAP_PDUSessionResourceReleaseResponseIEs>();
ieResp->id = ASN_NGAP_ProtocolIE_ID_id_PDUSessionResourceReleasedListRelRes;
ieResp->criticality = ASN_NGAP_Criticality_ignore;
ieResp->value.present =
ASN_NGAP_PDUSessionResourceReleaseResponseIEs__value_PR_PDUSessionResourceReleasedListRelRes;
// Perform release
for (auto &psi : psIds)
{
auto w = std::make_unique<NmGnbNgapToGtp>(NmGnbNgapToGtp::SESSION_RELEASE);
w->ueId = ue->ctxId;
w->psi = psi;
m_base->gtpTask->push(std::move(w));
ue->pduSessions.erase(psi);
}
for (auto &psi : psIds)
{
auto *tr = asn::New<ASN_NGAP_PDUSessionResourceReleaseResponseTransfer>();
OctetString encodedTr = ngap_encode::EncodeS(asn_DEF_ASN_NGAP_PDUSessionResourceReleaseResponseTransfer, tr);
if (encodedTr.length() == 0)
throw std::runtime_error("PDUSessionResourceReleaseResponseTransfer encoding failed");
asn::Free(asn_DEF_ASN_NGAP_PDUSessionResourceReleaseResponseTransfer, tr);
auto *item = asn::New<ASN_NGAP_PDUSessionResourceReleasedItemRelRes>();
item->pDUSessionID = static_cast<ASN_NGAP_PDUSessionID_t>(psi);
asn::SetOctetString(item->pDUSessionResourceReleaseResponseTransfer, encodedTr);
asn::SequenceAdd(ieResp->value.choice.PDUSessionResourceReleasedListRelRes, item);
}
auto *respPdu = asn::ngap::NewMessagePdu<ASN_NGAP_PDUSessionResourceReleaseResponse>({ieResp});
sendNgapUeAssociated(ue->ctxId, respPdu);
m_logger->info("PDU session resource(s) released for UE[%d] count[%d]", ue->ctxId, static_cast<int>(psIds.size()));
}
} // namespace nr::gnb

108
src/gnb/ngap/task.cpp Normal file
View File

@@ -0,0 +1,108 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <sstream>
#include <gnb/app/task.hpp>
#include <gnb/sctp/task.hpp>
namespace nr::gnb
{
NgapTask::NgapTask(TaskBase *base) : m_base{base}, m_ueNgapIdCounter{}, m_downlinkTeidCounter{}, m_isInitialized{}
{
m_logger = base->logBase->makeUniqueLogger("ngap");
}
void NgapTask::onStart()
{
for (auto &amfConfig : m_base->config->amfConfigs)
createAmfContext(amfConfig);
if (m_amfCtx.empty())
m_logger->warn("No AMF configuration is provided");
for (auto &amfCtx : m_amfCtx)
{
auto msg = std::make_unique<NmGnbSctp>(NmGnbSctp::CONNECTION_REQUEST);
msg->clientId = amfCtx.second->ctxId;
msg->localAddress = m_base->config->ngapIp;
msg->localPort = 0;
msg->remoteAddress = amfCtx.second->address;
msg->remotePort = amfCtx.second->port;
msg->ppid = sctp::PayloadProtocolId::NGAP;
msg->associatedTask = this;
m_base->sctpTask->push(std::move(msg));
}
}
void NgapTask::onLoop()
{
auto msg = take();
if (!msg)
return;
switch (msg->msgType)
{
case NtsMessageType::GNB_RRC_TO_NGAP: {
auto &w = dynamic_cast<NmGnbRrcToNgap &>(*msg);
switch (w.present)
{
case NmGnbRrcToNgap::INITIAL_NAS_DELIVERY: {
handleInitialNasTransport(w.ueId, w.pdu, w.rrcEstablishmentCause, w.sTmsi);
break;
}
case NmGnbRrcToNgap::UPLINK_NAS_DELIVERY: {
handleUplinkNasTransport(w.ueId, w.pdu);
break;
}
case NmGnbRrcToNgap::RADIO_LINK_FAILURE: {
handleRadioLinkFailure(w.ueId);
break;
}
}
break;
}
case NtsMessageType::GNB_SCTP: {
auto &w = dynamic_cast<NmGnbSctp &>(*msg);
switch (w.present)
{
case NmGnbSctp::ASSOCIATION_SETUP:
handleAssociationSetup(w.clientId, w.associationId, w.inStreams, w.outStreams);
break;
case NmGnbSctp::RECEIVE_MESSAGE:
handleSctpMessage(w.clientId, w.stream, w.buffer);
break;
case NmGnbSctp::ASSOCIATION_SHUTDOWN:
handleAssociationShutdown(w.clientId);
break;
default:
m_logger->unhandledNts(*msg);
break;
}
break;
}
default: {
m_logger->unhandledNts(*msg);
break;
}
}
}
void NgapTask::onQuit()
{
for (auto &i : m_ueCtx)
delete i.second;
for (auto &i : m_amfCtx)
delete i.second;
m_ueCtx.clear();
m_amfCtx.clear();
}
} // namespace nr::gnb

129
src/gnb/ngap/task.hpp vendored Normal file
View File

@@ -0,0 +1,129 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <optional>
#include <unordered_map>
#include <gnb/nts.hpp>
#include <gnb/types.hpp>
#include <lib/app/monitor.hpp>
#include <utils/logger.hpp>
#include <utils/nts.hpp>
extern "C"
{
struct ASN_NGAP_NGAP_PDU;
struct ASN_NGAP_NGSetupResponse;
struct ASN_NGAP_NGSetupFailure;
struct ASN_NGAP_ErrorIndication;
struct ASN_NGAP_DownlinkNASTransport;
struct ASN_NGAP_RerouteNASRequest;
struct ASN_NGAP_PDUSessionResourceSetupRequest;
struct ASN_NGAP_InitialContextSetupRequest;
struct ASN_NGAP_UEContextReleaseCommand;
struct ASN_NGAP_UEContextModificationRequest;
struct ASN_NGAP_AMFConfigurationUpdate;
struct ASN_NGAP_OverloadStart;
struct ASN_NGAP_OverloadStop;
struct ASN_NGAP_PDUSessionResourceReleaseCommand;
struct ASN_NGAP_Paging;
}
namespace nr::gnb
{
class SctpTask;
class GnbRrcTask;
class GtpTask;
class GnbAppTask;
class NgapTask : public NtsTask
{
private:
TaskBase *m_base;
std::unique_ptr<Logger> m_logger;
std::unordered_map<int, NgapAmfContext *> m_amfCtx;
std::unordered_map<int, NgapUeContext *> m_ueCtx;
int64_t m_ueNgapIdCounter;
uint32_t m_downlinkTeidCounter;
bool m_isInitialized;
friend class GnbCmdHandler;
public:
explicit NgapTask(TaskBase *base);
~NgapTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
private:
/* Utility functions */
void createAmfContext(const GnbAmfConfig &config);
NgapAmfContext *findAmfContext(int ctxId);
void createUeContext(int ueId, int32_t &requestedSliceType);
NgapUeContext *findUeContext(int ctxId);
NgapUeContext *findUeByRanId(int64_t ranUeNgapId);
NgapUeContext *findUeByAmfId(int64_t amfUeNgapId);
NgapUeContext *findUeByNgapIdPair(int amfCtxId, const NgapIdPair &idPair);
void deleteUeContext(int ueId);
void deleteAmfContext(int amfId);
/* Interface management */
void handleAssociationSetup(int amfId, int ascId, int inCount, int outCount);
void handleAssociationShutdown(int amfId);
void sendNgSetupRequest(int amfId);
void sendErrorIndication(int amfId, NgapCause cause = NgapCause::Protocol_unspecified, int ueId = 0);
void receiveNgSetupResponse(int amfId, ASN_NGAP_NGSetupResponse *msg);
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);
void sendNgapUeAssociated(int ueId, ASN_NGAP_NGAP_PDU *pdu);
void handleSctpMessage(int amfId, uint16_t stream, const UniqueBuffer &buffer);
bool handleSctpStreamId(int amfId, int stream, const ASN_NGAP_NGAP_PDU &pdu);
/* NAS transport */
void handleInitialNasTransport(int ueId, OctetString &nasPdu, int64_t rrcEstablishmentCause,
const std::optional<GutiMobileIdentity> &sTmsi);
void handleUplinkNasTransport(int ueId, const OctetString &nasPdu);
void receiveDownlinkNasTransport(int amfId, ASN_NGAP_DownlinkNASTransport *msg);
void deliverDownlinkNas(int ueId, OctetString &&nasPdu);
void sendNasNonDeliveryIndication(int ueId, const OctetString &nasPdu, NgapCause cause);
void receiveRerouteNasRequest(int amfId, ASN_NGAP_RerouteNASRequest *msg);
/* PDU session management */
void receiveSessionResourceSetupRequest(int amfId, ASN_NGAP_PDUSessionResourceSetupRequest *msg);
void receiveSessionResourceReleaseCommand(int amfId, ASN_NGAP_PDUSessionResourceReleaseCommand *msg);
std::optional<NgapCause> setupPduSessionResource(NgapUeContext *ue, PduSessionResource *resource);
/* UE context management */
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, int32_t &requestedSliceType);
NgapAmfContext *selectNewAmfForReAllocation(int ueId, int initiatedAmfId, int amfSetId);
/* Radio resource control */
void handleRadioLinkFailure(int ueId);
void receivePaging(int amfId, ASN_NGAP_Paging *msg);
};
} // namespace nr::gnb

407
src/gnb/ngap/transport.cpp Normal file
View File

@@ -0,0 +1,407 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "encode.hpp"
#include "task.hpp"
#include "utils.hpp"
#include <gnb/app/task.hpp>
#include <gnb/nts.hpp>
#include <gnb/sctp/task.hpp>
#include <lib/asn/ngap.hpp>
#include <lib/asn/utils.hpp>
#include <asn/ngap/ASN_NGAP_AMF-UE-NGAP-ID.h>
#include <asn/ngap/ASN_NGAP_InitiatingMessage.h>
#include <asn/ngap/ASN_NGAP_NGAP-PDU.h>
#include <asn/ngap/ASN_NGAP_ProtocolIE-Field.h>
#include <asn/ngap/ASN_NGAP_RAN-UE-NGAP-ID.h>
#include <asn/ngap/ASN_NGAP_SuccessfulOutcome.h>
#include <asn/ngap/ASN_NGAP_UnsuccessfulOutcome.h>
#include <asn/ngap/ASN_NGAP_UserLocationInformation.h>
#include <asn/ngap/ASN_NGAP_UserLocationInformationNR.h>
static e_ASN_NGAP_Criticality FindCriticalityOfUserIe(ASN_NGAP_NGAP_PDU *pdu, ASN_NGAP_ProtocolIE_ID_t ieId)
{
auto procedureCode =
pdu->present == ASN_NGAP_NGAP_PDU_PR_initiatingMessage ? pdu->choice.initiatingMessage->procedureCode
: pdu->present == ASN_NGAP_NGAP_PDU_PR_successfulOutcome ? pdu->choice.successfulOutcome->procedureCode
: pdu->choice.unsuccessfulOutcome->procedureCode;
if (ieId == ASN_NGAP_ProtocolIE_ID_id_UserLocationInformation)
{
return procedureCode == ASN_NGAP_ProcedureCode_id_InitialUEMessage ? ASN_NGAP_Criticality_reject
: ASN_NGAP_Criticality_ignore;
}
if (ieId == ASN_NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID || ieId == ASN_NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID)
{
if (procedureCode == ASN_NGAP_ProcedureCode_id_RerouteNASRequest)
{
return ieId == ASN_NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID ? ASN_NGAP_Criticality_reject
: ASN_NGAP_Criticality_ignore;
}
if (pdu->present == ASN_NGAP_NGAP_PDU_PR_initiatingMessage)
{
if (procedureCode == ASN_NGAP_ProcedureCode_id_UEContextReleaseRequest ||
procedureCode == ASN_NGAP_ProcedureCode_id_HandoverPreparation)
return ASN_NGAP_Criticality_reject;
}
if (procedureCode == ASN_NGAP_ProcedureCode_id_PDUSessionResourceNotify ||
procedureCode == ASN_NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication ||
procedureCode == ASN_NGAP_ProcedureCode_id_RRCInactiveTransitionReport ||
procedureCode == ASN_NGAP_ProcedureCode_id_HandoverNotification ||
procedureCode == ASN_NGAP_ProcedureCode_id_PathSwitchRequest ||
procedureCode == ASN_NGAP_ProcedureCode_id_HandoverCancel ||
procedureCode == ASN_NGAP_ProcedureCode_id_UplinkRANStatusTransfer ||
procedureCode == ASN_NGAP_ProcedureCode_id_InitialUEMessage ||
procedureCode == ASN_NGAP_ProcedureCode_id_DownlinkNASTransport ||
procedureCode == ASN_NGAP_ProcedureCode_id_UplinkNASTransport ||
procedureCode == ASN_NGAP_ProcedureCode_id_NASNonDeliveryIndication ||
procedureCode == ASN_NGAP_ProcedureCode_id_UplinkUEAssociatedNRPPaTransport ||
procedureCode == ASN_NGAP_ProcedureCode_id_UplinkNonUEAssociatedNRPPaTransport ||
procedureCode == ASN_NGAP_ProcedureCode_id_CellTrafficTrace ||
procedureCode == ASN_NGAP_ProcedureCode_id_TraceStart ||
procedureCode == ASN_NGAP_ProcedureCode_id_DeactivateTrace ||
procedureCode == ASN_NGAP_ProcedureCode_id_TraceFailureIndication ||
procedureCode == ASN_NGAP_ProcedureCode_id_LocationReport ||
procedureCode == ASN_NGAP_ProcedureCode_id_LocationReportingControl ||
procedureCode == ASN_NGAP_ProcedureCode_id_LocationReportingFailureIndication ||
procedureCode == ASN_NGAP_ProcedureCode_id_UERadioCapabilityInfoIndication)
return ASN_NGAP_Criticality_reject;
}
return ASN_NGAP_Criticality_ignore;
}
namespace nr::gnb
{
void NgapTask::sendNgapNonUe(int associatedAmf, ASN_NGAP_NGAP_PDU *pdu)
{
auto *amf = findAmfContext(associatedAmf);
if (amf == nullptr)
{
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
return;
}
char errorBuffer[1024];
size_t len;
if (asn_check_constraints(&asn_DEF_ASN_NGAP_NGAP_PDU, pdu, errorBuffer, &len) != 0)
{
m_logger->err("NGAP PDU ASN constraint validation failed");
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
return;
}
ssize_t encoded;
uint8_t *buffer;
if (!ngap_encode::Encode(asn_DEF_ASN_NGAP_NGAP_PDU, pdu, encoded, buffer))
m_logger->err("NGAP APER encoding failed");
else
{
auto msg = std::make_unique<NmGnbSctp>(NmGnbSctp::SEND_MESSAGE);
msg->clientId = amf->ctxId;
msg->stream = 0;
msg->buffer = UniqueBuffer{buffer, static_cast<size_t>(encoded)};
m_base->sctpTask->push(std::move(msg));
if (m_base->nodeListener)
{
std::string xer = ngap_encode::EncodeXer(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
if (xer.length() > 0)
{
m_base->nodeListener->onSend(app::NodeType::GNB, m_base->config->name, app::NodeType::AMF, amf->amfName,
app::ConnectionType::NGAP, xer);
}
}
}
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
}
void NgapTask::sendNgapUeAssociated(int ueId, ASN_NGAP_NGAP_PDU *pdu)
{
/* Find UE and AMF contexts */
auto *ue = findUeContext(ueId);
if (ue == nullptr)
{
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
return;
}
auto *amf = findAmfContext(ue->associatedAmfId);
if (amf == nullptr)
{
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
return;
}
/* Insert UE-related information elements */
{
if (ue->amfUeNgapId > 0)
{
asn::ngap::AddProtocolIeIfUsable(
*pdu, asn_DEF_ASN_NGAP_AMF_UE_NGAP_ID, ASN_NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID,
FindCriticalityOfUserIe(pdu, ASN_NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID), [ue](void *mem) {
auto &id = *reinterpret_cast<ASN_NGAP_AMF_UE_NGAP_ID_t *>(mem);
asn::SetSigned64(ue->amfUeNgapId, id);
});
}
asn::ngap::AddProtocolIeIfUsable(
*pdu, asn_DEF_ASN_NGAP_RAN_UE_NGAP_ID, ASN_NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID,
FindCriticalityOfUserIe(pdu, ASN_NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID),
[ue](void *mem) { *reinterpret_cast<ASN_NGAP_RAN_UE_NGAP_ID_t *>(mem) = ue->ranUeNgapId; });
asn::ngap::AddProtocolIeIfUsable(
*pdu, asn_DEF_ASN_NGAP_UserLocationInformation, ASN_NGAP_ProtocolIE_ID_id_UserLocationInformation,
FindCriticalityOfUserIe(pdu, ASN_NGAP_ProtocolIE_ID_id_UserLocationInformation), [this](void *mem) {
auto *loc = reinterpret_cast<ASN_NGAP_UserLocationInformation *>(mem);
loc->present = ASN_NGAP_UserLocationInformation_PR_userLocationInformationNR;
loc->choice.userLocationInformationNR = asn::New<ASN_NGAP_UserLocationInformationNR>();
auto &nr = loc->choice.userLocationInformationNR;
nr->timeStamp = asn::New<ASN_NGAP_TimeStamp_t>();
ngap_utils::ToPlmnAsn_Ref(m_base->config->plmn, nr->nR_CGI.pLMNIdentity);
asn::SetBitStringLong<36>(m_base->config->nci, nr->nR_CGI.nRCellIdentity);
ngap_utils::ToPlmnAsn_Ref(m_base->config->plmn, nr->tAI.pLMNIdentity);
asn::SetOctetString3(nr->tAI.tAC, octet3{m_base->config->tac});
asn::SetOctetString4(*nr->timeStamp, octet4{utils::CurrentTimeStamp().seconds32()});
});
}
/* Encode and send the PDU */
char errorBuffer[1024];
size_t len;
if (asn_check_constraints(&asn_DEF_ASN_NGAP_NGAP_PDU, pdu, errorBuffer, &len) != 0)
{
m_logger->err("NGAP PDU ASN constraint validation failed");
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
return;
}
ssize_t encoded;
uint8_t *buffer;
if (!ngap_encode::Encode(asn_DEF_ASN_NGAP_NGAP_PDU, pdu, encoded, buffer))
m_logger->err("NGAP APER encoding failed");
else
{
auto msg = std::make_unique<NmGnbSctp>(NmGnbSctp::SEND_MESSAGE);
msg->clientId = amf->ctxId;
msg->stream = ue->uplinkStream;
msg->buffer = UniqueBuffer{buffer, static_cast<size_t>(encoded)};
m_base->sctpTask->push(std::move(msg));
if (m_base->nodeListener)
{
std::string xer = ngap_encode::EncodeXer(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
if (xer.length() > 0)
{
m_base->nodeListener->onSend(app::NodeType::GNB, m_base->config->name, app::NodeType::AMF, amf->amfName,
app::ConnectionType::NGAP, xer);
}
}
}
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
}
void NgapTask::handleSctpMessage(int amfId, uint16_t stream, const UniqueBuffer &buffer)
{
auto *amf = findAmfContext(amfId);
if (amf == nullptr)
return;
auto *pdu = ngap_encode::Decode<ASN_NGAP_NGAP_PDU>(asn_DEF_ASN_NGAP_NGAP_PDU, buffer.data(), buffer.size());
if (pdu == nullptr)
{
m_logger->err("APER decoding failed for SCTP message");
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
sendErrorIndication(amfId, NgapCause::Protocol_transfer_syntax_error);
return;
}
if (m_base->nodeListener)
{
std::string xer = ngap_encode::EncodeXer(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
if (xer.length() > 0)
{
m_base->nodeListener->onReceive(app::NodeType::GNB, m_base->config->name, app::NodeType::AMF, amf->amfName,
app::ConnectionType::NGAP, xer);
}
}
if (!handleSctpStreamId(amf->ctxId, stream, *pdu))
{
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
return;
}
if (pdu->present == ASN_NGAP_NGAP_PDU_PR_initiatingMessage)
{
auto value = pdu->choice.initiatingMessage->value;
switch (value.present)
{
case ASN_NGAP_InitiatingMessage__value_PR_ErrorIndication:
receiveErrorIndication(amf->ctxId, &value.choice.ErrorIndication);
break;
case ASN_NGAP_InitiatingMessage__value_PR_InitialContextSetupRequest:
receiveInitialContextSetup(amf->ctxId, &value.choice.InitialContextSetupRequest);
break;
case ASN_NGAP_InitiatingMessage__value_PR_RerouteNASRequest:
receiveRerouteNasRequest(amf->ctxId, &value.choice.RerouteNASRequest);
break;
case ASN_NGAP_InitiatingMessage__value_PR_UEContextReleaseCommand:
receiveContextRelease(amf->ctxId, &value.choice.UEContextReleaseCommand);
break;
case ASN_NGAP_InitiatingMessage__value_PR_UEContextModificationRequest:
receiveContextModification(amf->ctxId, &value.choice.UEContextModificationRequest);
break;
case ASN_NGAP_InitiatingMessage__value_PR_PDUSessionResourceSetupRequest:
receiveSessionResourceSetupRequest(amf->ctxId, &value.choice.PDUSessionResourceSetupRequest);
break;
case ASN_NGAP_InitiatingMessage__value_PR_DownlinkNASTransport:
receiveDownlinkNasTransport(amf->ctxId, &value.choice.DownlinkNASTransport);
break;
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;
case ASN_NGAP_InitiatingMessage__value_PR_PDUSessionResourceReleaseCommand:
receiveSessionResourceReleaseCommand(amf->ctxId, &value.choice.PDUSessionResourceReleaseCommand);
break;
case ASN_NGAP_InitiatingMessage__value_PR_Paging:
receivePaging(amf->ctxId, &value.choice.Paging);
break;
default:
m_logger->err("Unhandled NGAP initiating-message received (%d)", value.present);
break;
}
}
else if (pdu->present == ASN_NGAP_NGAP_PDU_PR_successfulOutcome)
{
auto value = pdu->choice.successfulOutcome->value;
switch (value.present)
{
case ASN_NGAP_SuccessfulOutcome__value_PR_NGSetupResponse:
receiveNgSetupResponse(amf->ctxId, &value.choice.NGSetupResponse);
break;
default:
m_logger->err("Unhandled NGAP successful-outcome received (%d)", value.present);
break;
}
}
else if (pdu->present == ASN_NGAP_NGAP_PDU_PR_unsuccessfulOutcome)
{
auto value = pdu->choice.unsuccessfulOutcome->value;
switch (value.present)
{
case ASN_NGAP_UnsuccessfulOutcome__value_PR_NGSetupFailure:
receiveNgSetupFailure(amf->ctxId, &value.choice.NGSetupFailure);
break;
default:
m_logger->err("Unhandled NGAP unsuccessful-outcome received (%d)", value.present);
break;
}
}
else
{
m_logger->warn("Empty NGAP PDU ignored");
}
asn::Free(asn_DEF_ASN_NGAP_NGAP_PDU, pdu);
}
bool NgapTask::handleSctpStreamId(int amfId, int stream, const ASN_NGAP_NGAP_PDU &pdu)
{
if (m_base->config->ignoreStreamIds)
return true;
auto *ptr =
asn::ngap::FindProtocolIeInPdu(pdu, asn_DEF_ASN_NGAP_UE_NGAP_IDs, ASN_NGAP_ProtocolIE_ID_id_UE_NGAP_IDs);
if (ptr != nullptr)
{
if (stream == 0)
{
m_logger->err("Received stream number == 0 in UE-associated signalling");
sendErrorIndication(amfId, NgapCause::Protocol_unspecified);
return false;
}
auto &ids = *reinterpret_cast<ASN_NGAP_UE_NGAP_IDs *>(ptr);
auto *ue = findUeByNgapIdPair(amfId, ngap_utils::FindNgapIdPairFromAsnNgapIds(ids));
if (ue == nullptr)
return false;
if (ue->downlinkStream == 0)
ue->downlinkStream = stream;
else if (ue->downlinkStream != stream)
{
m_logger->err("received stream number is inconsistent. received %d, expected :%d", stream,
ue->downlinkStream);
sendErrorIndication(amfId, NgapCause::Protocol_unspecified);
return false;
}
}
else
{
ptr = asn::ngap::FindProtocolIeInPdu(pdu, asn_DEF_ASN_NGAP_RAN_UE_NGAP_ID,
ASN_NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID);
if (ptr != nullptr)
{
if (stream == 0)
{
m_logger->err("Received stream number == 0 in UE-associated signalling");
sendErrorIndication(amfId, NgapCause::Protocol_unspecified);
return false;
}
auto id = static_cast<int64_t>(*reinterpret_cast<ASN_NGAP_RAN_UE_NGAP_ID_t *>(ptr));
auto *ue = findUeByRanId(id);
if (ue == nullptr)
return false;
if (ue->downlinkStream == 0)
ue->downlinkStream = stream;
else if (ue->downlinkStream != stream)
{
m_logger->err("received stream number is inconsistent. received %d, expected :%d", stream,
ue->downlinkStream);
sendErrorIndication(amfId, NgapCause::Protocol_unspecified);
return false;
}
}
else
{
if (stream != 0)
{
m_logger->err("Received stream number != 0 in non-UE-associated signalling");
sendErrorIndication(amfId, NgapCause::Protocol_unspecified);
return false;
}
}
}
return true;
}
} // namespace nr::gnb

230
src/gnb/ngap/utils.cpp Normal file
View File

@@ -0,0 +1,230 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "utils.hpp"
namespace nr::gnb::ngap_utils
{
ASN_NGAP_PagingDRX_t PagingDrxToAsn(EPagingDrx pagingDrx)
{
switch (pagingDrx)
{
case EPagingDrx::V32:
return ASN_NGAP_PagingDRX_v32;
case EPagingDrx::V64:
return ASN_NGAP_PagingDRX_v64;
case EPagingDrx::V128:
return ASN_NGAP_PagingDRX_v128;
case EPagingDrx::V256:
return ASN_NGAP_PagingDRX_v256;
}
return ~0;
}
octet3 PlmnToOctet3(const Plmn &plmn)
{
int mcc = plmn.mcc;
int mcc3 = mcc % 10;
int mcc2 = (mcc % 100) / 10;
int mcc1 = (mcc % 1000) / 100;
int mnc = plmn.mnc;
if (plmn.isLongMnc)
{
int mnc1 = mnc % 1000 / 100;
int mnc2 = mnc % 100 / 10;
int mnc3 = mnc % 10;
int octet1 = mcc2 << 4 | mcc1;
int octet2 = mnc1 << 4 | mcc3;
int octet3 = mnc3 << 4 | mnc2;
return {(uint8_t)octet1, (uint8_t)octet2, (uint8_t)octet3};
}
else
{
int mnc1 = mnc % 100 / 10;
int mnc2 = mnc % 10;
int mnc3 = 0xF;
int octet1 = mcc2 << 4 | mcc1;
int octet2 = mnc3 << 4 | mcc3;
int octet3 = mnc2 << 4 | mnc1;
return {(uint8_t)octet1, (uint8_t)octet2, (uint8_t)octet3};
}
}
void PlmnFromAsn_Ref(const ASN_NGAP_PLMNIdentity_t &source, Plmn &target)
{
assert(source.size == 3);
int i = ((source.buf[1] & 0xf0) >> 4);
if (i == 0xf)
{
i = 0;
target.isLongMnc = false;
}
else
target.isLongMnc = true;
target.mcc = (((source.buf[0] & 0xf0) >> 4) * 10) + ((source.buf[0] & 0x0f) * 100) + (source.buf[1] & 0x0f);
target.mnc = (i * 100) + ((source.buf[2] & 0xf0) >> 4) + ((source.buf[2] & 0x0f) * 10);
}
void GuamiFromAsn_Ref(const ASN_NGAP_GUAMI_t &guami, Guami &target)
{
target.amfRegionId = asn::GetBitStringInt<8>(guami.aMFRegionID);
target.amfSetId = asn::GetBitStringInt<10>(guami.aMFSetID);
target.amfPointer = asn::GetBitStringInt<6>(guami.aMFPointer);
target.plmn = {};
PlmnFromAsn_Ref(guami.pLMNIdentity, target.plmn);
}
SingleSlice SliceSupportFromAsn(ASN_NGAP_SliceSupportItem &supportItem)
{
SingleSlice s{};
s.sst = asn::GetOctet1(supportItem.s_NSSAI.sST);
s.sd = std::nullopt;
if (supportItem.s_NSSAI.sD)
s.sd = asn::GetOctet3(*supportItem.s_NSSAI.sD);
return s;
}
std::string CauseToString(const ASN_NGAP_Cause_t &cause)
{
std::string result;
int64_t enumValue;
asn_TYPE_descriptor_t *typeDescriptor;
switch (cause.present)
{
case ASN_NGAP_Cause_PR_radioNetwork:
result = "radio-network";
enumValue = static_cast<int64_t>(cause.choice.radioNetwork);
typeDescriptor = &asn_DEF_ASN_NGAP_CauseRadioNetwork;
break;
case ASN_NGAP_Cause_PR_transport:
result = "transport";
enumValue = static_cast<int64_t>(cause.choice.transport);
typeDescriptor = &asn_DEF_ASN_NGAP_CauseTransport;
break;
case ASN_NGAP_Cause_PR_nas:
result = "nas";
enumValue = static_cast<int64_t>(cause.choice.nas);
typeDescriptor = &asn_DEF_ASN_NGAP_CauseNas;
break;
case ASN_NGAP_Cause_PR_protocol:
result = "protocol";
enumValue = static_cast<int64_t>(cause.choice.protocol);
typeDescriptor = &asn_DEF_ASN_NGAP_CauseProtocol;
break;
case ASN_NGAP_Cause_PR_misc:
result = "misc";
enumValue = static_cast<int64_t>(cause.choice.misc);
typeDescriptor = &asn_DEF_ASN_NGAP_CauseMisc;
break;
default:
return "<?>";
}
auto *specs = reinterpret_cast<const asn_INTEGER_specifics_t *>(typeDescriptor->specifics);
if (specs)
{
if (specs->value2enum)
{
for (int i = 0; i < specs->map_count; i++)
{
if (static_cast<int64_t>(specs->value2enum[i].nat_value) == enumValue)
{
result += "/" + std::string(specs->value2enum[i].enum_name);
break;
}
}
}
}
return result;
}
void ToCauseAsn_Ref(NgapCause source, ASN_NGAP_Cause_t &target)
{
int val = (int)source;
if (val >= 400)
{
val -= 400;
target.present = ASN_NGAP_Cause_PR_misc;
target.choice.misc = static_cast<ASN_NGAP_CauseMisc_t>(val);
}
else if (val >= 300)
{
val -= 300;
target.present = ASN_NGAP_Cause_PR_protocol;
target.choice.protocol = static_cast<ASN_NGAP_CauseProtocol_t>(val);
}
else if (val >= 200)
{
val -= 200;
target.present = ASN_NGAP_Cause_PR_nas;
target.choice.nas = static_cast<ASN_NGAP_CauseNas_t>(val);
}
else if (val >= 100)
{
val -= 100;
target.present = ASN_NGAP_Cause_PR_transport;
target.choice.transport = static_cast<ASN_NGAP_CauseTransport_t>(val);
}
else
{
target.present = ASN_NGAP_Cause_PR_radioNetwork;
target.choice.radioNetwork = static_cast<ASN_NGAP_CauseRadioNetwork_t>(val);
}
}
PduSessionType PduSessionTypeFromAsn(const ASN_NGAP_PDUSessionType_t &source)
{
switch (source)
{
case ASN_NGAP_PDUSessionType_ipv4:
return PduSessionType::IPv4;
case ASN_NGAP_PDUSessionType_ipv6:
return PduSessionType::IPv6;
case ASN_NGAP_PDUSessionType_ipv4v6:
return PduSessionType::IPv4v6;
case ASN_NGAP_PDUSessionType_ethernet:
return PduSessionType::ETHERNET;
default:
return PduSessionType::UNSTRUCTURED;
}
}
void ToPlmnAsn_Ref(const Plmn &source, ASN_NGAP_PLMNIdentity_t &target)
{
octet3 val = PlmnToOctet3(source);
asn::SetOctetString3(target, val);
}
NgapIdPair FindNgapIdPairFromAsnNgapIds(const ASN_NGAP_UE_NGAP_IDs &ngapIDs)
{
std::optional<int64_t> amfUeNgapId{}, ranUeNgapId{};
if (ngapIDs.present == ASN_NGAP_UE_NGAP_IDs_PR_uE_NGAP_ID_pair)
{
amfUeNgapId = asn::GetSigned64(ngapIDs.choice.uE_NGAP_ID_pair->aMF_UE_NGAP_ID);
ranUeNgapId = ngapIDs.choice.uE_NGAP_ID_pair->rAN_UE_NGAP_ID;
}
else if (ngapIDs.present == ASN_NGAP_UE_NGAP_IDs_PR_aMF_UE_NGAP_ID)
{
amfUeNgapId = asn::GetSigned64(ngapIDs.choice.aMF_UE_NGAP_ID);
}
return NgapIdPair{amfUeNgapId, ranUeNgapId};
}
} // namespace nr::gnb::ngap_utils

65
src/gnb/ngap/utils.hpp vendored Normal file
View File

@@ -0,0 +1,65 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <gnb/types.hpp>
#include <lib/asn/ngap.hpp>
#include <lib/asn/utils.hpp>
#include <utils/common.hpp>
#include <utils/common_types.hpp>
#include <asn/ngap/ASN_NGAP_Cause.h>
#include <asn/ngap/ASN_NGAP_GUAMI.h>
#include <asn/ngap/ASN_NGAP_PagingDRX.h>
#include <asn/ngap/ASN_NGAP_ProtocolIE-Field.h>
#include <asn/ngap/ASN_NGAP_SliceSupportItem.h>
#include <asn/ngap/ASN_NGAP_UE-NGAP-ID-pair.h>
namespace nr::gnb::ngap_utils
{
ASN_NGAP_PagingDRX_t PagingDrxToAsn(EPagingDrx pagingDrx);
std::string CauseToString(const ASN_NGAP_Cause_t &cause);
octet3 PlmnToOctet3(const Plmn &plmn);
PduSessionType PduSessionTypeFromAsn(const ASN_NGAP_PDUSessionType_t &source);
void PlmnFromAsn_Ref(const ASN_NGAP_PLMNIdentity_t &source, Plmn &target);
void GuamiFromAsn_Ref(const ASN_NGAP_GUAMI_t &guami, Guami &target);
void ToCauseAsn_Ref(NgapCause source, ASN_NGAP_Cause_t &target);
void ToPlmnAsn_Ref(const Plmn &source, ASN_NGAP_PLMNIdentity_t &target);
SingleSlice SliceSupportFromAsn(ASN_NGAP_SliceSupportItem &supportItem);
NgapIdPair FindNgapIdPairFromAsnNgapIds(const ASN_NGAP_UE_NGAP_IDs &ngapIDs);
template <typename T>
inline NgapIdPair FindNgapIdPair(T *msg)
{
auto *ieAmfUeNgapId = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID);
auto *ieRanUeNgapId = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID);
std::optional<int64_t> amfUeNgapId{}, ranUeNgapId{};
if (ieAmfUeNgapId)
amfUeNgapId = asn::GetSigned64(ieAmfUeNgapId->AMF_UE_NGAP_ID);
if (ieRanUeNgapId)
ranUeNgapId = ieRanUeNgapId->RAN_UE_NGAP_ID;
return NgapIdPair{amfUeNgapId, ranUeNgapId};
}
template <typename T>
inline NgapIdPair FindNgapIdPairFromUeNgapIds(T *msg)
{
auto *ieUeNgapIds = asn::ngap::GetProtocolIe(msg, ASN_NGAP_ProtocolIE_ID_id_UE_NGAP_IDs);
if (ieUeNgapIds)
return FindNgapIdPairFromAsnNgapIds(ieUeNgapIds->UE_NGAP_IDs);
return NgapIdPair{{}, {}};
}
} // namespace nr::gnb::ngap_utils

9
src/gnb/nts.cpp Normal file
View File

@@ -0,0 +1,9 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "nts.hpp"

312
src/gnb/nts.hpp vendored Normal file
View File

@@ -0,0 +1,312 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include "types.hpp"
#include <utility>
#include <lib/app/cli_base.hpp>
#include <lib/app/cli_cmd.hpp>
#include <lib/asn/utils.hpp>
#include <lib/rls/rls_base.hpp>
#include <lib/rrc/rrc.hpp>
#include <lib/sctp/sctp.hpp>
#include <utils/network.hpp>
#include <utils/nts.hpp>
#include <utils/octet_string.hpp>
#include <utils/unique_buffer.hpp>
extern "C"
{
struct ASN_NGAP_FiveG_S_TMSI;
struct ASN_NGAP_TAIListForPaging;
}
namespace nr::gnb
{
struct NmGnbRlsToRrc : NtsMessage
{
enum PR
{
SIGNAL_DETECTED,
UPLINK_RRC,
} present;
// SIGNAL_DETECTED
// UPLINK_RRC
int ueId{};
// UPLINK_RRC
OctetString data;
rrc::RrcChannel rrcChannel{};
explicit NmGnbRlsToRrc(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_RRC), present(present)
{
}
};
struct NmGnbRlsToGtp : NtsMessage
{
enum PR
{
DATA_PDU_DELIVERY,
} present;
// DATA_PDU_DELIVERY
int ueId{};
int psi{};
OctetString pdu;
explicit NmGnbRlsToGtp(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_GTP), present(present)
{
}
};
struct NmGnbGtpToRls : NtsMessage
{
enum PR
{
DATA_PDU_DELIVERY,
} present;
// DATA_PDU_DELIVERY
int ueId{};
int psi{};
OctetString pdu{};
explicit NmGnbGtpToRls(PR present) : NtsMessage(NtsMessageType::GNB_GTP_TO_RLS), present(present)
{
}
};
struct NmGnbRlsToRls : NtsMessage
{
enum PR
{
SIGNAL_DETECTED,
SIGNAL_LOST,
RECEIVE_RLS_MESSAGE,
DOWNLINK_RRC,
DOWNLINK_DATA,
UPLINK_RRC,
UPLINK_DATA,
RADIO_LINK_FAILURE,
TRANSMISSION_FAILURE,
} present;
// SIGNAL_DETECTED
// SIGNAL_LOST
// DOWNLINK_RRC
// DOWNLINK_DATA
// UPLINK_DATA
// UPLINK_RRC
int ueId{};
// RECEIVE_RLS_MESSAGE
std::unique_ptr<rls::RlsMessage> msg{};
// DOWNLINK_DATA
// UPLINK_DATA
int psi{};
// DOWNLINK_DATA
// DOWNLINK_RRC
// UPLINK_DATA
// UPLINK_RRC
OctetString data;
// DOWNLINK_RRC
uint32_t pduId{};
// DOWNLINK_RRC
// UPLINK_RRC
rrc::RrcChannel rrcChannel{};
// RADIO_LINK_FAILURE
rls::ERlfCause rlfCause{};
// TRANSMISSION_FAILURE
std::vector<rls::PduInfo> pduList;
explicit NmGnbRlsToRls(PR present) : NtsMessage(NtsMessageType::GNB_RLS_TO_RLS), present(present)
{
}
};
struct NmGnbRrcToRls : NtsMessage
{
enum PR
{
RRC_PDU_DELIVERY,
} present;
// RRC_PDU_DELIVERY
int ueId{};
rrc::RrcChannel channel{};
OctetString pdu{};
explicit NmGnbRrcToRls(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_RLS), present(present)
{
}
};
struct NmGnbNgapToRrc : NtsMessage
{
enum PR
{
RADIO_POWER_ON,
NAS_DELIVERY,
AN_RELEASE,
PAGING,
} present;
// NAS_DELIVERY
// AN_RELEASE
int ueId{};
// NAS_DELIVERY
OctetString pdu{};
// PAGING
asn::Unique<ASN_NGAP_FiveG_S_TMSI> uePagingTmsi{};
asn::Unique<ASN_NGAP_TAIListForPaging> taiListForPaging{};
explicit NmGnbNgapToRrc(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_RRC), present(present)
{
}
};
struct NmGnbRrcToNgap : NtsMessage
{
enum PR
{
INITIAL_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
int64_t rrcEstablishmentCause{};
std::optional<GutiMobileIdentity> sTmsi{};
explicit NmGnbRrcToNgap(PR present) : NtsMessage(NtsMessageType::GNB_RRC_TO_NGAP), present(present)
{
}
};
struct NmGnbNgapToGtp : NtsMessage
{
enum PR
{
UE_CONTEXT_UPDATE,
UE_CONTEXT_RELEASE,
SESSION_CREATE,
SESSION_RELEASE,
} present;
// UE_CONTEXT_UPDATE
std::unique_ptr<GtpUeContextUpdate> update{};
// SESSION_CREATE
PduSessionResource *resource{};
// UE_CONTEXT_RELEASE
// SESSION_RELEASE
int ueId{};
// SESSION_RELEASE
int psi{};
explicit NmGnbNgapToGtp(PR present) : NtsMessage(NtsMessageType::GNB_NGAP_TO_GTP), present(present)
{
}
};
struct NmGnbSctp : NtsMessage
{
enum PR
{
CONNECTION_REQUEST,
CONNECTION_CLOSE,
ASSOCIATION_SETUP,
ASSOCIATION_SHUTDOWN,
RECEIVE_MESSAGE,
SEND_MESSAGE,
UNHANDLED_NOTIFICATION,
} present;
// CONNECTION_REQUEST
// CONNECTION_CLOSE
// ASSOCIATION_SETUP
// ASSOCIATION_SHUTDOWN
// RECEIVE_MESSAGE
// SEND_MESSAGE
// UNHANDLED_NOTIFICATION
int clientId{};
// CONNECTION_REQUEST
std::string localAddress{};
uint16_t localPort{};
std::string remoteAddress{};
uint16_t remotePort{};
sctp::PayloadProtocolId ppid{};
NtsTask *associatedTask{};
// ASSOCIATION_SETUP
int associationId{};
int inStreams{};
int outStreams{};
// RECEIVE_MESSAGE
// SEND_MESSAGE
UniqueBuffer buffer{};
uint16_t stream{};
explicit NmGnbSctp(PR present) : NtsMessage(NtsMessageType::GNB_SCTP), present(present)
{
}
};
struct NmGnbStatusUpdate : NtsMessage
{
static constexpr const int NGAP_IS_UP = 1;
const int what;
// NGAP_IS_UP
bool isNgapUp{};
explicit NmGnbStatusUpdate(const int what) : NtsMessage(NtsMessageType::GNB_STATUS_UPDATE), what(what)
{
}
};
struct NmGnbCliCommand : NtsMessage
{
std::unique_ptr<app::GnbCliCommand> cmd;
InetAddress address;
NmGnbCliCommand(std::unique_ptr<app::GnbCliCommand> cmd, InetAddress address)
: NtsMessage(NtsMessageType::GNB_CLI_COMMAND), cmd(std::move(cmd)), address(address)
{
}
};
} // namespace nr::gnb

258
src/gnb/rls/ctl_task.cpp Normal file
View File

@@ -0,0 +1,258 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "ctl_task.hpp"
#include <stdexcept>
#include <utils/common.hpp>
static constexpr const size_t MAX_PDU_COUNT = 4096;
static constexpr const int MAX_PDU_TTL = 3000;
static constexpr const int TIMER_ID_ACK_CONTROL = 1;
static constexpr const int TIMER_ID_ACK_SEND = 2;
static constexpr const int TIMER_PERIOD_ACK_CONTROL = 1500;
static constexpr const int TIMER_PERIOD_ACK_SEND = 2250;
namespace nr::gnb
{
RlsControlTask::RlsControlTask(TaskBase *base, uint64_t sti)
: m_sti{sti}, m_mainTask{}, m_udpTask{}, m_pduMap{}, m_pendingAck{}
{
m_logger = base->logBase->makeUniqueLogger("rls-ctl");
}
void RlsControlTask::initialize(NtsTask *mainTask, RlsUdpTask *udpTask)
{
m_mainTask = mainTask;
m_udpTask = udpTask;
}
void RlsControlTask::onStart()
{
setTimer(TIMER_ID_ACK_CONTROL, TIMER_PERIOD_ACK_CONTROL);
setTimer(TIMER_ID_ACK_SEND, TIMER_PERIOD_ACK_SEND);
}
void RlsControlTask::onLoop()
{
auto msg = take();
if (!msg)
return;
switch (msg->msgType)
{
case NtsMessageType::GNB_RLS_TO_RLS: {
auto &w = dynamic_cast<NmGnbRlsToRls &>(*msg);
switch (w.present)
{
case NmGnbRlsToRls::SIGNAL_DETECTED:
handleSignalDetected(w.ueId);
break;
case NmGnbRlsToRls::SIGNAL_LOST:
handleSignalLost(w.ueId);
break;
case NmGnbRlsToRls::RECEIVE_RLS_MESSAGE:
handleRlsMessage(w.ueId, *w.msg);
break;
case NmGnbRlsToRls::DOWNLINK_DATA:
handleDownlinkDataDelivery(w.ueId, w.psi, std::move(w.data));
break;
case NmGnbRlsToRls::DOWNLINK_RRC:
handleDownlinkRrcDelivery(w.ueId, w.pduId, w.rrcChannel, std::move(w.data));
break;
default:
m_logger->unhandledNts(*msg);
break;
}
break;
}
case NtsMessageType::TIMER_EXPIRED: {
auto &w = dynamic_cast<NmTimerExpired &>(*msg);
if (w.timerId == TIMER_ID_ACK_CONTROL)
{
setTimer(TIMER_ID_ACK_CONTROL, TIMER_PERIOD_ACK_CONTROL);
onAckControlTimerExpired();
}
else if (w.timerId == TIMER_ID_ACK_SEND)
{
setTimer(TIMER_ID_ACK_SEND, TIMER_PERIOD_ACK_SEND);
onAckSendTimerExpired();
}
break;
}
default:
m_logger->unhandledNts(*msg);
break;
}
}
void RlsControlTask::onQuit()
{
}
void RlsControlTask::handleSignalDetected(int ueId)
{
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::SIGNAL_DETECTED);
w->ueId = ueId;
m_mainTask->push(std::move(w));
}
void RlsControlTask::handleSignalLost(int ueId)
{
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::SIGNAL_LOST);
w->ueId = ueId;
m_mainTask->push(std::move(w));
}
void RlsControlTask::handleRlsMessage(int ueId, rls::RlsMessage &msg)
{
if (msg.msgType == rls::EMessageType::PDU_TRANSMISSION_ACK)
{
auto &m = (rls::RlsPduTransmissionAck &)msg;
for (auto pduId : m.pduIds)
m_pduMap.erase(pduId);
}
else if (msg.msgType == rls::EMessageType::PDU_TRANSMISSION)
{
auto &m = (rls::RlsPduTransmission &)msg;
if (m.pduId != 0)
m_pendingAck[ueId].push_back(m.pduId);
if (m.pduType == rls::EPduType::DATA)
{
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::UPLINK_DATA);
w->ueId = ueId;
w->psi = static_cast<int>(m.payload);
w->data = std::move(m.pdu);
m_mainTask->push(std::move(w));
}
else if (m.pduType == rls::EPduType::RRC)
{
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::UPLINK_RRC);
w->ueId = ueId;
w->rrcChannel = static_cast<rrc::RrcChannel>(m.payload);
w->data = std::move(m.pdu);
m_mainTask->push(std::move(w));
}
else
{
m_logger->err("Unhandled RLS PDU type");
}
}
else
{
m_logger->err("Unhandled RLS message type");
}
}
void RlsControlTask::handleDownlinkRrcDelivery(int ueId, uint32_t pduId, rrc::RrcChannel channel, OctetString &&data)
{
if (ueId == 0 && pduId != 0)
{
// PDU ID must be not set in case of broadcast
throw std::runtime_error("");
}
if (pduId != 0)
{
if (m_pduMap.count(pduId))
{
m_pduMap.clear();
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::RADIO_LINK_FAILURE);
w->rlfCause = rls::ERlfCause::PDU_ID_EXISTS;
m_mainTask->push(std::move(w));
return;
}
if (m_pduMap.size() > MAX_PDU_COUNT)
{
m_pduMap.clear();
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::RADIO_LINK_FAILURE);
w->rlfCause = rls::ERlfCause::PDU_ID_FULL;
m_mainTask->push(std::move(w));
return;
}
m_pduMap[pduId].endPointId = ueId;
m_pduMap[pduId].id = pduId;
m_pduMap[pduId].pdu = data.copy();
m_pduMap[pduId].rrcChannel = channel;
m_pduMap[pduId].sentTime = utils::CurrentTimeMillis();
}
rls::RlsPduTransmission msg{m_sti};
msg.pduType = rls::EPduType::RRC;
msg.pdu = std::move(data);
msg.payload = static_cast<uint32_t>(channel);
msg.pduId = pduId;
m_udpTask->send(ueId, msg);
}
void RlsControlTask::handleDownlinkDataDelivery(int ueId, int psi, OctetString &&data)
{
rls::RlsPduTransmission msg{m_sti};
msg.pduType = rls::EPduType::DATA;
msg.pdu = std::move(data);
msg.payload = static_cast<uint32_t>(psi);
msg.pduId = 0;
m_udpTask->send(ueId, msg);
}
void RlsControlTask::onAckControlTimerExpired()
{
int64_t current = utils::CurrentTimeMillis();
std::vector<uint32_t> transmissionFailureIds;
std::vector<rls::PduInfo> transmissionFailures;
for (auto &pdu : m_pduMap)
{
auto delta = current - pdu.second.sentTime;
if (delta > MAX_PDU_TTL)
{
transmissionFailureIds.push_back(pdu.first);
transmissionFailures.push_back(std::move(pdu.second));
}
}
for (auto id : transmissionFailureIds)
m_pduMap.erase(id);
if (!transmissionFailures.empty())
{
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::TRANSMISSION_FAILURE);
w->pduList = std::move(transmissionFailures);
m_mainTask->push(std::move(w));
}
}
void RlsControlTask::onAckSendTimerExpired()
{
auto copy = m_pendingAck;
m_pendingAck.clear();
for (auto &item : copy)
{
if (!item.second.empty())
continue;
rls::RlsPduTransmissionAck msg{m_sti};
msg.pduIds = std::move(item.second);
m_udpTask->send(item.first, msg);
}
}
} // namespace nr::gnb

52
src/gnb/rls/ctl_task.hpp vendored Normal file
View File

@@ -0,0 +1,52 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include "udp_task.hpp"
#include <gnb/nts.hpp>
#include <gnb/types.hpp>
#include <utils/nts.hpp>
namespace nr::gnb
{
class RlsControlTask : public NtsTask
{
private:
std::unique_ptr<Logger> m_logger;
uint64_t m_sti;
NtsTask *m_mainTask;
RlsUdpTask *m_udpTask;
std::unordered_map<uint32_t, rls::PduInfo> m_pduMap;
std::unordered_map<int, std::vector<uint32_t>> m_pendingAck;
public:
explicit RlsControlTask(TaskBase *base, uint64_t sti);
~RlsControlTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
public:
void initialize(NtsTask *mainTask, RlsUdpTask *udpTask);
private:
void handleSignalDetected(int ueId);
void handleSignalLost(int ueId);
void handleRlsMessage(int ueId, rls::RlsMessage &msg);
void handleDownlinkRrcDelivery(int ueId, uint32_t pduId, rrc::RrcChannel channel, OctetString &&data);
void handleDownlinkDataDelivery(int ueId, int psi, OctetString &&data);
void onAckControlTimerExpired();
void onAckSendTimerExpired();
};
} // namespace nr::gnb

135
src/gnb/rls/task.cpp Normal file
View File

@@ -0,0 +1,135 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/gtp/task.hpp>
#include <gnb/rrc/task.hpp>
#include <utils/common.hpp>
#include <utils/random.hpp>
namespace nr::gnb
{
GnbRlsTask::GnbRlsTask(TaskBase *base) : m_base{base}
{
m_logger = m_base->logBase->makeUniqueLogger("rls");
m_sti = Random::Mixed(base->config->name).nextUL();
m_udpTask = new RlsUdpTask(base, m_sti, base->config->phyLocation);
m_ctlTask = new RlsControlTask(base, m_sti);
m_udpTask->initialize(m_ctlTask);
m_ctlTask->initialize(this, m_udpTask);
}
void GnbRlsTask::onStart()
{
m_udpTask->start();
m_ctlTask->start();
}
void GnbRlsTask::onLoop()
{
auto msg = take();
if (!msg)
return;
switch (msg->msgType)
{
case NtsMessageType::GNB_RLS_TO_RLS: {
auto &w = dynamic_cast<NmGnbRlsToRls &>(*msg);
switch (w.present)
{
case NmGnbRlsToRls::SIGNAL_DETECTED: {
auto m = std::make_unique<NmGnbRlsToRrc>(NmGnbRlsToRrc::SIGNAL_DETECTED);
m->ueId = w.ueId;
m_base->rrcTask->push(std::move(m));
break;
}
case NmGnbRlsToRls::SIGNAL_LOST: {
m_logger->debug("UE[%d] signal lost", w.ueId);
break;
}
case NmGnbRlsToRls::UPLINK_DATA: {
auto m = std::make_unique<NmGnbRlsToGtp>(NmGnbRlsToGtp::DATA_PDU_DELIVERY);
m->ueId = w.ueId;
m->psi = w.psi;
m->pdu = std::move(w.data);
m_base->gtpTask->push(std::move(m));
break;
}
case NmGnbRlsToRls::UPLINK_RRC: {
auto m = std::make_unique<NmGnbRlsToRrc>(NmGnbRlsToRrc::UPLINK_RRC);
m->ueId = w.ueId;
m->rrcChannel = w.rrcChannel;
m->data = std::move(w.data);
m_base->rrcTask->push(std::move(m));
break;
}
case NmGnbRlsToRls::RADIO_LINK_FAILURE: {
m_logger->debug("radio link failure [%d]", (int)w.rlfCause);
break;
}
case NmGnbRlsToRls::TRANSMISSION_FAILURE: {
m_logger->debug("transmission failure [%s]", "");
break;
}
default: {
m_logger->unhandledNts(*msg);
break;
}
}
break;
}
case NtsMessageType::GNB_RRC_TO_RLS: {
auto &w = dynamic_cast<NmGnbRrcToRls &>(*msg);
switch (w.present)
{
case NmGnbRrcToRls::RRC_PDU_DELIVERY: {
auto m = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::DOWNLINK_RRC);
m->ueId = w.ueId;
m->rrcChannel = w.channel;
m->pduId = 0;
m->data = std::move(w.pdu);
m_ctlTask->push(std::move(m));
break;
}
}
break;
}
case NtsMessageType::GNB_GTP_TO_RLS: {
auto &w = dynamic_cast<NmGnbGtpToRls &>(*msg);
switch (w.present)
{
case NmGnbGtpToRls::DATA_PDU_DELIVERY: {
auto m = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::DOWNLINK_DATA);
m->ueId = w.ueId;
m->psi = w.psi;
m->data = std::move(w.pdu);
m_ctlTask->push(std::move(m));
break;
}
}
break;
}
default:
m_logger->unhandledNts(*msg);
break;
}
}
void GnbRlsTask::onQuit()
{
m_udpTask->quit();
m_ctlTask->quit();
delete m_udpTask;
delete m_ctlTask;
}
} // namespace nr::gnb

52
src/gnb/rls/task.hpp vendored Normal file
View File

@@ -0,0 +1,52 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include "ctl_task.hpp"
#include "udp_task.hpp"
#include <memory>
#include <thread>
#include <unordered_map>
#include <vector>
#include <gnb/nts.hpp>
#include <gnb/types.hpp>
#include <lib/rls/rls_pdu.hpp>
#include <lib/udp/server_task.hpp>
#include <utils/logger.hpp>
#include <utils/nts.hpp>
namespace nr::gnb
{
class GnbRlsTask : public NtsTask
{
private:
TaskBase *m_base;
std::unique_ptr<Logger> m_logger;
RlsUdpTask *m_udpTask;
RlsControlTask *m_ctlTask;
uint64_t m_sti;
friend class GnbCmdHandler;
public:
explicit GnbRlsTask(TaskBase *base);
~GnbRlsTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
};
} // namespace nr::gnb

201
src/gnb/rls/udp_task.cpp Normal file
View File

@@ -0,0 +1,201 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "udp_task.hpp"
#include <cmath>
#include <cstdint>
#include <cstring>
#include <set>
#include <gnb/nts.hpp>
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utils/libc_error.hpp>
static constexpr const int BUFFER_SIZE = 16384;
static constexpr const int LOOP_PERIOD = 1000;
static constexpr const int RECEIVE_TIMEOUT = 200;
static constexpr const int HEARTBEAT_THRESHOLD = 2000; // (LOOP_PERIOD + RECEIVE_TIMEOUT)'dan büyük olmalı
static constexpr const int MIN_ALLOWED_DBM = -120;
static int EstimateSimulatedDbm(const Vector3 &myPos, const Vector3 &uePos)
{
int deltaX = myPos.x - uePos.x;
int deltaY = myPos.y - uePos.y;
int deltaZ = myPos.z - uePos.z;
int distance = static_cast<int>(std::sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ));
if (distance == 0)
return -1; // 0 may be confusing for people
return -distance;
}
namespace nr::gnb
{
RlsUdpTask::RlsUdpTask(TaskBase *base, uint64_t sti, Vector3 phyLocation)
: m_server{}, m_ctlTask{}, m_sti{sti}, m_phyLocation{phyLocation}, m_lastLoop{}, m_stiToUe{}, m_ueMap{}, m_newIdCounter{}
{
m_logger = base->logBase->makeUniqueLogger("rls-udp");
try
{
m_server = new udp::UdpServer(base->config->linkIp, cons::RadioLinkPort);
}
catch (const LibError &e)
{
m_logger->err("RLS failure [%s]", e.what());
quit();
return;
}
}
void RlsUdpTask::onStart()
{
}
void RlsUdpTask::onLoop()
{
auto current = utils::CurrentTimeMillis();
if (current - m_lastLoop > LOOP_PERIOD)
{
m_lastLoop = current;
heartbeatCycle(current);
}
uint8_t buffer[BUFFER_SIZE];
InetAddress peerAddress;
int size = m_server->Receive(buffer, BUFFER_SIZE, RECEIVE_TIMEOUT, peerAddress);
if (size > 0)
{
auto rlsMsg = rls::DecodeRlsMessage(OctetView{buffer, static_cast<size_t>(size)});
if (rlsMsg == nullptr)
m_logger->err("Unable to decode RLS message");
else
receiveRlsPdu(peerAddress, std::move(rlsMsg));
}
}
void RlsUdpTask::onQuit()
{
delete m_server;
}
void RlsUdpTask::receiveRlsPdu(const InetAddress &addr, std::unique_ptr<rls::RlsMessage> &&msg)
{
if (msg->msgType == rls::EMessageType::HEARTBEAT)
{
int dbm = EstimateSimulatedDbm(m_phyLocation, ((const rls::RlsHeartBeat &)*msg).simPos);
if (dbm < MIN_ALLOWED_DBM)
{
// if the simulated signal strength is such low, then ignore this message
return;
}
if (m_stiToUe.count(msg->sti))
{
int ueId = m_stiToUe[msg->sti];
m_ueMap[ueId].address = addr;
m_ueMap[ueId].lastSeen = utils::CurrentTimeMillis();
}
else
{
int ueId = ++m_newIdCounter;
m_stiToUe[msg->sti] = ueId;
m_ueMap[ueId].address = addr;
m_ueMap[ueId].lastSeen = utils::CurrentTimeMillis();
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::SIGNAL_DETECTED);
w->ueId = ueId;
m_ctlTask->push(std::move(w));
}
rls::RlsHeartBeatAck ack{m_sti};
ack.dbm = dbm;
sendRlsPdu(addr, ack);
return;
}
if (!m_stiToUe.count(msg->sti))
{
// if no HB received yet, and the message is not HB, then ignore the message
return;
}
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::RECEIVE_RLS_MESSAGE);
w->ueId = m_stiToUe[msg->sti];
w->msg = std::move(msg);
m_ctlTask->push(std::move(w));
}
void RlsUdpTask::sendRlsPdu(const InetAddress &addr, const rls::RlsMessage &msg)
{
OctetString stream;
rls::EncodeRlsMessage(msg, stream);
m_server->Send(addr, stream.data(), static_cast<size_t>(stream.length()));
}
void RlsUdpTask::heartbeatCycle(int64_t time)
{
std::set<int> lostUeId{};
std::set<uint64_t> lostSti{};
for (auto &item : m_ueMap)
{
if (time - item.second.lastSeen > HEARTBEAT_THRESHOLD)
{
lostUeId.insert(item.first);
lostSti.insert(item.second.sti);
}
}
for (uint64_t sti : lostSti)
m_stiToUe.erase(sti);
for (int ueId : lostUeId)
m_ueMap.erase(ueId);
for (int ueId : lostUeId)
{
auto w = std::make_unique<NmGnbRlsToRls>(NmGnbRlsToRls::SIGNAL_LOST);
w->ueId = ueId;
m_ctlTask->push(std::move(w));
}
}
void RlsUdpTask::initialize(NtsTask *ctlTask)
{
m_ctlTask = ctlTask;
}
void RlsUdpTask::send(int ueId, const rls::RlsMessage &msg)
{
if (ueId == 0)
{
for (auto &ue : m_ueMap)
send(ue.first, msg);
return;
}
if (!m_ueMap.count(ueId))
{
// ignore the message
return;
}
sendRlsPdu(m_ueMap[ueId].address, msg);
}
} // namespace nr::gnb

63
src/gnb/rls/udp_task.hpp vendored Normal file
View File

@@ -0,0 +1,63 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <cstdint>
#include <unordered_map>
#include <vector>
#include <gnb/types.hpp>
#include <lib/rls/rls_pdu.hpp>
#include <lib/udp/server.hpp>
#include <utils/nts.hpp>
namespace nr::gnb
{
class RlsUdpTask : public NtsTask
{
private:
struct UeInfo
{
uint64_t sti{};
InetAddress address;
int64_t lastSeen{};
};
private:
std::unique_ptr<Logger> m_logger;
udp::UdpServer *m_server;
NtsTask *m_ctlTask;
uint64_t m_sti;
Vector3 m_phyLocation;
int64_t m_lastLoop;
std::unordered_map<uint64_t, int> m_stiToUe;
std::unordered_map<int, UeInfo> m_ueMap;
int m_newIdCounter;
public:
explicit RlsUdpTask(TaskBase *base, uint64_t sti, Vector3 phyLocation);
~RlsUdpTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
private:
void receiveRlsPdu(const InetAddress &addr, std::unique_ptr<rls::RlsMessage> &&msg);
void sendRlsPdu(const InetAddress &addr, const rls::RlsMessage &msg);
void heartbeatCycle(int64_t time);
public:
void initialize(NtsTask *ctlTask);
void send(int ueId, const rls::RlsMessage &msg);
};
} // namespace nr::gnb

122
src/gnb/rrc/broadcast.cpp Normal file
View File

@@ -0,0 +1,122 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/ngap/task.hpp>
#include <lib/asn/rrc.hpp>
#include <lib/asn/utils.hpp>
#include <lib/rrc/encode.hpp>
#include <utils/common.hpp>
#include <asn/rrc/ASN_RRC_MIB.h>
#include <asn/rrc/ASN_RRC_PLMN-IdentityInfo.h>
#include <asn/rrc/ASN_RRC_PLMN-IdentityInfoList.h>
#include <asn/rrc/ASN_RRC_SIB1.h>
#include <asn/rrc/ASN_RRC_UAC-BarringInfoSet.h>
#include <asn/rrc/ASN_RRC_UAC-BarringInfoSetIndex.h>
#include <asn/rrc/ASN_RRC_UAC-BarringPerCat.h>
#include <asn/rrc/ASN_RRC_UAC-BarringPerCatList.h>
namespace nr::gnb
{
static ASN_RRC_BCCH_BCH_Message *ConstructMibMessage(bool barred, bool intraFreqReselectAllowed)
{
auto *pdu = asn::New<ASN_RRC_BCCH_BCH_Message>();
pdu->message.present = ASN_RRC_BCCH_BCH_MessageType_PR_mib;
pdu->message.choice.mib = asn::New<ASN_RRC_MIB>();
auto &mib = *pdu->message.choice.mib;
asn::SetBitStringInt<6>(0, mib.systemFrameNumber);
mib.subCarrierSpacingCommon = ASN_RRC_MIB__subCarrierSpacingCommon_scs15or60;
mib.ssb_SubcarrierOffset = 0;
mib.dmrs_TypeA_Position = ASN_RRC_MIB__dmrs_TypeA_Position_pos2;
mib.cellBarred = barred ? ASN_RRC_MIB__cellBarred_barred : ASN_RRC_MIB__cellBarred_notBarred;
mib.intraFreqReselection = intraFreqReselectAllowed ? ASN_RRC_MIB__intraFreqReselection_allowed
: ASN_RRC_MIB__intraFreqReselection_notAllowed;
asn::SetBitStringInt<1>(0, mib.spare);
mib.pdcch_ConfigSIB1.controlResourceSetZero = 0;
mib.pdcch_ConfigSIB1.searchSpaceZero = 0;
return pdu;
}
static ASN_RRC_BCCH_DL_SCH_Message *ConstructSib1Message(bool cellReserved, int tac, int64_t nci, const Plmn &plmn,
const UacAiBarringSet &aiBarringSet)
{
auto *pdu = asn::New<ASN_RRC_BCCH_DL_SCH_Message>();
pdu->message.present = ASN_RRC_BCCH_DL_SCH_MessageType_PR_c1;
pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1);
pdu->message.choice.c1->present = ASN_RRC_BCCH_DL_SCH_MessageType__c1_PR_systemInformationBlockType1;
pdu->message.choice.c1->choice.systemInformationBlockType1 = asn::New<ASN_RRC_SIB1>();
auto &sib1 = *pdu->message.choice.c1->choice.systemInformationBlockType1;
if (cellReserved)
{
asn::MakeNew(sib1.cellAccessRelatedInfo.cellReservedForOtherUse);
*sib1.cellAccessRelatedInfo.cellReservedForOtherUse =
ASN_RRC_CellAccessRelatedInfo__cellReservedForOtherUse_true;
}
auto *plmnInfo = asn::New<ASN_RRC_PLMN_IdentityInfo>();
plmnInfo->cellReservedForOperatorUse = cellReserved
? ASN_RRC_PLMN_IdentityInfo__cellReservedForOperatorUse_reserved
: ASN_RRC_PLMN_IdentityInfo__cellReservedForOperatorUse_notReserved;
asn::MakeNew(plmnInfo->trackingAreaCode);
asn::SetBitStringInt<24>(tac, *plmnInfo->trackingAreaCode);
asn::SetBitStringLong<36>(nci, plmnInfo->cellIdentity);
asn::SequenceAdd(plmnInfo->plmn_IdentityList, asn::rrc::NewPlmnId(plmn));
asn::SequenceAdd(sib1.cellAccessRelatedInfo.plmn_IdentityList, plmnInfo);
asn::MakeNew(sib1.uac_BarringInfo);
auto *info = asn::New<ASN_RRC_UAC_BarringInfoSet>();
info->uac_BarringFactor = ASN_RRC_UAC_BarringInfoSet__uac_BarringFactor_p50;
info->uac_BarringTime = ASN_RRC_UAC_BarringInfoSet__uac_BarringTime_s4;
asn::SetBitStringInt<7>(bits::Consequential8(false, aiBarringSet.ai1, aiBarringSet.ai2, aiBarringSet.ai11,
aiBarringSet.ai12, aiBarringSet.ai13, aiBarringSet.ai14,
aiBarringSet.ai15),
info->uac_BarringForAccessIdentity);
asn::SequenceAdd(sib1.uac_BarringInfo->uac_BarringInfoSetList, info);
asn::MakeNew(sib1.uac_BarringInfo->uac_BarringForCommon);
for (size_t i = 0; i < 63; i++)
{
auto *item = asn::New<ASN_RRC_UAC_BarringPerCat>();
item->accessCategory = static_cast<decltype(item->accessCategory)>(i + 1);
item->uac_barringInfoSetIndex = 1;
asn::SequenceAdd(*sib1.uac_BarringInfo->uac_BarringForCommon, item);
}
return pdu;
}
void GnbRrcTask::onBroadcastTimerExpired()
{
triggerSysInfoBroadcast();
}
void GnbRrcTask::triggerSysInfoBroadcast()
{
auto *mib = ConstructMibMessage(m_isBarred, m_intraFreqReselectAllowed);
auto *sib1 = ConstructSib1Message(m_cellReserved, m_config->tac, m_config->nci, m_config->plmn, m_aiBarringSet);
sendRrcMessage(mib);
sendRrcMessage(sib1);
asn::Free(asn_DEF_ASN_RRC_BCCH_BCH_Message, mib);
asn::Free(asn_DEF_ASN_RRC_BCCH_DL_SCH_Message, sib1);
}
} // namespace nr::gnb

227
src/gnb/rrc/channel.cpp Normal file
View File

@@ -0,0 +1,227 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/rls/task.hpp>
#include <lib/rrc/encode.hpp>
#include <asn/rrc/ASN_RRC_UL-CCCH-Message.h>
#include <asn/rrc/ASN_RRC_UL-DCCH-Message.h>
namespace nr::gnb
{
void GnbRrcTask::handleUplinkRrc(int ueId, rrc::RrcChannel channel, const OctetString &rrcPdu)
{
switch (channel)
{
case rrc::RrcChannel::BCCH_BCH: {
auto *pdu = rrc::encode::Decode<ASN_RRC_BCCH_BCH_Message>(asn_DEF_ASN_RRC_BCCH_BCH_Message, rrcPdu);
if (pdu == nullptr)
m_logger->err("RRC BCCH-BCH PDU decoding failed.");
else
receiveRrcMessage(ueId, pdu);
asn::Free(asn_DEF_ASN_RRC_BCCH_BCH_Message, pdu);
break;
}
case rrc::RrcChannel::UL_CCCH: {
auto *pdu = rrc::encode::Decode<ASN_RRC_UL_CCCH_Message>(asn_DEF_ASN_RRC_UL_CCCH_Message, rrcPdu);
if (pdu == nullptr)
m_logger->err("RRC UL-CCCH PDU decoding failed.");
else
receiveRrcMessage(ueId, pdu);
asn::Free(asn_DEF_ASN_RRC_UL_CCCH_Message, pdu);
break;
}
case rrc::RrcChannel::UL_CCCH1: {
auto *pdu = rrc::encode::Decode<ASN_RRC_UL_CCCH1_Message>(asn_DEF_ASN_RRC_UL_CCCH1_Message, rrcPdu);
if (pdu == nullptr)
m_logger->err("RRC UL-CCCH1 PDU decoding failed.");
else
receiveRrcMessage(ueId, pdu);
asn::Free(asn_DEF_ASN_RRC_UL_CCCH1_Message, pdu);
break;
}
case rrc::RrcChannel::UL_DCCH: {
auto *pdu = rrc::encode::Decode<ASN_RRC_UL_DCCH_Message>(asn_DEF_ASN_RRC_UL_DCCH_Message, rrcPdu);
if (pdu == nullptr)
m_logger->err("RRC UL-DCCH PDU decoding failed.");
else
receiveRrcMessage(ueId, pdu);
asn::Free(asn_DEF_ASN_RRC_UL_DCCH_Message, pdu);
break;
}
case rrc::RrcChannel::PCCH:
case rrc::RrcChannel::BCCH_DL_SCH:
case rrc::RrcChannel::DL_CCCH:
case rrc::RrcChannel::DL_DCCH:
break;
}
}
void GnbRrcTask::sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg)
{
OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_BCCH_BCH_Message, msg);
if (pdu.length() == 0)
{
m_logger->err("RRC BCCH-BCH encoding failed.");
return;
}
auto w = std::make_unique<NmGnbRrcToRls>(NmGnbRrcToRls::RRC_PDU_DELIVERY);
w->ueId = 0;
w->channel = rrc::RrcChannel::BCCH_BCH;
w->pdu = std::move(pdu);
m_base->rlsTask->push(std::move(w));
}
void GnbRrcTask::sendRrcMessage(ASN_RRC_BCCH_DL_SCH_Message *msg)
{
OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_BCCH_DL_SCH_Message, msg);
if (pdu.length() == 0)
{
m_logger->err("RRC BCCH-DL-SCH encoding failed.");
return;
}
auto w = std::make_unique<NmGnbRrcToRls>(NmGnbRrcToRls::RRC_PDU_DELIVERY);
w->ueId = 0;
w->channel = rrc::RrcChannel::BCCH_DL_SCH;
w->pdu = std::move(pdu);
m_base->rlsTask->push(std::move(w));
}
void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg)
{
OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_DL_CCCH_Message, msg);
if (pdu.length() == 0)
{
m_logger->err("RRC DL-CCCH encoding failed.");
return;
}
auto w = std::make_unique<NmGnbRrcToRls>(NmGnbRrcToRls::RRC_PDU_DELIVERY);
w->ueId = ueId;
w->channel = rrc::RrcChannel::DL_CCCH;
w->pdu = std::move(pdu);
m_base->rlsTask->push(std::move(w));
}
void GnbRrcTask::sendRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg)
{
OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_DL_DCCH_Message, msg);
if (pdu.length() == 0)
{
m_logger->err("RRC DL-DCCH encoding failed.");
return;
}
auto w = std::make_unique<NmGnbRrcToRls>(NmGnbRrcToRls::RRC_PDU_DELIVERY);
w->ueId = ueId;
w->channel = rrc::RrcChannel::DL_DCCH;
w->pdu = std::move(pdu);
m_base->rlsTask->push(std::move(w));
}
void GnbRrcTask::sendRrcMessage(ASN_RRC_PCCH_Message *msg)
{
OctetString pdu = rrc::encode::EncodeS(asn_DEF_ASN_RRC_PCCH_Message, msg);
if (pdu.length() == 0)
{
m_logger->err("RRC PCCH encoding failed.");
return;
}
auto w = std::make_unique<NmGnbRrcToRls>(NmGnbRrcToRls::RRC_PDU_DELIVERY);
w->ueId = 0;
w->channel = rrc::RrcChannel::PCCH;
w->pdu = std::move(pdu);
m_base->rlsTask->push(std::move(w));
}
void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg)
{
// TODO
}
void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_UL_CCCH_Message *msg)
{
if (msg->message.present != ASN_RRC_UL_CCCH_MessageType_PR_c1)
return;
auto &c1 = msg->message.choice.c1;
switch (c1->present)
{
case ASN_RRC_UL_CCCH_MessageType__c1_PR_NOTHING:
return;
case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcSetupRequest:
receiveRrcSetupRequest(ueId, *c1->choice.rrcSetupRequest);
break;
case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcResumeRequest:
break; // todo
case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcReestablishmentRequest:
break; // todo
case ASN_RRC_UL_CCCH_MessageType__c1_PR_rrcSystemInfoRequest:
break; // todo
}
}
void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_UL_CCCH1_Message *msg)
{
// TODO
}
void GnbRrcTask::receiveRrcMessage(int ueId, ASN_RRC_UL_DCCH_Message *msg)
{
if (msg->message.present != ASN_RRC_UL_DCCH_MessageType_PR_c1)
return;
auto &c1 = msg->message.choice.c1;
switch (c1->present)
{
case ASN_RRC_UL_DCCH_MessageType__c1_PR_NOTHING:
return;
case ASN_RRC_UL_DCCH_MessageType__c1_PR_measurementReport:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_rrcReconfigurationComplete:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_rrcSetupComplete:
receiveRrcSetupComplete(ueId, *c1->choice.rrcSetupComplete);
break;
case ASN_RRC_UL_DCCH_MessageType__c1_PR_rrcReestablishmentComplete:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_rrcResumeComplete:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_securityModeComplete:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_securityModeFailure:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_ulInformationTransfer:
receiveUplinkInformationTransfer(ueId, *c1->choice.ulInformationTransfer);
break;
case ASN_RRC_UL_DCCH_MessageType__c1_PR_locationMeasurementIndication:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_ueCapabilityInformation:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_counterCheckResponse:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_ueAssistanceInformation:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_failureInformation:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_ulInformationTransferMRDC:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_scgFailureInformation:
break; // TODO
case ASN_RRC_UL_DCCH_MessageType__c1_PR_scgFailureInformationEUTRA:
break; // TODO
}
}
} // namespace nr::gnb

135
src/gnb/rrc/connection.cpp Normal file
View File

@@ -0,0 +1,135 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/ngap/task.hpp>
#include <lib/rrc/encode.hpp>
#include <asn/ngap/ASN_NGAP_FiveG-S-TMSI.h>
#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>
#include <asn/rrc/ASN_RRC_DL-CCCH-Message.h>
#include <asn/rrc/ASN_RRC_DL-DCCH-Message.h>
#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_Paging.h>
#include <asn/rrc/ASN_RRC_PagingRecord.h>
#include <asn/rrc/ASN_RRC_PagingRecordList.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>
#include <asn/rrc/ASN_RRC_RRCSetupComplete.h>
#include <asn/rrc/ASN_RRC_RRCSetupRequest.h>
#include <asn/rrc/ASN_RRC_UL-CCCH-Message.h>
#include <asn/rrc/ASN_RRC_UL-CCCH1-Message.h>
#include <asn/rrc/ASN_RRC_UL-DCCH-Message.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer-IEs.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer.h>
namespace nr::gnb
{
void GnbRrcTask::receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg)
{
auto *ue = tryFindUe(ueId);
if (ue)
{
// TODO: handle this more properly
m_logger->warn("Discarding RRC Setup Request, UE context already exists");
return;
}
if (msg.rrcSetupRequest.ue_Identity.present == ASN_RRC_InitialUE_Identity_PR_NOTHING)
{
m_logger->err("Bad constructed RRC message ignored");
return;
}
ue = createUe(ueId);
if (msg.rrcSetupRequest.ue_Identity.present == ASN_RRC_InitialUE_Identity_PR_ng_5G_S_TMSI_Part1)
{
ue->initialId = asn::GetBitStringLong<39>(msg.rrcSetupRequest.ue_Identity.choice.ng_5G_S_TMSI_Part1);
ue->isInitialIdSTmsi = true;
}
else
{
ue->initialId = asn::GetBitStringLong<39>(msg.rrcSetupRequest.ue_Identity.choice.randomValue);
ue->isInitialIdSTmsi = false;
}
ue->establishmentCause = static_cast<int64_t>(msg.rrcSetupRequest.establishmentCause);
// Prepare RRC Setup
auto *pdu = asn::New<ASN_RRC_DL_CCCH_Message>();
pdu->message.present = ASN_RRC_DL_CCCH_MessageType_PR_c1;
pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1);
pdu->message.choice.c1->present = ASN_RRC_DL_CCCH_MessageType__c1_PR_rrcSetup;
auto &rrcSetup = pdu->message.choice.c1->choice.rrcSetup = asn::New<ASN_RRC_RRCSetup>();
rrcSetup->rrc_TransactionIdentifier = getNextTid();
rrcSetup->criticalExtensions.present = ASN_RRC_RRCSetup__criticalExtensions_PR_rrcSetup;
auto &rrcSetupIEs = rrcSetup->criticalExtensions.choice.rrcSetup = asn::New<ASN_RRC_RRCSetup_IEs>();
ASN_RRC_CellGroupConfig masterCellGroup{};
masterCellGroup.cellGroupId = 0;
asn::SetOctetString(rrcSetupIEs->masterCellGroup,
rrc::encode::EncodeS(asn_DEF_ASN_RRC_CellGroupConfig, &masterCellGroup));
m_logger->info("RRC Setup for UE[%d]", ueId);
sendRrcMessage(ueId, pdu);
asn::Free(asn_DEF_ASN_RRC_DL_CCCH_Message, pdu);
}
void GnbRrcTask::receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplete &msg)
{
auto *ue = findUe(ueId);
if (!ue)
return;
auto setupComplete = msg.criticalExtensions.choice.rrcSetupComplete;
if (msg.criticalExtensions.choice.rrcSetupComplete)
{
// Handle received 5G S-TMSI if any
if (msg.criticalExtensions.choice.rrcSetupComplete->ng_5G_S_TMSI_Value)
{
ue->sTmsi = std::nullopt;
auto &sTmsiValue = msg.criticalExtensions.choice.rrcSetupComplete->ng_5G_S_TMSI_Value;
if (sTmsiValue->present == ASN_RRC_RRCSetupComplete_IEs__ng_5G_S_TMSI_Value_PR_ng_5G_S_TMSI)
{
ue->sTmsi = GutiMobileIdentity::FromSTmsi(asn::GetBitStringLong<48>(sTmsiValue->choice.ng_5G_S_TMSI));
}
else if (sTmsiValue->present == ASN_RRC_RRCSetupComplete_IEs__ng_5G_S_TMSI_Value_PR_ng_5G_S_TMSI_Part2)
{
if (ue->isInitialIdSTmsi)
{
int64_t part2 = asn::GetBitStringLong<9>(sTmsiValue->choice.ng_5G_S_TMSI_Part2);
ue->sTmsi = GutiMobileIdentity::FromSTmsi((part2 << 39) | (ue->initialId));
}
}
}
}
auto w = std::make_unique<NmGnbRrcToNgap>(NmGnbRrcToNgap::INITIAL_NAS_DELIVERY);
w->ueId = ueId;
w->pdu = asn::GetOctetString(setupComplete->dedicatedNAS_Message);
w->rrcEstablishmentCause = ue->establishmentCause;
w->sTmsi = ue->sTmsi;
m_base->ngapTask->push(std::move(w));
}
} // namespace nr::gnb

137
src/gnb/rrc/handler.cpp Normal file
View File

@@ -0,0 +1,137 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/ngap/task.hpp>
#include <lib/rrc/encode.hpp>
#include <asn/ngap/ASN_NGAP_FiveG-S-TMSI.h>
#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>
#include <asn/rrc/ASN_RRC_DL-CCCH-Message.h>
#include <asn/rrc/ASN_RRC_DL-DCCH-Message.h>
#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_Paging.h>
#include <asn/rrc/ASN_RRC_PagingRecord.h>
#include <asn/rrc/ASN_RRC_PagingRecordList.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>
#include <asn/rrc/ASN_RRC_RRCSetupComplete.h>
#include <asn/rrc/ASN_RRC_RRCSetupRequest.h>
#include <asn/rrc/ASN_RRC_UL-CCCH-Message.h>
#include <asn/rrc/ASN_RRC_UL-CCCH1-Message.h>
#include <asn/rrc/ASN_RRC_UL-DCCH-Message.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer-IEs.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer.h>
namespace nr::gnb
{
void GnbRrcTask::handleDownlinkNasDelivery(int ueId, const OctetString &nasPdu)
{
auto *pdu = asn::New<ASN_RRC_DL_DCCH_Message>();
pdu->message.present = ASN_RRC_DL_DCCH_MessageType_PR_c1;
pdu->message.choice.c1 =
asn::New<ASN_RRC_DL_DCCH_MessageType_t::ASN_RRC_DL_DCCH_MessageType_u::ASN_RRC_DL_DCCH_MessageType__c1>();
pdu->message.choice.c1->present = ASN_RRC_DL_DCCH_MessageType__c1_PR_dlInformationTransfer;
pdu->message.choice.c1->choice.dlInformationTransfer = asn::New<ASN_RRC_DLInformationTransfer>();
auto &c1 = pdu->message.choice.c1->choice.dlInformationTransfer->criticalExtensions;
c1.present = ASN_RRC_DLInformationTransfer__criticalExtensions_PR_dlInformationTransfer;
c1.choice.dlInformationTransfer = asn::New<ASN_RRC_DLInformationTransfer_IEs>();
c1.choice.dlInformationTransfer->dedicatedNAS_Message = asn::New<ASN_RRC_DedicatedNAS_Message_t>();
asn::SetOctetString(*c1.choice.dlInformationTransfer->dedicatedNAS_Message, nasPdu);
sendRrcMessage(ueId, pdu);
asn::Free(asn_DEF_ASN_RRC_DL_DCCH_Message, pdu);
}
void GnbRrcTask::deliverUplinkNas(int ueId, OctetString &&nasPdu)
{
auto w = std::make_unique<NmGnbRrcToNgap>(NmGnbRrcToNgap::UPLINK_NAS_DELIVERY);
w->ueId = ueId;
w->pdu = std::move(nasPdu);
m_base->ngapTask->push(std::move(w));
}
void GnbRrcTask::receiveUplinkInformationTransfer(int ueId, const ASN_RRC_ULInformationTransfer &msg)
{
if (msg.criticalExtensions.present == ASN_RRC_ULInformationTransfer__criticalExtensions_PR_ulInformationTransfer)
deliverUplinkNas(
ueId, asn::GetOctetString(*msg.criticalExtensions.choice.ulInformationTransfer->dedicatedNAS_Message));
}
void GnbRrcTask::releaseConnection(int ueId)
{
m_logger->info("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);
asn::Free(asn_DEF_ASN_RRC_DL_DCCH_Message, pdu);
// Delete UE RRC context
m_ueCtx.erase(ueId);
}
void GnbRrcTask::handleRadioLinkFailure(int ueId)
{
// Notify NGAP task
auto w = std::make_unique<NmGnbRrcToNgap>(NmGnbRrcToNgap::RADIO_LINK_FAILURE);
w->ueId = ueId;
m_base->ngapTask->push(std::move(w));
// Delete UE RRC context
m_ueCtx.erase(ueId);
}
void GnbRrcTask::handlePaging(const asn::Unique<ASN_NGAP_FiveG_S_TMSI> &tmsi,
const asn::Unique<ASN_NGAP_TAIListForPaging> &taiList)
{
// Construct and send a Paging message
auto *pdu = asn::New<ASN_RRC_PCCH_Message>();
pdu->message.present = ASN_RRC_PCCH_MessageType_PR_c1;
pdu->message.choice.c1 = asn::NewFor(pdu->message.choice.c1);
pdu->message.choice.c1->present = ASN_RRC_PCCH_MessageType__c1_PR_paging;
auto &paging = pdu->message.choice.c1->choice.paging = asn::New<ASN_RRC_Paging>();
auto *record = asn::New<ASN_RRC_PagingRecord>();
record->ue_Identity.present = ASN_RRC_PagingUE_Identity_PR_ng_5G_S_TMSI;
OctetString tmsiOctets{};
tmsiOctets.appendOctet2(bits::Ranged16({
{10, asn::GetBitStringInt<10>(tmsi->aMFSetID)},
{6, asn::GetBitStringInt<10>(tmsi->aMFPointer)},
}));
tmsiOctets.append(asn::GetOctetString(tmsi->fiveG_TMSI));
asn::SetBitString(record->ue_Identity.choice.ng_5G_S_TMSI, tmsiOctets);
paging->pagingRecordList = asn::NewFor(paging->pagingRecordList);
asn::SequenceAdd(*paging->pagingRecordList, record);
sendRrcMessage(pdu);
asn::Free(asn_DEF_ASN_RRC_PCCH_Message, pdu);
}
} // namespace nr::gnb

View File

@@ -0,0 +1,21 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
namespace nr::gnb
{
int GnbRrcTask::getNextTid()
{
m_tidCounter++;
m_tidCounter %= 4;
return m_tidCounter;
}
} // namespace nr::gnb

33
src/gnb/rrc/sap.cpp Normal file
View File

@@ -0,0 +1,33 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/ngap/task.hpp>
#include <lib/rrc/encode.hpp>
namespace nr::gnb
{
void GnbRrcTask::handleRlsSapMessage(NmGnbRlsToRrc &msg)
{
switch (msg.present)
{
case NmGnbRlsToRrc::SIGNAL_DETECTED: {
m_logger->debug("UE[%d] new signal detected", msg.ueId);
triggerSysInfoBroadcast();
break;
}
case NmGnbRlsToRrc::UPLINK_RRC: {
handleUplinkRrc(msg.ueId, msg.rrcChannel, msg.data);
break;
}
}
}
} // namespace nr::gnb

90
src/gnb/rrc/task.cpp Normal file
View File

@@ -0,0 +1,90 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/nts.hpp>
#include <gnb/rls/task.hpp>
#include <lib/rrc/encode.hpp>
#include <asn/rrc/ASN_RRC_DLInformationTransfer-IEs.h>
#include <asn/rrc/ASN_RRC_DLInformationTransfer.h>
static constexpr const int TIMER_ID_SI_BROADCAST = 1;
static constexpr const int TIMER_PERIOD_SI_BROADCAST = 10'000;
namespace nr::gnb
{
GnbRrcTask::GnbRrcTask(TaskBase *base) : m_base{base}, m_ueCtx{}, m_tidCounter{}
{
m_logger = base->logBase->makeUniqueLogger("rrc");
m_config = m_base->config;
}
void GnbRrcTask::onStart()
{
setTimer(TIMER_ID_SI_BROADCAST, TIMER_PERIOD_SI_BROADCAST);
}
void GnbRrcTask::onQuit()
{
// todo
}
void GnbRrcTask::onLoop()
{
auto msg = take();
if (!msg)
return;
switch (msg->msgType)
{
case NtsMessageType::GNB_RLS_TO_RRC: {
handleRlsSapMessage(dynamic_cast<NmGnbRlsToRrc &>(*msg));
break;
}
case NtsMessageType::GNB_NGAP_TO_RRC: {
auto &w = dynamic_cast<NmGnbNgapToRrc &>(*msg);
switch (w.present)
{
case NmGnbNgapToRrc::RADIO_POWER_ON: {
m_isBarred = false;
triggerSysInfoBroadcast();
break;
}
case NmGnbNgapToRrc::NAS_DELIVERY: {
handleDownlinkNasDelivery(w.ueId, w.pdu);
break;
}
case NmGnbNgapToRrc::AN_RELEASE: {
releaseConnection(w.ueId);
break;
}
case NmGnbNgapToRrc::PAGING:
handlePaging(w.uePagingTmsi, w.taiListForPaging);
break;
}
break;
}
case NtsMessageType::TIMER_EXPIRED: {
auto w = dynamic_cast<NmTimerExpired &>(*msg);
if (w.timerId == TIMER_ID_SI_BROADCAST)
{
setTimer(TIMER_ID_SI_BROADCAST, TIMER_PERIOD_SI_BROADCAST);
onBroadcastTimerExpired();
}
break;
}
default:
m_logger->unhandledNts(*msg);
break;
}
}
} // namespace nr::gnb

112
src/gnb/rrc/task.hpp vendored Normal file
View File

@@ -0,0 +1,112 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <memory>
#include <thread>
#include <unordered_map>
#include <vector>
#include <gnb/nts.hpp>
#include <utils/logger.hpp>
#include <utils/nts.hpp>
extern "C"
{
struct ASN_RRC_BCCH_BCH_Message;
struct ASN_RRC_BCCH_DL_SCH_Message;
struct ASN_RRC_DL_CCCH_Message;
struct ASN_RRC_DL_DCCH_Message;
struct ASN_RRC_PCCH_Message;
struct ASN_RRC_UL_CCCH_Message;
struct ASN_RRC_UL_CCCH1_Message;
struct ASN_RRC_UL_DCCH_Message;
struct ASN_RRC_RRCSetupRequest;
struct ASN_RRC_RRCSetupComplete;
struct ASN_RRC_ULInformationTransfer;
}
namespace nr::gnb
{
class NgapTask;
class GnbRrcTask : public NtsTask
{
private:
TaskBase *m_base;
GnbConfig *m_config;
std::unique_ptr<Logger> m_logger;
std::unordered_map<int, RrcUeContext *> m_ueCtx;
int m_tidCounter;
bool m_isBarred = true;
bool m_cellReserved = false;
UacAiBarringSet m_aiBarringSet = {};
bool m_intraFreqReselectAllowed = true;
friend class GnbCmdHandler;
public:
explicit GnbRrcTask(TaskBase *base);
~GnbRrcTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
private:
/* Management */
int getNextTid();
/* Handlers */
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 handlePaging(const asn::Unique<ASN_NGAP_FiveG_S_TMSI> &tmsi,
const asn::Unique<ASN_NGAP_TAIListForPaging> &taiList);
void receiveUplinkInformationTransfer(int ueId, const ASN_RRC_ULInformationTransfer &msg);
/* RRC channel send message */
void sendRrcMessage(ASN_RRC_BCCH_BCH_Message *msg);
void sendRrcMessage(ASN_RRC_BCCH_DL_SCH_Message *msg);
void sendRrcMessage(int ueId, ASN_RRC_DL_CCCH_Message *msg);
void sendRrcMessage(int ueId, ASN_RRC_DL_DCCH_Message *msg);
void sendRrcMessage(ASN_RRC_PCCH_Message *msg);
/* RRC channel receive message */
void receiveRrcMessage(int ueId, ASN_RRC_BCCH_BCH_Message *msg);
void receiveRrcMessage(int ueId, ASN_RRC_UL_CCCH_Message *msg);
void receiveRrcMessage(int ueId, ASN_RRC_UL_CCCH1_Message *msg);
void receiveRrcMessage(int ueId, ASN_RRC_UL_DCCH_Message *msg);
/* System Information Broadcast related */
void onBroadcastTimerExpired();
void triggerSysInfoBroadcast();
/* Service Access Point */
void handleRlsSapMessage(NmGnbRlsToRrc &msg);
/* UE Management */
RrcUeContext *createUe(int id);
RrcUeContext *tryFindUe(int id);
RrcUeContext *findUe(int id);
/* Connection Control */
void receiveRrcSetupRequest(int ueId, const ASN_RRC_RRCSetupRequest &msg);
void receiveRrcSetupComplete(int ueId, const ASN_RRC_RRCSetupComplete &msg);
};
} // namespace nr::gnb

42
src/gnb/rrc/ues.cpp Normal file
View File

@@ -0,0 +1,42 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <gnb/ngap/task.hpp>
#include <lib/rrc/encode.hpp>
namespace nr::gnb
{
RrcUeContext *GnbRrcTask::createUe(int id)
{
auto *ctx = new RrcUeContext(id);
m_ueCtx[id] = ctx;
return ctx;
}
RrcUeContext *GnbRrcTask::tryFindUe(int id)
{
if (m_ueCtx.count(id))
return m_ueCtx[id];
return nullptr;
}
RrcUeContext *GnbRrcTask::findUe(int id)
{
auto *ue = tryFindUe(id);
if (ue == nullptr)
{
m_logger->err("UE context with ID[%d] not found", id);
return ue;
}
return ue;
}
} // namespace nr::gnb

335
src/gnb/sctp/task.cpp Normal file
View File

@@ -0,0 +1,335 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include "task.hpp"
#include <cstring>
#include <thread>
#include <utility>
// #define MOCKED_PACKETS
#ifdef MOCKED_PACKETS
static std::string MOCK_LIST[] = {
std::string(
"201500320000040001000e05806f70656e3567732d616d663000600008000009f10702004000564001ff005000080009f10700000008"),
std::string("0004403e000003000a000200060055000200010026002b2a7e005600020000215660aed893ca993801ad60ba50575cb020101c"
"016820d4f980"
"004ff8987b0bfcbbb8"),
std::string("00044029000003000a0002000600550002000100260016157e03cbc05c7b007e005d020004f0f0f0f0e1360102"),
std::string("000e00809e000009000a00020006005500020001006e000a0c3e800000303e800000001c00070009f107020040000000020001"
"007700091c00"
"0e000700038000005e002091f9908eefe559d08b9f958b4d7c1a94094918dedcf40bb57c55428a00d4dee90022400835693803"
"56fffff00026"
"402f2e7e02687a0bd3017e0042010177000bf209f107020040e700acbe54072009f10700000115020101210201005e0129"),
std::string("0004403c000003000a0002000600550002000100260029287e02f41c3d78027e0054430f10004f00700065006e003500470053"
"462147121062"
"41547021490100"),
};
static int MOCK_INDEX = -1;
#endif
namespace nr::gnb
{
class SctpHandler : public sctp::ISctpHandler
{
private:
SctpTask *const sctpTask;
int clientId;
public:
SctpHandler(SctpTask *const sctpTask, int clientId) : sctpTask(sctpTask), clientId(clientId)
{
}
private:
void onAssociationSetup(int associationId, int inStreams, int outStreams) override
{
auto w = std::make_unique<NmGnbSctp>(NmGnbSctp::ASSOCIATION_SETUP);
w->clientId = clientId;
w->associationId = associationId;
w->inStreams = inStreams;
w->outStreams = outStreams;
sctpTask->push(std::move(w));
}
void onAssociationShutdown() override
{
auto w = std::make_unique<NmGnbSctp>(NmGnbSctp::ASSOCIATION_SHUTDOWN);
w->clientId = clientId;
sctpTask->push(std::move(w));
}
void onMessage(const uint8_t *buffer, size_t length, uint16_t stream) override
{
auto *data = new uint8_t[length];
std::memcpy(data, buffer, length);
auto w = std::make_unique<NmGnbSctp>(NmGnbSctp::RECEIVE_MESSAGE);
w->clientId = clientId;
w->buffer = UniqueBuffer{data, length};
w->stream = stream;
sctpTask->push(std::move(w));
}
void onUnhandledNotification() override
{
auto w = std::make_unique<NmGnbSctp>(NmGnbSctp::UNHANDLED_NOTIFICATION);
w->clientId = clientId;
sctpTask->push(std::move(w));
}
void onConnectionReset() override
{
auto w = std::make_unique<NmGnbSctp>(NmGnbSctp::UNHANDLED_NOTIFICATION);
w->clientId = clientId;
sctpTask->push(std::move(w));
}
};
[[noreturn]] static void ReceiverThread(std::pair<sctp::SctpClient *, sctp::ISctpHandler *> *args)
{
sctp::SctpClient *client = args->first;
sctp::ISctpHandler *handler = args->second;
delete args;
while (true)
client->receive(handler);
}
SctpTask::SctpTask(TaskBase *base) : m_base{base}, m_clients{}
{
m_logger = base->logBase->makeUniqueLogger("sctp");
}
void SctpTask::onStart()
{
}
void SctpTask::onLoop()
{
auto msg = take();
if (!msg)
return;
switch (msg->msgType)
{
case NtsMessageType::GNB_SCTP: {
auto& w = dynamic_cast<NmGnbSctp &>(*msg);
switch (w.present)
{
case NmGnbSctp::CONNECTION_REQUEST: {
receiveSctpConnectionSetupRequest(w.clientId, w.localAddress, w.localPort, w.remoteAddress,
w.remotePort, w.ppid, w.associatedTask);
break;
}
case NmGnbSctp::CONNECTION_CLOSE: {
receiveConnectionClose(w.clientId);
break;
}
case NmGnbSctp::ASSOCIATION_SETUP: {
receiveAssociationSetup(w.clientId, w.associationId, w.inStreams, w.outStreams);
break;
}
case NmGnbSctp::ASSOCIATION_SHUTDOWN: {
receiveAssociationShutdown(w.clientId);
break;
}
case NmGnbSctp::RECEIVE_MESSAGE: {
receiveClientReceive(w.clientId, w.stream, std::move(w.buffer));
break;
}
case NmGnbSctp::SEND_MESSAGE: {
receiveSendMessage(w.clientId, w.stream, std::move(w.buffer));
break;
}
case NmGnbSctp::UNHANDLED_NOTIFICATION: {
receiveUnhandledNotification(w.clientId);
break;
}
default:
m_logger->unhandledNts(*msg);
break;
}
break;
}
default:
m_logger->unhandledNts(*msg);
break;
}
}
void SctpTask::onQuit()
{
for (auto &client : m_clients)
{
ClientEntry *entry = client.second;
DeleteClientEntry(entry);
}
m_clients.clear();
}
void SctpTask::DeleteClientEntry(ClientEntry *entry)
{
entry->associatedTask = nullptr;
delete entry->receiverThread;
delete entry->client;
delete entry->handler;
delete entry;
}
void SctpTask::receiveSctpConnectionSetupRequest(int clientId, const std::string &localAddress, uint16_t localPort,
const std::string &remoteAddress, uint16_t remotePort,
sctp::PayloadProtocolId ppid, NtsTask *associatedTask)
{
m_logger->info("Trying to establish SCTP connection... (%s:%d)", remoteAddress.c_str(), remotePort);
auto *client = new sctp::SctpClient(ppid, localAddress);
try
{
client->bind(localAddress, localPort);
}
catch (const sctp::SctpError &exc)
{
m_logger->err("Binding to %s:%d failed. %s", localAddress.c_str(), localPort, exc.what());
delete client;
return;
}
try
{
client->connect(remoteAddress, remotePort);
}
catch (const sctp::SctpError &exc)
{
m_logger->err("Connecting to %s:%d failed. %s", remoteAddress.c_str(), remotePort, exc.what());
delete client;
return;
}
m_logger->info("SCTP connection established (%s:%d)", remoteAddress.c_str(), remotePort);
sctp::ISctpHandler *handler = new SctpHandler(this, clientId);
auto *entry = new ClientEntry;
m_clients[clientId] = entry;
entry->id = clientId;
entry->client = client;
entry->handler = handler;
entry->associatedTask = associatedTask;
entry->receiverThread = new ScopedThread(
[](void *arg) { ReceiverThread(reinterpret_cast<std::pair<sctp::SctpClient *, sctp::ISctpHandler *> *>(arg)); },
new std::pair<sctp::SctpClient *, sctp::ISctpHandler *>(client, handler));
}
void SctpTask::receiveAssociationSetup(int clientId, int associationId, int inStreams, int outStreams)
{
m_logger->debug("SCTP association setup ascId[%d]", associationId);
ClientEntry *entry = m_clients[clientId];
if (entry == nullptr)
{
m_logger->warn("Client entry not found for id: %d", clientId);
return;
}
// Notify the relevant task
auto msg = std::make_unique<NmGnbSctp>(NmGnbSctp::ASSOCIATION_SETUP);
msg->clientId = clientId;
msg->associationId = associationId;
msg->inStreams = inStreams;
msg->outStreams = outStreams;
entry->associatedTask->push(std::move(msg));
}
void SctpTask::receiveAssociationShutdown(int clientId)
{
m_logger->debug("SCTP association shutdown (clientId: %d)", clientId);
ClientEntry *entry = m_clients[clientId];
if (entry == nullptr)
{
m_logger->warn("Client entry not found for id: %d", clientId);
return;
}
// Notify the relevant task
auto msg = std::make_unique<NmGnbSctp>(NmGnbSctp::ASSOCIATION_SHUTDOWN);
msg->clientId = clientId;
entry->associatedTask->push(std::move(msg));
}
void SctpTask::receiveClientReceive(int clientId, uint16_t stream, UniqueBuffer &&buffer)
{
ClientEntry *entry = m_clients[clientId];
if (entry == nullptr)
{
m_logger->warn("Client entry not found for id: %d", clientId);
return;
}
// Notify the relevant task
auto msg = std::make_unique<NmGnbSctp>(NmGnbSctp::RECEIVE_MESSAGE);
msg->clientId = clientId;
msg->stream = stream;
msg->buffer = std::move(buffer);
entry->associatedTask->push(std::move(msg));
}
void SctpTask::receiveUnhandledNotification(int clientId)
{
// NOTE: For unhandled notifications, "clientId" may be invalid for some notifications.
// Because some notification may be received after shutdown.
// Print warning
m_logger->warn("Unhandled SCTP notification received");
}
void SctpTask::receiveConnectionClose(int clientId)
{
ClientEntry *entry = m_clients[clientId];
if (entry == nullptr)
{
m_logger->warn("Client entry not found for id: %d", clientId);
return;
}
DeleteClientEntry(entry);
}
void SctpTask::receiveSendMessage(int clientId, uint16_t stream, UniqueBuffer &&buffer)
{
ClientEntry *entry = m_clients[clientId];
if (entry == nullptr)
{
m_logger->warn("Client entry not found for id: %d", clientId);
return;
}
#ifdef MOCKED_PACKETS
{
std::string ss = MOCK_LIST[++MOCK_INDEX];
OctetString data = OctetString::FromHex(ss);
auto *copy = new uint8_t[data.length()];
std::memcpy(copy, data.data(), data.length());
receiveClientReceive(clientId, 0, copy, data.length());
}
#else
entry->client->send(stream, buffer.data(), 0, buffer.size());
#endif
}
} // namespace nr::gnb

68
src/gnb/sctp/task.hpp vendored Normal file
View File

@@ -0,0 +1,68 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <memory>
#include <thread>
#include <unordered_map>
#include <vector>
#include <gnb/nts.hpp>
#include <lib/sctp/sctp.hpp>
#include <utils/logger.hpp>
#include <utils/nts.hpp>
#include <utils/scoped_thread.hpp>
namespace nr::gnb
{
class SctpTask : public NtsTask
{
private:
struct ClientEntry
{
int id;
sctp::SctpClient *client;
ScopedThread *receiverThread;
sctp::ISctpHandler *handler;
NtsTask *associatedTask;
};
private:
TaskBase *m_base;
std::unique_ptr<Logger> m_logger;
std::unordered_map<int, ClientEntry *> m_clients;
friend class GnbCmdHandler;
public:
explicit SctpTask(TaskBase *base);
~SctpTask() override = default;
protected:
void onStart() override;
void onLoop() override;
void onQuit() override;
private:
static void DeleteClientEntry(ClientEntry *entry);
private:
void receiveSctpConnectionSetupRequest(int clientId, const std::string &localAddress, uint16_t localPort,
const std::string &remoteAddress, uint16_t remotePort,
sctp::PayloadProtocolId ppid, NtsTask *associatedTask);
void receiveAssociationSetup(int clientId, int associationId, int inStreams, int outStreams);
void receiveAssociationShutdown(int clientId);
void receiveClientReceive(int clientId, uint16_t stream, UniqueBuffer &&buffer);
void receiveUnhandledNotification(int clientId);
void receiveConnectionClose(int clientId);
void receiveSendMessage(int clientId, uint16_t stream, UniqueBuffer &&buffer);
};
} // namespace nr::gnb

106
src/gnb/types.cpp Normal file
View File

@@ -0,0 +1,106 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#include <sstream>
#include <gnb/types.hpp>
#include <utils/common.hpp>
namespace nr::gnb
{
Json ToJson(const GnbStatusInfo &v)
{
return Json::Obj({{"is-ngap-up", v.isNgapUp}});
}
Json ToJson(const GnbConfig &v)
{
return Json::Obj({
{"name", v.name},
{"nci", v.nci},
{"plmn", ToJson(v.plmn)},
{"tac", v.tac},
{"nssai", ToJson(v.nssai)},
{"ngap-ip", v.ngapIp},
{"gtp-ip", v.gtpIp},
{"paging-drx", ToJson(v.pagingDrx)},
{"ignore-sctp-id", v.ignoreStreamIds},
});
}
Json ToJson(const NgapAmfContext &v)
{
auto isIp6 = utils::GetIpVersion(v.address) == 6;
auto address = isIp6 ? "[" + v.address + "]" : v.address;
return Json::Obj({
{"id", v.ctxId},
{"name", v.amfName},
{"address", address + ":" + std::to_string(v.port)},
{"state", ToJson(v.state).str()},
{"capacity", v.relativeCapacity},
{"association", ToJson(v.association)},
{"served-guami", ::ToJson(v.servedGuamiList)},
{"served-plmn", ::ToJson(v.plmnSupportList)},
});
}
Json ToJson(const EAmfState &v)
{
switch (v)
{
case EAmfState::NOT_CONNECTED:
return "NOT_CONNECTED";
case EAmfState::WAITING_NG_SETUP:
return "WAITING_NG_SETUP";
case EAmfState::CONNECTED:
return "CONNECTED";
default:
return "?";
}
}
Json ToJson(const EPagingDrx &v)
{
switch (v)
{
case EPagingDrx::V32:
return "v32";
case EPagingDrx::V64:
return "v64";
case EPagingDrx::V128:
return "v128";
case EPagingDrx::V256:
return "v256";
default:
return "?";
}
}
Json ToJson(const SctpAssociation &v)
{
return Json::Obj({{"id", v.associationId}, {"rx-num", v.inStreams}, {"tx-num", v.outStreams}});
}
Json ToJson(const ServedGuami &v)
{
return Json::Obj({{"guami", ToJson(v.guami)}, {"backup-amf", v.backupAmfName}});
}
Json ToJson(const Guami &v)
{
return Json::Obj({
{"plmn", ToJson(v.plmn)},
{"region-id", ::ToJson(v.amfRegionId)},
{"set-id", ::ToJson(v.amfSetId)},
{"pointer", ::ToJson(v.amfPointer)},
});
}
} // namespace nr::gnb

358
src/gnb/types.hpp vendored Normal file
View File

@@ -0,0 +1,358 @@
//
// This file is a part of UERANSIM project.
// Copyright (c) 2023 ALİ GÜNGÖR.
//
// https://github.com/aligungr/UERANSIM/
// See README, LICENSE, and CONTRIBUTING files for licensing details.
//
#pragma once
#include <set>
#include <lib/app/monitor.hpp>
#include <lib/asn/utils.hpp>
#include <utils/common_types.hpp>
#include <utils/logger.hpp>
#include <utils/network.hpp>
#include <utils/nts.hpp>
#include <utils/octet_string.hpp>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestList.h>
#include <asn/rrc/ASN_RRC_InitialUE-Identity.h>
namespace nr::gnb
{
class GnbAppTask;
class GtpTask;
class NgapTask;
class GnbRrcTask;
class GnbRlsTask;
class SctpTask;
enum class EAmfState
{
NOT_CONNECTED = 0,
WAITING_NG_SETUP,
CONNECTED
};
struct SctpAssociation
{
int associationId{};
int inStreams{};
int outStreams{};
};
struct Guami
{
Plmn plmn{};
int amfRegionId{}; // 8-bit
int amfSetId{}; // 10-bit
int amfPointer{}; // 6-bit
};
struct ServedGuami
{
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{};
int64_t relativeCapacity{};
EAmfState state{};
OverloadInfo overloadInfo{};
std::vector<ServedGuami *> servedGuamiList{};
std::vector<PlmnSupport *> plmnSupportList{};
};
struct RlsUeContext
{
const int ueId;
uint64_t sti{};
InetAddress addr{};
int64_t lastSeen{};
explicit RlsUeContext(int ueId) : ueId(ueId)
{
}
};
struct AggregateMaximumBitRate
{
uint64_t dlAmbr{};
uint64_t ulAmbr{};
};
struct NgapUeContext
{
const int ctxId{};
int64_t amfUeNgapId = -1; // -1 if not assigned
int64_t ranUeNgapId{};
int associatedAmfId{};
int uplinkStream{};
int downlinkStream{};
AggregateMaximumBitRate ueAmbr{};
std::set<int> pduSessions{};
explicit NgapUeContext(int ctxId) : ctxId(ctxId)
{
}
};
struct RrcUeContext
{
const int ueId{};
int64_t initialId = -1; // 39-bit value, or -1
bool isInitialIdSTmsi{}; // TMSI-part-1 or a random value
int64_t establishmentCause{};
std::optional<GutiMobileIdentity> sTmsi{};
explicit RrcUeContext(const int ueId) : ueId(ueId)
{
}
};
struct NgapIdPair
{
std::optional<int64_t> amfUeNgapId{};
std::optional<int64_t> ranUeNgapId{};
NgapIdPair() : amfUeNgapId{}, ranUeNgapId{}
{
}
NgapIdPair(const std::optional<int64_t> &amfUeNgapId, const std::optional<int64_t> &ranUeNgapId)
: amfUeNgapId(amfUeNgapId), ranUeNgapId(ranUeNgapId)
{
}
};
enum class NgapCause
{
RadioNetwork_unspecified = 0,
RadioNetwork_txnrelocoverall_expiry,
RadioNetwork_successful_handover,
RadioNetwork_release_due_to_ngran_generated_reason,
RadioNetwork_release_due_to_5gc_generated_reason,
RadioNetwork_handover_cancelled,
RadioNetwork_partial_handover,
RadioNetwork_ho_failure_in_target_5GC_ngran_node_or_target_system,
RadioNetwork_ho_target_not_allowed,
RadioNetwork_tngrelocoverall_expiry,
RadioNetwork_tngrelocprep_expiry,
RadioNetwork_cell_not_available,
RadioNetwork_unknown_targetID,
RadioNetwork_no_radio_resources_available_in_target_cell,
RadioNetwork_unknown_local_UE_NGAP_ID,
RadioNetwork_inconsistent_remote_UE_NGAP_ID,
RadioNetwork_handover_desirable_for_radio_reason,
RadioNetwork_time_critical_handover,
RadioNetwork_resource_optimisation_handover,
RadioNetwork_reduce_load_in_serving_cell,
RadioNetwork_user_inactivity,
RadioNetwork_radio_connection_with_ue_lost,
RadioNetwork_radio_resources_not_available,
RadioNetwork_invalid_qos_combination,
RadioNetwork_failure_in_radio_interface_procedure,
RadioNetwork_interaction_with_other_procedure,
RadioNetwork_unknown_PDU_session_ID,
RadioNetwork_unkown_qos_flow_ID,
RadioNetwork_multiple_PDU_session_ID_instances,
RadioNetwork_multiple_qos_flow_ID_instances,
RadioNetwork_encryption_and_or_integrity_protection_algorithms_not_supported,
RadioNetwork_ng_intra_system_handover_triggered,
RadioNetwork_ng_inter_system_handover_triggered,
RadioNetwork_xn_handover_triggered,
RadioNetwork_not_supported_5QI_value,
RadioNetwork_ue_context_transfer,
RadioNetwork_ims_voice_eps_fallback_or_rat_fallback_triggered,
RadioNetwork_up_integrity_protection_not_possible,
RadioNetwork_up_confidentiality_protection_not_possible,
RadioNetwork_slice_not_supported,
RadioNetwork_ue_in_rrc_inactive_state_not_reachable,
RadioNetwork_redirection,
RadioNetwork_resources_not_available_for_the_slice,
RadioNetwork_ue_max_integrity_protected_data_rate_reason,
RadioNetwork_release_due_to_cn_detected_mobility,
RadioNetwork_n26_interface_not_available,
RadioNetwork_release_due_to_pre_emption,
RadioNetwork_multiple_location_reporting_reference_ID_instances,
Transport_transport_resource_unavailable = 100,
Transport_unspecified,
Nas_normal_release = 200,
Nas_authentication_failure,
Nas_deregister,
Nas_unspecified,
Protocol_transfer_syntax_error = 300,
Protocol_abstract_syntax_error_reject,
Protocol_abstract_syntax_error_ignore_and_notify,
Protocol_message_not_compatible_with_receiver_state,
Protocol_semantic_error,
Protocol_abstract_syntax_error_falsely_constructed_message,
Protocol_unspecified,
Misc_control_processing_overload = 400,
Misc_not_enough_user_plane_processing_resources,
Misc_hardware_failure,
Misc_om_intervention,
Misc_unknown_PLMN,
};
struct GtpTunnel
{
uint32_t teid{};
OctetString address{};
};
struct PduSessionResource
{
const int ueId;
const int psi;
AggregateMaximumBitRate sessionAmbr{};
bool dataForwardingNotPossible{};
PduSessionType sessionType = PduSessionType::UNSTRUCTURED;
GtpTunnel upTunnel{};
GtpTunnel downTunnel{};
asn::Unique<ASN_NGAP_QosFlowSetupRequestList> qosFlows{};
PduSessionResource(const int ueId, const int psi) : ueId(ueId), psi(psi)
{
}
};
struct GnbStatusInfo
{
bool isNgapUp{};
};
struct GtpUeContext
{
const int ueId;
AggregateMaximumBitRate ueAmbr{};
explicit GtpUeContext(const int ueId) : ueId(ueId)
{
}
};
struct GtpUeContextUpdate
{
bool isCreate{};
int ueId{};
AggregateMaximumBitRate ueAmbr{};
GtpUeContextUpdate(bool isCreate, int ueId, const AggregateMaximumBitRate &ueAmbr)
: isCreate(isCreate), ueId(ueId), ueAmbr(ueAmbr)
{
}
};
struct GnbAmfConfig
{
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{};
NetworkSlice nssai{};
std::vector<GnbAmfConfig> amfConfigs{};
std::string linkIp{};
std::string ngapIp{};
std::string gtpIp{};
std::optional<std::string> gtpAdvertiseIp{};
bool ignoreStreamIds{};
/* Assigned by program */
std::string name{};
EPagingDrx pagingDrx{};
Vector3 phyLocation{};
[[nodiscard]] inline uint32_t getGnbId() const
{
return static_cast<uint32_t>((nci & 0xFFFFFFFFFLL) >> (36LL - static_cast<int64_t>(gnbIdLength)));
}
[[nodiscard]] inline int getCellId() const
{
return static_cast<int>(nci & static_cast<uint64_t>((1 << (36 - gnbIdLength)) - 1));
}
};
struct TaskBase
{
GnbConfig *config{};
LogBase *logBase{};
app::INodeListener *nodeListener{};
NtsTask *cliCallbackTask{};
GnbAppTask *appTask{};
GtpTask *gtpTask{};
NgapTask *ngapTask{};
GnbRrcTask *rrcTask{};
SctpTask *sctpTask{};
GnbRlsTask *rlsTask{};
};
Json ToJson(const GnbStatusInfo &v);
Json ToJson(const GnbConfig &v);
Json ToJson(const NgapAmfContext &v);
Json ToJson(const EAmfState &v);
Json ToJson(const EPagingDrx &v);
Json ToJson(const SctpAssociation &v);
Json ToJson(const ServedGuami &v);
Json ToJson(const Guami &v);
} // namespace nr::gnb