init-version-3.27
This commit is contained in:
11
src/gnb/CMakeLists.txt
vendored
Normal file
11
src/gnb/CMakeLists.txt
vendored
Normal 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
160
src/gnb/app/cmd_handler.cpp
Normal 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
42
src/gnb/app/cmd_handler.hpp
vendored
Normal 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
60
src/gnb/app/task.cpp
Normal 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
43
src/gnb/app/task.hpp
vendored
Normal 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
76
src/gnb/gnb.cpp
Normal 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
36
src/gnb/gnb.hpp
vendored
Normal 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
416
src/gnb/gtp/proto.cpp
Normal 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 >p, 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
159
src/gnb/gtp/proto.hpp
vendored
Normal 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
287
src/gnb/gtp/task.cpp
Normal 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
61
src/gnb/gtp/task.hpp
vendored
Normal 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
235
src/gnb/gtp/utils.cpp
Normal 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
95
src/gnb/gtp/utils.hpp
vendored
Normal 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
324
src/gnb/ngap/context.cpp
Normal 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
14
src/gnb/ngap/encode.cpp
Normal 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
96
src/gnb/ngap/encode.hpp
vendored
Normal 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
362
src/gnb/ngap/interface.cpp
Normal 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
157
src/gnb/ngap/management.cpp
Normal 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
248
src/gnb/ngap/nas.cpp
Normal 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
35
src/gnb/ngap/nnsf.cpp
Normal 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
59
src/gnb/ngap/radio.cpp
Normal 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
316
src/gnb/ngap/session.cpp
Normal 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
108
src/gnb/ngap/task.cpp
Normal 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
129
src/gnb/ngap/task.hpp
vendored
Normal 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
407
src/gnb/ngap/transport.cpp
Normal 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
230
src/gnb/ngap/utils.cpp
Normal 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
65
src/gnb/ngap/utils.hpp
vendored
Normal 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
9
src/gnb/nts.cpp
Normal 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
312
src/gnb/nts.hpp
vendored
Normal 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
258
src/gnb/rls/ctl_task.cpp
Normal 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
52
src/gnb/rls/ctl_task.hpp
vendored
Normal 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
135
src/gnb/rls/task.cpp
Normal 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
52
src/gnb/rls/task.hpp
vendored
Normal 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
201
src/gnb/rls/udp_task.cpp
Normal 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
63
src/gnb/rls/udp_task.hpp
vendored
Normal 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
122
src/gnb/rrc/broadcast.cpp
Normal 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
227
src/gnb/rrc/channel.cpp
Normal 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
135
src/gnb/rrc/connection.cpp
Normal 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
137
src/gnb/rrc/handler.cpp
Normal 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
|
||||
21
src/gnb/rrc/management.cpp
Normal file
21
src/gnb/rrc/management.cpp
Normal 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
33
src/gnb/rrc/sap.cpp
Normal 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
90
src/gnb/rrc/task.cpp
Normal 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
112
src/gnb/rrc/task.hpp
vendored
Normal 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
42
src/gnb/rrc/ues.cpp
Normal 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
335
src/gnb/sctp/task.cpp
Normal 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
68
src/gnb/sctp/task.hpp
vendored
Normal 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
106
src/gnb/types.cpp
Normal 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
358
src/gnb/types.hpp
vendored
Normal 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
|
||||
Reference in New Issue
Block a user