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

Merge pull request #253 from aligungr/dev

v3.1.0
parents ed544330 245a2330
......@@ -66,3 +66,12 @@ add_library(devbnd SHARED src/binder.cpp)
target_compile_options(devbnd PRIVATE -D_GNU_SOURCE -Wall -Wextra)
target_link_options(devbnd PRIVATE -nostartfiles)
target_link_libraries(devbnd dl)
#################### CLI EXECUTABLE ####################
add_executable(nr-cli src/cli.cpp)
target_link_libraries(nr-cli pthread)
target_compile_options(nr-cli PRIVATE -Wall -Wextra -pedantic)
target_link_libraries(nr-cli app)
target_link_libraries(nr-cli udp)
target_link_libraries(nr-cli utils)
......@@ -2,7 +2,7 @@
<a href="https://github.com/aligungr/UERANSIM"><img src="/.github/logo.png" width="75" title="UERANSIM"></a>
</p>
<p align="center">
<img src="https://img.shields.io/badge/UERANSIM-v3.0.3-blue" />
<img src="https://img.shields.io/badge/UERANSIM-v3.1.0-blue" />
<img src="https://img.shields.io/badge/3GPP-R15-orange" />
<img src="https://img.shields.io/badge/License-GPL--3.0-green"/>
</p>
......
......@@ -13,6 +13,7 @@ build: FORCE
cp cmake-build-release/nr-gnb build/
cp cmake-build-release/nr-ue build/
cp cmake-build-release/nr-cli build/
cp cmake-build-release/libdevbnd.so build/
cp tools/nr-binder build/
......
......@@ -8,3 +8,4 @@ add_library(app ${HDR_FILES} ${SRC_FILES})
target_compile_options(app PRIVATE -Wall -Wextra -pedantic -Wno-unused-parameter)
target_link_libraries(app utils)
target_link_libraries(app udp)
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "base_app.hpp"
#include <atomic>
#include <csignal>
#include <cstdio>
#include <exception>
#include <iostream>
#include <utils/constants.hpp>
#include <vector>
static std::atomic_int g_instanceCount{};
static std::vector<void (*)()> g_runAtExit{};
static std::vector<std::string> g_deleteAtExit{};
extern "C" void BaseSignalHandler(int num)
{
for (auto &fun : g_runAtExit)
fun();
for (auto &file : g_deleteAtExit)
std::remove(file.c_str());
if (num == SIGTERM || num == SIGINT)
exit(0);
}
namespace app
{
void Initialize()
{
if (g_instanceCount++ != 0)
std::terminate();
std::signal(SIGTERM, BaseSignalHandler);
std::signal(SIGINT, BaseSignalHandler);
}
void RunAtExit(void (*fun)())
{
g_runAtExit.push_back(fun);
}
void DeleteAtExit(const std::string &file)
{
g_deleteAtExit.push_back(file);
}
} // namespace app
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
namespace app
{
void Initialize();
void RunAtExit(void (*fun)());
void DeleteAtExit(const std::string &file);
} // namespace app
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "cli_base.hpp"
#include <utils/octet_string.hpp>
#include <utils/octet_view.hpp>
#define CMD_BUFFER_SIZE 8192
#define CMD_RCV_TIMEOUT 2500
#define CMD_MIN_LENGTH (3 + 4 + 4 + 1)
namespace app
{
InetAddress CliServer::assignedAddress() const
{
return m_socket.getAddress();
}
CliMessage CliServer::receiveMessage()
{
uint8_t buffer[CMD_BUFFER_SIZE] = {0};
InetAddress address;
int size = m_socket.receive(buffer, CMD_BUFFER_SIZE, CMD_RCV_TIMEOUT, address);
if (size < CMD_MIN_LENGTH || size >= CMD_BUFFER_SIZE)
return {};
OctetView v{buffer, static_cast<size_t>(size)};
if (v.readI() != cons::Major)
return {};
if (v.readI() != cons::Minor)
return {};
if (v.readI() != cons::Patch)
return {};
CliMessage res{};
res.type = static_cast<CliMessage::Type>(v.readI());
int nodeNameLength = v.read4I();
res.nodeName = v.readUtf8String(nodeNameLength);
int valueLength = v.read4I();
res.value = v.readUtf8String(valueLength);
res.clientAddr = address;
return res;
}
void CliServer::sendMessage(const CliMessage &msg)
{
OctetString stream{};
stream.appendOctet(cons::Major);
stream.appendOctet(cons::Minor);
stream.appendOctet(cons::Patch);
stream.appendOctet(static_cast<int>(msg.type));
stream.appendOctet4(static_cast<size_t>(msg.nodeName.size()));
for (char c : msg.nodeName)
stream.appendOctet(static_cast<uint8_t>(c));
stream.appendOctet4(static_cast<size_t>(msg.value.size()));
for (char c : msg.value)
stream.appendOctet(static_cast<uint8_t>(c));
m_socket.send(msg.clientAddr, stream.data(), static_cast<size_t>(stream.length()));
}
} // namespace app
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#pragma once
#include <string>
#include <udp/server.hpp>
#include <utility>
#include <utils/constants.hpp>
#include <utils/network.hpp>
#include <utils/nts.hpp>
#include <vector>
namespace app
{
struct CliMessage
{
enum class Type
{
EMPTY = 0,
ECHO,
ERROR,
RESULT,
COMMAND
} type{};
std::string nodeName{};
std::string value{};
InetAddress clientAddr{};
static CliMessage Error(InetAddress addr, std::string msg, std::string node = "")
{
CliMessage m{};
m.type = Type::ERROR;
m.value = std::move(msg);
m.nodeName = std::move(node);
m.clientAddr = addr;
return m;
}
static CliMessage Result(InetAddress addr, std::string msg, std::string node = "")
{
CliMessage m{};
m.type = Type::RESULT;
m.value = std::move(msg);
m.nodeName = std::move(node);
m.clientAddr = addr;
return m;
}
static CliMessage Echo(InetAddress addr, std::string msg)
{
CliMessage m{};
m.type = Type::ECHO;
m.value = std::move(msg);
m.nodeName = "";
m.clientAddr = addr;
return m;
}
static CliMessage Command(InetAddress addr, std::string msg, std::string node = "")
{
CliMessage m{};
m.type = Type::COMMAND;
m.value = std::move(msg);
m.nodeName = std::move(node);
m.clientAddr = addr;
return m;
}
};
class CliServer
{
private:
Socket m_socket;
public:
explicit CliServer() : m_socket{Socket::CreateAndBindUdp({cons::CMD_SERVER_IP, 0})}
{
}
~CliServer()
{
m_socket.close();
}
[[nodiscard]] InetAddress assignedAddress() const;
CliMessage receiveMessage();
void sendMessage(const CliMessage &msg);
};
struct NwCliSendResponse : NtsMessage
{
InetAddress address{};
std::string output{};
bool isError{};
NwCliSendResponse(const InetAddress &address, std::string output, bool isError)
: NtsMessage(NtsMessageType::CLI_SEND_RESPONSE), address(address), output(std::move(output)), isError(isError)
{
}
};
class CliResponseTask : public NtsTask
{
private:
app::CliServer *cliServer;
public:
explicit CliResponseTask(CliServer *cliServer) : cliServer(cliServer)
{
}
protected:
void onStart() override
{
}
void onLoop() override
{
auto *msg = take();
if (msg == nullptr)
return;
if (msg->msgType == NtsMessageType::CLI_SEND_RESPONSE)
{
auto *w = dynamic_cast<NwCliSendResponse *>(msg);
cliServer->sendMessage(w->isError ? CliMessage::Error(w->address, w->output)
: CliMessage::Result(w->address, w->output));
}
delete msg;
}
void onQuit() override
{
}
};
} // namespace app
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "cli_cmd.hpp"
#include <map>
#include <optional>
#include <sstream>
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utils/options.hpp>
#define CMD_ERR(x) \
{ \
error = x; \
return nullptr; \
}
static opt::OptionsDescription Desc(const std::string &subCommand, const std::string &desc, const std::string &usage,
bool helpIfEmpty)
{
return {subCommand, cons::Tag, desc, {}, subCommand, {usage}, helpIfEmpty};
}
class OptionsHandler : public opt::IOptionsHandler
{
public:
std::stringstream m_output{};
std::stringstream m_err{};
public:
std::ostream &ostream(bool isError) override
{
return isError ? m_err : m_output;
}
void status(int code) override
{
// nothing to do
}
};
static std::string DumpCommands(const std::map<std::string, std::string> &descTable)
{
size_t maxLength = 0;
for (auto &item : descTable)
maxLength = std::max(maxLength, item.first.size());
std::stringstream ss{};
for (auto &item : descTable)
ss << item.first << std::string(maxLength - item.first.size(), ' ') << " | " << item.second << "\n";
std::string output = ss.str();
utils::Trim(output);
return output;
}
namespace app
{
static std::map<std::string, std::string> g_gnbCmdToDescription = {
{"status", "Show some status information about the gNB"},
{"info", "Show some information about the gNB"},
{"amf-list", "List all AMFs associated with the gNB"},
{"amf-info", "Show some status information about the given AMF"},
{"ue-list", "List all UEs associated with the gNB"},
{"ue-count", "Print the total number of UEs connected the this gNB"},
};
static std::map<std::string, std::string> g_gnbCmdToUsage = {
{"status", "[option...]"}, {"info", "[option...]"},
{"amf-list", "[option...]"}, {"amf-info", "<amf-id> [option...]"},
{"ue-list", "[option...]"}, {"ue-count", "[option...]"},
};
static std::map<std::string, bool> g_gnbCmdToHelpIfEmpty = {{"status", false}, {"info", false},
{"amf-list", false}, {"amf-info", true},
{"ue-list", false}, {"ue-count", false}};
static std::map<std::string, std::string> g_ueCmdToDescription = {
{"info", "Show some information about the UE"},
{"status", "Show some status information about the UE"},
{"timers", "Dump current status of the timers in the UE"},
};
static std::map<std::string, std::string> g_ueCmdToUsage = {
{"info", "[option...]"},
{"status", "[option...]"},
{"timers", "[option...]"},
};
static std::map<std::string, bool> g_ueCmdToHelpIfEmpty = {
{"info", false},
{"status", false},
{"timers", false},
};
std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output)
{
if (tokens.empty())
{
error = "Empty command";
return nullptr;
}
std::string &subCmd = tokens[0];
if (subCmd == "commands")
{
output = DumpCommands(g_gnbCmdToDescription);
return nullptr;
}
if (g_gnbCmdToDescription.count(subCmd) == 0 || g_gnbCmdToUsage.count(subCmd) == 0 ||
g_gnbCmdToHelpIfEmpty.count(subCmd) == 0)
{
error = "Command not recognized: " + subCmd;
return nullptr;
}
opt::OptionsDescription desc =
Desc(subCmd, g_gnbCmdToDescription[subCmd], g_gnbCmdToUsage[subCmd], g_gnbCmdToHelpIfEmpty[subCmd]);
OptionsHandler handler{};
opt::OptionsResult options{tokens, desc, &handler};
error = handler.m_err.str();
output = handler.m_output.str();
utils::Trim(error);
utils::Trim(output);
if (!error.empty() || !output.empty())
return nullptr;
if (subCmd == "info")
{
return std::make_unique<GnbCliCommand>(GnbCliCommand::INFO);
}
if (subCmd == "status")
{
return std::make_unique<GnbCliCommand>(GnbCliCommand::STATUS);
}
else if (subCmd == "amf-list")
{
return std::make_unique<GnbCliCommand>(GnbCliCommand::AMF_LIST);
}
else if (subCmd == "amf-info")
{
auto cmd = std::make_unique<GnbCliCommand>(GnbCliCommand::AMF_INFO);
if (options.positionalCount() == 0)
CMD_ERR("AMF ID is expected")
if (options.positionalCount() > 1)
CMD_ERR("Only one AMF ID is expected")
cmd->amfId = utils::ParseInt(options.getPositional(0));
if (cmd->amfId <= 0)
CMD_ERR("Invalid AMF ID")
return cmd;
}
else if (subCmd == "ue-list")
{
return std::make_unique<GnbCliCommand>(GnbCliCommand::UE_LIST);
}
else if (subCmd == "ue-count")
{
return std::make_unique<GnbCliCommand>(GnbCliCommand::UE_COUNT);
}
return nullptr;
}
std::unique_ptr<UeCliCommand> ParseUeCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output)
{
if (tokens.empty())
{
error = "Empty command";
return nullptr;
}
std::string &subCmd = tokens[0];
if (subCmd == "commands")
{
output = DumpCommands(g_ueCmdToDescription);
return nullptr;
}
if (g_ueCmdToDescription.count(subCmd) == 0 || g_ueCmdToUsage.count(subCmd) == 0 ||
g_ueCmdToHelpIfEmpty.count(subCmd) == 0)
{
error = "Command not recognized: " + subCmd;
return nullptr;
}
opt::OptionsDescription desc =
Desc(subCmd, g_ueCmdToDescription[subCmd], g_ueCmdToUsage[subCmd], g_ueCmdToHelpIfEmpty[subCmd]);
OptionsHandler handler{};
opt::OptionsResult options{tokens, desc, &handler};
error = handler.m_err.str();
output = handler.m_output.str();
utils::Trim(error);
utils::Trim(output);
if (!error.empty() || !output.empty())
return nullptr;
if (subCmd == "info")
{
return std::make_unique<UeCliCommand>(UeCliCommand::INFO);
}
else if (subCmd == "status")
{
return std::make_unique<UeCliCommand>(UeCliCommand::STATUS);
}
else if (subCmd == "timers")
{
return std::make_unique<UeCliCommand>(UeCliCommand::TIMERS);
}
return nullptr;
}
} // namespace app
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#pragma once
#include <memory>
#include <string>
#include <vector>
namespace app
{
struct GnbCliCommand
{
enum PR
{
STATUS,
INFO,
AMF_LIST,
AMF_INFO,
UE_LIST,
UE_COUNT
} present;
// AMF_INFO
int amfId{};
explicit GnbCliCommand(PR present) : present(present)
{
}
};
struct UeCliCommand
{
enum PR
{
INFO,
STATUS,
TIMERS,
} present;
explicit UeCliCommand(PR present) : present(present)
{
}
};
std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output);
std::unique_ptr<UeCliCommand> ParseUeCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output);
} // namespace app
\ No newline at end of file
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "proc_table.hpp"
#include "base_app.hpp"
#include <fstream>
#include <ostream>
#include <sstream>
#include <sys/types.h>
#include <unistd.h>
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utils/io.hpp>
namespace app
{
void CreateProcTable(const std::vector<std::string> &nodes, int cmdPort)
{
try
{
io::CreateDirectory(cons::PROC_TABLE_DIR);
std::string filePath;
while (true)
{
std::string fileName = utils::IntToHex(utils::Random64()) + utils::IntToHex(utils::Random64());
filePath = cons::PROC_TABLE_DIR + fileName;
if (!io::Exists(filePath))
break;
}
ProcTableEntry entry{};
entry.major = cons::Major;
entry.minor = cons::Minor;
entry.patch = cons::Patch;
entry.pid = static_cast<int>(::getpid());
entry.port = cmdPort;
entry.nodes = nodes;
io::WriteAllText(filePath, ProcTableEntry::Encode(entry));
DeleteAtExit(filePath);
}
catch (const std::runtime_error &e)
{
throw std::runtime_error("ProcTable could not be created: " + std::string(e.what()));
}
}
std::string ProcTableEntry::Encode(const ProcTableEntry &e)
{
std::stringstream ss{""};
ss << e.major << " ";
ss << e.minor << " ";
ss << e.patch << " ";
ss << e.pid << " ";
ss << e.port << " ";
ss << e.nodes.size() << " ";
for (auto &n : e.nodes)
{
utils::AssertNodeName(n);
ss << n << " ";
}
return ss.str();
}
ProcTableEntry ProcTableEntry::Decode(const std::string &s)
{
ProcTableEntry e{};
std::stringstream ss{s};
ss >> e.major;
ss >> e.minor;
ss >> e.patch;
ss >> e.pid;
ss >> e.port;
size_t nodeSize = 0;
ss >> nodeSize;
for (size_t i = 0; i < nodeSize; i++)
{
std::string n{};
ss >> n;
e.nodes.push_back(std::move(n));
}
return e;
}
} // namespace app
\ No newline at end of file
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
namespace app
{
struct ProcTableEntry
{
int major{};
int minor{};
int patch{};
int pid{};
uint16_t port{};
std::vector<std::string> nodes{};
// ... (New members must always be added to the end)
static std::string Encode(const ProcTableEntry &e);
static ProcTableEntry Decode(const std::string &s);
};
void CreateProcTable(const std::vector<std::string> &nodes, int cmdPort);
template <typename T>
inline void CreateProcTable(const std::unordered_map<std::string, T> &nodeMap, int cmdPort)
{
std::vector<std::string> nodes{};
for (auto &node : nodeMap)
nodes.push_back(node.first);
CreateProcTable(nodes, cmdPort);
}
} // namespace app
......@@ -85,7 +85,7 @@ std::string GetPrintableString(const PrintableString_t &source)
{
std::string r(source.size, '0');
for (size_t i = 0; i < source.size; i++)
r += (char)source.buf[i];
r[i] = (char)source.buf[i];
return r;
}
......
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include <algorithm>
#include <app/cli_base.hpp>
#include <app/proc_table.hpp>
#include <iostream>
#include <set>
#include <string>
#include <unordered_map>
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utils/io.hpp>
#include <utils/network.hpp>
#include <utils/options.hpp>
#include <vector>
static struct Options
{
bool dumpNodes{};
std::string nodeName{};
std::string directCmd{};
} g_options{};
static std::set<int> FindProcesses()
{
std::set<int> res{};
for (const auto &file : io::GetEntries(cons::PROCESS_DIR))
{
if (!io::IsRegularFile(file))
{
auto name = io::GetStem(file);
if (!utils::IsNumeric(name))
continue;
int pid = utils::ParseInt(name);
res.insert(pid);
}
}
return res;
}
static uint16_t DiscoverNode(const std::string &node, int &skippedDueToVersion)
{
if (!io::Exists(cons::PROC_TABLE_DIR))
return 0;
// Find all processes in the environment
auto processes = FindProcesses();
// Read and parse ProcTable entries.
std::unordered_map<std::string, app::ProcTableEntry> entries{};
for (const auto &file : io::GetEntries(cons::PROC_TABLE_DIR))
{
if (!io::IsRegularFile(file))
continue;
std::string content = io::ReadAllText(file);
entries[file] = app::ProcTableEntry::Decode(content);
}
uint16_t found = 0;
skippedDueToVersion = 0;
for (auto &e : entries)
{
// If no such process, it means that this ProcTable file is outdated
// Therefore that file should be deleted
if (processes.count(e.second.pid) == 0)
{
io::Remove(e.first);
continue;
}
// If searching node exists in this file, extract port number from it.
for (auto &n : e.second.nodes)
{
if (n == node)
{
if (e.second.major == cons::Major && e.second.minor == cons::Minor && e.second.patch == cons::Patch)
found = e.second.port;
else
skippedDueToVersion++;
}
}
}
return found;
}
static std::vector<std::string> DumpNames()
{
std::vector<std::string> v{};
if (!io::Exists(cons::PROC_TABLE_DIR))
return v;
// Find all processes in the environment
auto processes = FindProcesses();
// Read and parse ProcTable entries.
std::unordered_map<std::string, app::ProcTableEntry> entries{};
for (const auto &file : io::GetEntries(cons::PROC_TABLE_DIR))
{
if (!io::IsRegularFile(file))
continue;
std::string content = io::ReadAllText(file);
entries[file] = app::ProcTableEntry::Decode(content);
}
for (auto &e : entries)
{
// If no such process, it means that this ProcTable file is outdated
// Therefore that file should be deleted
if (processes.count(e.second.pid) == 0)
{
io::Remove(e.first);
continue;
}
for (auto &n : e.second.nodes)
v.push_back(n);
}
std::sort(v.begin(), v.end());
return v;
}
static void ReadOptions(int argc, char **argv)
{
opt::OptionsDescription desc{"UERANSIM", cons::Tag, "Command Line Interface",
cons::Owner, "nr-cli", {"<node-name> [option...]", "--dump"},
true};
opt::OptionItem itemDump = {'d', "dump", "List all UE and gNBs in the environment", std::nullopt};
opt::OptionItem itemExec = {'e', "exec", "Execute the given command directly without an interactive shell",
"command"};
desc.items.push_back(itemDump);
desc.items.push_back(itemExec);
opt::OptionsResult opt{argc, argv, desc, false, nullptr};
g_options.dumpNodes = opt.hasFlag(itemDump);
if (!g_options.dumpNodes)
{
if (opt.positionalCount() == 0)
{
opt.showError("Node name is expected");
return;
}
if (opt.positionalCount() > 1)
{
opt.showError("Only one node name is expected");
return;
}
g_options.nodeName = opt.getPositional(0);
if (g_options.nodeName.size() < cons::MinNodeName)
{
opt.showError("Node name is too short");
return;
}
if (g_options.nodeName.size() > cons::MaxNodeName)
{
opt.showError("Node name is too long");
return;
}
g_options.directCmd = opt.getOption(itemExec);
if (opt.hasFlag(itemExec) && g_options.directCmd.size() < 3)
{
opt.showError("Command is too short");
return;
}
}
}
static bool HandleMessage(const app::CliMessage &msg, bool isOneShot)
{
if (msg.type == app::CliMessage::Type::ERROR)
{
std::cerr << "ERROR: " << msg.value << std::endl;
if (isOneShot)
exit(1);
return true;
}
if (msg.type == app::CliMessage::Type::ECHO)
{
std::cout << msg.value << std::endl;
return true;
}
if (msg.type == app::CliMessage::Type::RESULT)
{
std::cout << msg.value << std::endl;
if (isOneShot)
exit(0);
return true;
}
return false;
}
[[noreturn]] static void SendCommand(uint16_t port)
{
app::CliServer server{};
if (g_options.directCmd.empty())
{
while (true)
{
std::cout << "\x1b[1m";
std::cout << std::string(92, '-') << std::endl;
std::string line{};
bool isEof{};
std::vector<std::string> tokens{};
if (!opt::ReadLine(std::cin, std::cout, line, tokens, isEof))
{
if (isEof)
exit(0);
else
std::cout << "ERROR: Invalid command" << std::endl;
}
std::cout << "\x1b[0m";
if (line.empty())
continue;
server.sendMessage(
app::CliMessage::Command(InetAddress{cons::CMD_SERVER_IP, port}, line, g_options.nodeName));
while (!HandleMessage(server.receiveMessage(), false))
{
// empty
}
}
}
else
{
server.sendMessage(
app::CliMessage::Command(InetAddress{cons::CMD_SERVER_IP, port}, g_options.directCmd, g_options.nodeName));
while (true)
HandleMessage(server.receiveMessage(), true);
}
}
int main(int argc, char **argv)
{
ReadOptions(argc, argv);
// NOTE: This does not guarantee showing the exact realtime status.
if (g_options.dumpNodes)
{
for (auto &n : DumpNames())
std::cout << n << "\n";
std::cout.flush();
exit(0);
}
if (g_options.nodeName.empty())
{
std::cerr << "ERROR: No node name is specified" << std::endl;
exit(1);
}
if (g_options.nodeName.size() > cons::MaxNodeName)
{
std::cerr << "ERROR: Node name is too long" << std::endl;
exit(1);
}
if (g_options.nodeName.size() < cons::MinNodeName)
{
std::cerr << "ERROR: Node name is too short" << std::endl;
exit(1);
}
uint16_t cmdPort{};
int skippedDueToVersion{};
try
{
cmdPort = DiscoverNode(g_options.nodeName, skippedDueToVersion);
}
catch (const std::runtime_error &e)
{
throw std::runtime_error("Node discovery failure: " + std::string{e.what()});
}
if (cmdPort == 0)
{
std::cerr << "ERROR: No node found with name: " << g_options.nodeName << std::endl;
if (skippedDueToVersion > 0)
std::cerr << "WARNING: " << skippedDueToVersion
<< " node(s) skipped due to version mismatch between the node and the CLI" << std::endl;
return 1;
}
SendCommand(cmdPort);
return 0;
}
\ No newline at end of file
This diff is collapsed.
......@@ -6,19 +6,36 @@
// and subject to the terms and conditions defined in LICENSE file.
//
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <cxxopts/cxxopts.hpp>
#include <app/base_app.hpp>
#include <app/cli_base.hpp>
#include <app/cli_cmd.hpp>
#include <app/proc_table.hpp>
#include <gnb/gnb.hpp>
#include <iostream>
#include <unistd.h>
#include <yaml-cpp/yaml.h>
#include <unordered_map>
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utils/io.hpp>
#include <utils/options.hpp>
#include <utils/yaml_utils.hpp>
#include <yaml-cpp/yaml.h>
static app::CliServer *g_cliServer = nullptr;
static nr::gnb::GnbConfig *g_refConfig = nullptr;
static std::unordered_map<std::string, nr::gnb::GNodeB *> g_gnbMap{};
static app::CliResponseTask *g_cliRespTask = nullptr;
static nr::gnb::GnbConfig *ReadConfigYaml(const std::string &file)
static struct Options
{
std::string configFile{};
bool disableCmd{};
} g_options{};
static nr::gnb::GnbConfig *ReadConfigYaml()
{
auto *result = new nr::gnb::GnbConfig();
auto config = YAML::LoadFile(file);
auto config = YAML::LoadFile(g_options.configFile);
result->plmn.mcc = yaml::GetInt32(config, "mcc", 1, 999);
yaml::GetString(config, "mcc", 3, 3);
......@@ -58,11 +75,28 @@ static nr::gnb::GnbConfig *ReadConfigYaml(const std::string &file)
return result;
}
static nr::gnb::GnbConfig *GetConfig(const std::string &file)
static void ReadOptions(int argc, char **argv)
{
opt::OptionsDescription desc{cons::Project, cons::Tag, "5G-SA gNB implementation",
cons::Owner, "nr-gnb", {"-c <config-file> [option...]"},
true};
opt::OptionItem itemConfigFile = {'c', "config", "Use specified configuration file for gNB", "config-file"};
opt::OptionItem itemDisableCmd = {'l', "disable-cmd", "Disable command line functionality for this instance",
std::nullopt};
desc.items.push_back(itemConfigFile);
desc.items.push_back(itemDisableCmd);
opt::OptionsResult opt{argc, argv, desc, false, nullptr};
if (opt.hasFlag(itemDisableCmd))
g_options.disableCmd = true;
g_options.configFile = opt.getOption(itemConfigFile);
try
{
return ReadConfigYaml(file);
g_refConfig = ReadConfigYaml();
}
catch (const std::runtime_error &e)
{
......@@ -71,43 +105,111 @@ static nr::gnb::GnbConfig *GetConfig(const std::string &file)
}
}
static void ReadOptions(int argc, char **argv, std::string &configFile)
static void ReceiveCommand(app::CliMessage &msg)
{
try
if (msg.value.empty())
{
cxxopts::Options options("nr-gnb", "5G-SA gNB implementation | Copyright (c) 2021 Ali Güngör");
options.add_options()("c,config", "Use specified configuration file for gNB",
cxxopts::value<std::string>())("h,help", "Show this help message and exit");
g_cliServer->sendMessage(app::CliMessage::Result(msg.clientAddr, ""));
return;
}
auto result = options.parse(argc, argv);
std::vector<std::string> tokens{};
if (result.arguments().empty() || result.count("help"))
{
std::cout << options.help() << std::endl;
exit(0);
}
auto exp = opt::PerformExpansion(msg.value, tokens);
if (exp != opt::ExpansionResult::SUCCESS)
{
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Invalid command: " + msg.value));
return;
}
configFile = result["config"].as<std::string>();
if (tokens.empty())
{
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Empty command"));
return;
}
catch (const cxxopts::OptionException &e)
std::string error{}, output{};
auto cmd = app::ParseGnbCliCommand(std::move(tokens), error, output);
if (!error.empty())
{
std::cerr << "ERROR: " << e.what() << std::endl;
exit(1);
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, error));
return;
}
if (!output.empty())
{
g_cliServer->sendMessage(app::CliMessage::Result(msg.clientAddr, output));
return;
}
if (cmd == nullptr)
{
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, ""));
return;
}
if (g_gnbMap.count(msg.nodeName) == 0)
{
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Node not found: " + msg.nodeName));
return;
}
auto *gnb = g_gnbMap[msg.nodeName];
gnb->pushCommand(std::move(cmd), msg.clientAddr, g_cliRespTask);
}
static void Loop()
{
if (!g_cliServer)
{
::pause();
return;
}
auto msg = g_cliServer->receiveMessage();
if (msg.type == app::CliMessage::Type::ECHO)
{
g_cliServer->sendMessage(msg);
return;
}
if (msg.type != app::CliMessage::Type::COMMAND)
return;
if (msg.value.size() > 0xFFFF)
{
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Command is too large"));
return;
}
if (msg.nodeName.size() > 0xFFFF)
{
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Node name is too large"));
return;
}
ReceiveCommand(msg);
}
int main(int argc, char **argv)
{
app::Initialize();
ReadOptions(argc, argv);
std::cout << cons::Name << std::endl;
std::string configFile;
ReadOptions(argc, argv, configFile);
auto *gnb = new nr::gnb::GNodeB(g_refConfig, nullptr);
g_gnbMap[g_refConfig->name] = gnb;
if (!g_options.disableCmd)
{
g_cliServer = new app::CliServer{};
app::CreateProcTable(g_gnbMap, g_cliServer->assignedAddress().getPort());
nr::gnb::GnbConfig *config = GetConfig(configFile);
g_cliRespTask = new app::CliResponseTask(g_cliServer);
g_cliRespTask->start();
}
auto *gnb = new nr::gnb::GNodeB(config, nullptr);
gnb->start();
while (true)
::pause();
}
\ No newline at end of file
Loop();
}
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "cmd_handler.hpp"
#include <gnb/app/task.hpp>
#include <gnb/gtp/task.hpp>
#include <gnb/mr/task.hpp>
#include <gnb/ngap/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::PauseTasks(TaskBase &base)
{
base.gtpTask->requestPause();
base.mrTask->requestPause();
base.ngapTask->requestPause();
base.rrcTask->requestPause();
base.sctpTask->requestPause();
}
void GnbCmdHandler::UnpauseTasks(TaskBase &base)
{
base.gtpTask->requestUnpause();
base.mrTask->requestUnpause();
base.ngapTask->requestUnpause();
base.rrcTask->requestUnpause();
base.sctpTask->requestUnpause();
}
bool GnbCmdHandler::IsAllPaused(TaskBase &base)
{
if (!base.gtpTask->isPauseConfirmed())
return false;
if (!base.mrTask->isPauseConfirmed())
return false;
if (!base.ngapTask->isPauseConfirmed())
return false;
if (!base.rrcTask->isPauseConfirmed())
return false;
if (!base.sctpTask->isPauseConfirmed())
return false;
return true;
}
void GnbCmdHandler::HandleCmd(TaskBase &base, NwGnbCliCommand &msg)
{
PauseTasks(base);
uint64_t currentTime = utils::CurrentTimeMillis();
uint64_t endTime = currentTime + PAUSE_CONFIRM_TIMEOUT;
bool isPaused = false;
while (currentTime < endTime)
{
currentTime = utils::CurrentTimeMillis();
if (IsAllPaused(base))
{
isPaused = true;
break;
}
utils::Sleep(PAUSE_POLLING);
}
if (!isPaused)
{
msg.sendError("gNB is unable process command due to pausing timeout");
}
else
{
HandleCmdImpl(base, msg);
}
UnpauseTasks(base);
}
void GnbCmdHandler::HandleCmdImpl(TaskBase &base, NwGnbCliCommand &msg)
{
switch (msg.cmd->present)
{
case app::GnbCliCommand::STATUS: {
msg.sendResult(ToJson(base.appTask->m_statusInfo).dumpYaml());
break;
}
case app::GnbCliCommand::INFO: {
msg.sendResult(ToJson(*base.config).dumpYaml());
break;
}
case app::GnbCliCommand::AMF_LIST: {
Json json = Json::Arr({});
for (auto &amf : base.ngapTask->m_amfCtx)
json.push(Json::Obj({{"id", amf.first}}));
msg.sendResult(json.dumpYaml());
break;
}
case app::GnbCliCommand::AMF_INFO: {
if (base.ngapTask->m_amfCtx.count(msg.cmd->amfId) == 0)
msg.sendError("AMF not found with given ID");
else
{
auto amf = base.ngapTask->m_amfCtx[msg.cmd->amfId];
msg.sendResult(ToJson(*amf).dumpYaml());
}
break;
}
case app::GnbCliCommand::UE_LIST: {
Json json = Json::Arr({});
for (auto &ue : base.ngapTask->m_ueCtx)
{
json.push(Json::Obj({
{"ue-name", base.mrTask->m_ueMap[ue.first].name},
{"ran-ngap-id", ue.second->ranUeNgapId},
{"amf-ngap-id", ue.second->amfUeNgapId},
}));
}
msg.sendResult(json.dumpYaml());
break;
}
case app::GnbCliCommand::UE_COUNT: {
msg.sendResult(std::to_string(base.ngapTask->m_ueCtx.size()));
break;
}
}
}
} // namespace nr::gnb
\ No newline at end of file
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#pragma once
#include <gnb/nts.hpp>
#include <gnb/types.hpp>
namespace nr::gnb
{
class GnbCmdHandler
{
private:
static void PauseTasks(TaskBase &base);
static void UnpauseTasks(TaskBase &base);
static bool IsAllPaused(TaskBase &base);
public:
static void HandleCmd(TaskBase &base, NwGnbCliCommand &msg);
private:
static void HandleCmdImpl(TaskBase &base, NwGnbCliCommand &msg);
};
} // namespace nr::gnb
......@@ -7,6 +7,7 @@
//
#include "task.hpp"
#include "cmd_handler.hpp"
#include <gnb/nts.hpp>
namespace nr::gnb
......@@ -30,20 +31,26 @@ void GnbAppTask::onLoop()
switch (msg->msgType)
{
case NtsMessageType::GNB_STATUS_UPDATE: {
auto *m = dynamic_cast<NwGnbStatusUpdate *>(msg);
switch (m->what)
auto *w = dynamic_cast<NwGnbStatusUpdate *>(msg);
switch (w->what)
{
case NwGnbStatusUpdate::NGAP_IS_UP:
m_statusInfo.isNgapUp = m->isNgapUp;
m_statusInfo.isNgapUp = w->isNgapUp;
break;
}
break;
}
case NtsMessageType::GNB_CLI_COMMAND: {
auto *w = dynamic_cast<NwGnbCliCommand *>(msg);
GnbCmdHandler::HandleCmd(*m_base, *w);
break;
}
default:
m_logger->unhandledNts(msg);
delete msg;
break;
}
delete msg;
}
void GnbAppTask::onQuit()
......
......@@ -27,6 +27,8 @@ class GnbAppTask : public NtsTask
GnbStatusInfo m_statusInfo;
friend class GnbCmdHandler;
public:
explicit GnbAppTask(TaskBase *base);
~GnbAppTask() override = default;
......
......@@ -14,7 +14,13 @@
#include "rrc/task.hpp"
#include "sctp/task.hpp"
nr::gnb::GNodeB::GNodeB(GnbConfig *config, app::INodeListener *nodeListener)
#include <app/cli_base.hpp>
#include <utils/common.hpp>
namespace nr::gnb
{
GNodeB::GNodeB(GnbConfig *config, app::INodeListener *nodeListener)
{
auto *base = new TaskBase();
base->config = config;
......@@ -31,7 +37,7 @@ nr::gnb::GNodeB::GNodeB(GnbConfig *config, app::INodeListener *nodeListener)
taskBase = base;
}
nr::gnb::GNodeB::~GNodeB()
GNodeB::~GNodeB()
{
taskBase->appTask->quit();
taskBase->sctpTask->quit();
......@@ -52,7 +58,7 @@ nr::gnb::GNodeB::~GNodeB()
delete taskBase;
}
void nr::gnb::GNodeB::start()
void GNodeB::start()
{
taskBase->appTask->start();
taskBase->sctpTask->start();
......@@ -61,3 +67,10 @@ void nr::gnb::GNodeB::start()
taskBase->mrTask->start();
taskBase->gtpTask->start();
}
void GNodeB::pushCommand(std::unique_ptr<app::GnbCliCommand> cmd, const InetAddress &address, NtsTask *callbackTask)
{
taskBase->appTask->push(new NwGnbCliCommand(std::move(cmd), address, callbackTask));
}
} // namespace nr::gnb
......@@ -10,9 +10,13 @@
#include "types.hpp"
#include <app/cli_cmd.hpp>
#include <app/monitor.hpp>
#include <utils/logger.hpp>
#include <memory>
#include <string>
#include <utils/logger.hpp>
#include <utils/network.hpp>
#include <utils/nts.hpp>
namespace nr::gnb
{
......@@ -28,6 +32,7 @@ class GNodeB
public:
void start();
void pushCommand(std::unique_ptr<app::GnbCliCommand> cmd, const InetAddress &address, NtsTask *callbackTask);
};
} // namespace nr::gnb
\ No newline at end of file
......@@ -9,6 +9,7 @@
#include "task.hpp"
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestItem.h>
#include <gnb/mr/task.hpp>
#include <gtp/encode.hpp>
#include <gtp/message.hpp>
#include <utils/constants.hpp>
......
......@@ -21,8 +21,6 @@
namespace nr::gnb
{
struct GnbMrTask;
class GtpTask : public NtsTask
{
private:
......@@ -35,6 +33,8 @@ class GtpTask : public NtsTask
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;
......
......@@ -37,7 +37,7 @@ void GnbRls::onUeConnected(int ue, std::string name)
void GnbRls::onUeReleased(int ue, rls::ECause cause)
{
auto *w = new NwGnbMrToMr(NwGnbMrToMr::UE_CONNECTED);
auto *w = new NwGnbMrToMr(NwGnbMrToMr::UE_RELEASED);
w->ue = ue;
w->cause = cause;
m_targetTask->push(w);
......
......@@ -9,6 +9,8 @@
#include "task.hpp"
#include "rls.hpp"
#include <gnb/nts.hpp>
#include <gnb/rrc/task.hpp>
#include <gnb/gtp/task.hpp>
#include <utils/constants.hpp>
#include <utils/libc_error.hpp>
......
......@@ -10,6 +10,7 @@
#include "rls.hpp"
#include <gnb/nts.hpp>
#include <gnb/types.hpp>
#include <memory>
#include <thread>
#include <udp/server_task.hpp>
......@@ -32,6 +33,8 @@ class GnbMrTask : public NtsTask
GnbRls *m_rlsEntity;
std::unordered_map<int, MrUeContext> m_ueMap;
friend class GnbCmdHandler;
public:
explicit GnbMrTask(TaskBase *base);
~GnbMrTask() override = default;
......
......@@ -23,6 +23,7 @@
#include <asn/ngap/ASN_NGAP_UEContextReleaseCommand.h>
#include <asn/ngap/ASN_NGAP_UEContextReleaseComplete.h>
#include <asn/ngap/ASN_NGAP_UESecurityCapabilities.h>
#include <gnb/gtp/task.hpp>
namespace nr::gnb
{
......
......@@ -11,6 +11,8 @@
#include <algorithm>
#include <gnb/app/task.hpp>
#include <gnb/sctp/task.hpp>
#include <gnb/rrc/task.hpp>
#include <asn/ngap/ASN_NGAP_AMFConfigurationUpdate.h>
#include <asn/ngap/ASN_NGAP_AMFConfigurationUpdateFailure.h>
......
......@@ -26,6 +26,7 @@
#include <asn/ngap/ASN_NGAP_QosFlowPerTNLInformationList.h>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestItem.h>
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestList.h>
#include <gnb/gtp/task.hpp>
namespace nr::gnb
{
......
......@@ -8,8 +8,8 @@
#include "task.hpp"
#include <gnb/app/task.hpp>
#include <utils/common.hpp>
#include <gnb/sctp/task.hpp>
#include <sstream>
namespace nr::gnb
{
......
......@@ -49,6 +49,8 @@ class NgapTask : public NtsTask
uint32_t m_downlinkTeidCounter;
bool m_isInitialized;
friend class GnbCmdHandler;
public:
explicit NgapTask(TaskBase *base);
~NgapTask() override = default;
......
......@@ -13,6 +13,7 @@
#include <asn/utils/utils.hpp>
#include <gnb/app/task.hpp>
#include <gnb/nts.hpp>
#include <gnb/sctp/task.hpp>
#include <asn/ngap/ASN_NGAP_AMF-UE-NGAP-ID.h>
#include <asn/ngap/ASN_NGAP_InitiatingMessage.h>
......
......@@ -8,6 +8,8 @@
#pragma once
#include <app/cli_base.hpp>
#include <app/cli_cmd.hpp>
#include <rrc/rrc.hpp>
#include <sctp/sctp.hpp>
#include <urs/rls/rls.hpp>
......@@ -240,4 +242,26 @@ struct NwGnbStatusUpdate : NtsMessage
}
};
struct NwGnbCliCommand : NtsMessage
{
std::unique_ptr<app::GnbCliCommand> cmd;
InetAddress address;
NtsTask *callbackTask;
NwGnbCliCommand(std::unique_ptr<app::GnbCliCommand> cmd, InetAddress address, NtsTask *callbackTask)
: NtsMessage(NtsMessageType::GNB_CLI_COMMAND), cmd(std::move(cmd)), address(address), callbackTask(callbackTask)
{
}
void sendResult(const std::string &output) const
{
callbackTask->push(new app::NwCliSendResponse(address, output, false));
}
void sendError(const std::string &output) const
{
callbackTask->push(new app::NwCliSendResponse(address, output, true));
}
};
} // namespace nr::gnb
......@@ -10,6 +10,7 @@
#include <asn/rrc/ASN_RRC_UL-CCCH-Message.h>
#include <asn/rrc/ASN_RRC_UL-DCCH-Message.h>
#include <rrc/encode.hpp>
#include <gnb/mr/task.hpp>
namespace nr::gnb
{
......
......@@ -25,6 +25,7 @@
#include <asn/rrc/ASN_RRC_UL-DCCH-Message.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer-IEs.h>
#include <asn/rrc/ASN_RRC_ULInformationTransfer.h>
#include <gnb/ngap/task.hpp>
#include <rrc/encode.hpp>
namespace nr::gnb
......
......@@ -9,6 +9,7 @@
#include "task.hpp"
#include <asn/rrc/ASN_RRC_DLInformationTransfer-IEs.h>
#include <asn/rrc/ASN_RRC_DLInformationTransfer.h>
#include <gnb/mr/task.hpp>
#include <gnb/nts.hpp>
#include <rrc/encode.hpp>
......
......@@ -46,6 +46,8 @@ class GnbRrcTask : public NtsTask
std::unordered_map<int, RrcUeContext *> m_ueCtx;
int m_tidCounter;
friend class GnbCmdHandler;
public:
explicit GnbRrcTask(TaskBase *base);
~GnbRrcTask() override = default;
......
......@@ -39,6 +39,8 @@ class SctpTask : public NtsTask
std::unique_ptr<Logger> m_logger;
std::unordered_map<int, ClientEntry *> m_clients;
friend class GnbCmdHandler;
public:
explicit SctpTask(TaskBase *base);
~SctpTask() override = default;
......@@ -52,9 +54,9 @@ class SctpTask : public NtsTask
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 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);
......
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include <gnb/types.hpp>
#include <iomanip>
#include <sstream>
#include <utils/printer.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.nssais)},
{"ngap-ip", v.ngapIp},
{"gtp-ip", v.gtpIp},
{"paging-drx", ToJson(v.pagingDrx)},
{"ignore-sctp-id", v.ignoreStreamIds},
});
}
Json ToJson(const NgapAmfContext &v)
{
return Json::Obj({
{"id", v.ctxId},
{"name", v.amfName},
{"address", v.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
......@@ -21,6 +21,13 @@
namespace nr::gnb
{
class GnbAppTask;
class GtpTask;
class GnbMrTask;
class NgapTask;
class GnbRrcTask;
class SctpTask;
enum class EAmfState
{
NOT_CONNECTED,
......@@ -277,12 +284,12 @@ struct TaskBase
LogBase *logBase;
app::INodeListener *nodeListener;
NtsTask *appTask;
NtsTask *gtpTask;
NtsTask *mrTask;
NtsTask *ngapTask;
NtsTask *rrcTask;
NtsTask *sctpTask;
GnbAppTask *appTask;
GtpTask *gtpTask;
GnbMrTask *mrTask;
NgapTask *ngapTask;
GnbRrcTask *rrcTask;
SctpTask *sctpTask;
};
struct MrUeContext
......@@ -291,4 +298,13 @@ struct MrUeContext
std::string name;
};
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
\ No newline at end of file
......@@ -443,4 +443,50 @@ void IEEapMessage::Encode(const IEEapMessage &ie, OctetString &stream)
eap::EncodeEapPdu(stream, *ie.eap);
}
Json ToJson(const IE5gsMobileIdentity &v)
{
switch (v.type)
{
case EIdentityType::NO_IDENTITY:
return "no-identitiy";
case EIdentityType::SUCI: {
switch (v.supiFormat)
{
case ESupiFormat::IMSI:
return Json::Obj({
{"plmn", ToJson(v.imsi.plmn)},
{"routing-indicator", v.imsi.routingIndicator},
{"protection-schema", v.imsi.protectionSchemaId},
{"hnp-key-id", ToJson(v.imsi.homeNetworkPublicKeyIdentifier)},
{"scheme-output", v.imsi.schemeOutput},
});
case ESupiFormat::NETWORK_SPECIFIC_IDENTIFIER:
return "nai-" + v.value;
default:
return "?";
}
}
case EIdentityType::GUTI:
return Json::Obj({
{"plmn", ToJson(v.gutiOrTmsi.plmn)},
{"amf-region-id", ToJson(v.gutiOrTmsi.amfRegionId)},
{"amf-set-id", v.gutiOrTmsi.amfSetId},
{"amf-pointer", v.gutiOrTmsi.amfPointer},
{"tmsi", ToJson(v.gutiOrTmsi.tmsi)},
});
case EIdentityType::IMEI:
return "imei-" + v.value;
case EIdentityType::TMSI:
return Json::Obj({
{"amf-set-id", v.gutiOrTmsi.amfSetId},
{"amf-pointer", v.gutiOrTmsi.amfPointer},
{"tmsi", ToJson(v.gutiOrTmsi.tmsi)},
});
case EIdentityType::IMEISV:
return "imeisv-" + v.value;
default:
return "?";
}
}
} // namespace nas
\ No newline at end of file
......@@ -14,6 +14,7 @@
#include "values.hpp"
#include <utils/common_types.hpp>
#include <utils/json.hpp>
namespace nas
{
......@@ -187,7 +188,7 @@ struct IE5gsMobileIdentity : InformationElement6
ESupiFormat supiFormat; // meaningful if identityType is SUCI
// using at most one of the following 3 members:
GutiMobileIdentity gutiOrTmsi{}; // used for TMSI and GUTI (first two member not exist in TMSI)
GutiMobileIdentity gutiOrTmsi{}; // used for TMSI and GUTI (some members not exist in TMSI)
std::string value{}; // used for IMEI, IMEI-SV, NSI,
ImsiMobileIdentity imsi{}; // used for IMSI
......@@ -203,4 +204,6 @@ struct IEEapMessage : InformationElement6
static void Encode(const IEEapMessage &ie, OctetString &stream);
};
Json ToJson(const IE5gsMobileIdentity &v);
} // namespace nas
......@@ -118,4 +118,15 @@ int NasTimer::getRemaining() const
return static_cast<int>(std::max(interval - elapsed, 0L));
}
Json ToJson(const NasTimer &v)
{
int interval = v.getInterval();
return Json::Obj({
{"interval", interval == INT32_MAX ? Json{"inf"} : interval},
{"remaining", v.getRemaining()},
{"running", v.isRunning()},
});
}
} // namespace nas
......@@ -10,6 +10,8 @@
#include "ie4.hpp"
#include <utils/json.hpp>
namespace nas
{
......@@ -40,4 +42,6 @@ class NasTimer
[[nodiscard]] int getRemaining() const;
};
Json ToJson(const NasTimer &v);
} // namespace nas
\ No newline at end of file
This diff is collapsed.
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "cmd_handler.hpp"
#include <ue/app/task.hpp>
#include <ue/mr/task.hpp>
#include <ue/nas/task.hpp>
#include <ue/rrc/task.hpp>
#include <ue/tun/task.hpp>
#include <utils/common.hpp>
#include <utils/printer.hpp>
#define PAUSE_CONFIRM_TIMEOUT 3000
#define PAUSE_POLLING 10
namespace nr::ue
{
void UeCmdHandler::PauseTasks(TaskBase &base)
{
base.mrTask->requestPause();
base.nasTask->requestPause();
base.rrcTask->requestPause();
}
void UeCmdHandler::UnpauseTasks(TaskBase &base)
{
base.mrTask->requestUnpause();
base.nasTask->requestUnpause();
base.rrcTask->requestUnpause();
}
bool UeCmdHandler::IsAllPaused(TaskBase &base)
{
if (!base.mrTask->isPauseConfirmed())
return false;
if (!base.nasTask->isPauseConfirmed())
return false;
if (!base.rrcTask->isPauseConfirmed())
return false;
return true;
}
void UeCmdHandler::HandleCmd(TaskBase &base, NwUeCliCommand &msg)
{
PauseTasks(base);
uint64_t currentTime = utils::CurrentTimeMillis();
uint64_t endTime = currentTime + PAUSE_CONFIRM_TIMEOUT;
bool isPaused = false;
while (currentTime < endTime)
{
currentTime = utils::CurrentTimeMillis();
if (IsAllPaused(base))
{
isPaused = true;
break;
}
utils::Sleep(PAUSE_POLLING);
}
if (!isPaused)
{
msg.sendError("UE is unable process command due to pausing timeout");
}
else
{
HandleCmdImpl(base, msg);
}
UnpauseTasks(base);
}
void UeCmdHandler::HandleCmdImpl(TaskBase &base, NwUeCliCommand &msg)
{
switch (msg.cmd->present)
{
case app::UeCliCommand::STATUS: {
std::vector<Json> pduSessions{};
int index = 0;
for (auto &pduSession : base.appTask->m_statusInfo.pduSessions)
{
if (pduSession.has_value())
{
pduSessions.push_back(
Json::Obj({{"id", index}, {"type", pduSession->type}, {"address", pduSession->address}}));
}
index++;
}
Json json = Json::Obj({
{"cm-state", ToJson(base.nasTask->mm->m_cmState)},
{"rm-state", ToJson(base.nasTask->mm->m_rmState)},
{"mm-state", ToJson(base.nasTask->mm->m_mmSubState)},
{"sim-inserted", base.nasTask->mm->m_validSim},
{"stored-suci", ToJson(base.nasTask->mm->m_storedSuci)},
{"stored-guti", ToJson(base.nasTask->mm->m_storedGuti)},
{"pdu-sessions", Json::Arr(std::move(pduSessions))},
});
msg.sendResult(json.dumpYaml());
break;
}
case app::UeCliCommand::INFO: {
msg.sendResult(ToJson(*base.config).dumpYaml());
break;
}
case app::UeCliCommand::TIMERS: {
msg.sendResult(ToJson(base.nasTask->timers).dumpYaml());
break;
}
}
}
} // namespace nr::ue
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#pragma once
#include <ue/nts.hpp>
#include <ue/types.hpp>
namespace nr::ue
{
class UeCmdHandler
{
private:
static void PauseTasks(TaskBase &base);
static void UnpauseTasks(TaskBase &base);
static bool IsAllPaused(TaskBase &base);
public:
static void HandleCmd(TaskBase &base, NwUeCliCommand &msg);
private:
static void HandleCmdImpl(TaskBase &base, NwUeCliCommand &msg);
};
} // namespace nr::ue
......@@ -7,7 +7,9 @@
//
#include "task.hpp"
#include "cmd_handler.hpp"
#include <nas/utils.hpp>
#include <ue/mr/task.hpp>
#include <ue/tun/tun.hpp>
#include <utils/common.hpp>
#include <utils/constants.hpp>
......@@ -85,6 +87,11 @@ void UeAppTask::onLoop()
receiveStatusUpdate(*dynamic_cast<NwUeStatusUpdate *>(msg));
break;
}
case NtsMessageType::UE_CLI_COMMAND: {
auto *w = dynamic_cast<NwUeCliCommand *>(msg);
UeCmdHandler::HandleCmd(*m_base, *w);
break;
}
default:
m_logger->unhandledNts(msg);
break;
......@@ -94,32 +101,7 @@ void UeAppTask::onLoop()
void UeAppTask::receiveStatusUpdate(NwUeStatusUpdate &msg)
{
if (msg.what == NwUeStatusUpdate::CONNECTED_GNB)
{
if (msg.gnbName.length() > 0)
{
m_statusInfo.connectedGnb = std::move(msg.gnbName);
m_statusInfo.isConnected = true;
}
else
{
m_statusInfo.connectedGnb = {};
m_statusInfo.isConnected = false;
}
}
else if (msg.what == NwUeStatusUpdate::MM_STATE)
{
m_statusInfo.mmState = msg.mmSubState;
}
else if (msg.what == NwUeStatusUpdate::RM_STATE)
{
m_statusInfo.rmState = msg.rmState;
}
else if (msg.what == NwUeStatusUpdate::CM_STATE)
{
m_statusInfo.cmState = msg.cmState;
}
else if (msg.what == NwUeStatusUpdate::SESSION_ESTABLISHMENT)
if (msg.what == NwUeStatusUpdate::SESSION_ESTABLISHMENT)
{
auto *session = msg.pduSession;
......
......@@ -30,6 +30,8 @@ class UeAppTask : public NtsTask
UeStatusInfo m_statusInfo;
TunTask *m_tunTasks[16];
friend class UeCmdHandler;
public:
explicit UeAppTask(TaskBase *base);
~UeAppTask() override = default;
......
......@@ -9,6 +9,8 @@
#include "mm.hpp"
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
#include <ue/rrc/task.hpp>
#include <utils/common.hpp>
namespace nr::ue
......@@ -29,15 +31,6 @@ NasMm::NasMm(TaskBase *base, NtsTask *nas, UeTimers *timers) : m_base{base}, m_n
void NasMm::onStart(NasSm *sm)
{
m_sm = sm;
auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::MM_STATE);
statusUpdate->mmState = MmStateName(m_mmState);
statusUpdate->mmSubState = MmSubStateName(m_mmSubState);
m_base->appTask->push(statusUpdate);
statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::RM_STATE);
statusUpdate->rmState = RmStateName(m_rmState);
m_base->appTask->push(statusUpdate);
}
void NasMm::onQuit()
......@@ -122,18 +115,13 @@ void NasMm::switchMmState(EMmState state, EMmSubState subState)
if (m_base->nodeListener)
{
m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::MM,
MmSubStateName(oldSubState), MmSubStateName(subState));
ToJson(oldSubState).str(), ToJson(subState).str());
m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::MM_SUB,
MmStateName(oldState), MmStateName(state));
ToJson(oldState).str(), ToJson(state).str());
}
auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::MM_STATE);
statusUpdate->mmState = MmStateName(state);
statusUpdate->mmSubState = MmSubStateName(subState);
m_base->appTask->push(statusUpdate);
if (state != oldState || subState != oldSubState)
m_logger->info("UE switches to state: %s", MmSubStateName(subState));
m_logger->info("UE switches to state: %s", ToJson(subState).str().c_str());
triggerMmCycle();
}
......@@ -148,13 +136,9 @@ void NasMm::switchRmState(ERmState state)
if (m_base->nodeListener)
{
m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::RM,
RmStateName(oldState), RmStateName(m_rmState));
ToJson(oldState).str(), ToJson(m_rmState).str());
}
auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::RM_STATE);
statusUpdate->rmState = RmStateName(state);
m_base->appTask->push(statusUpdate);
// No need to log it
// m_logger->info("UE switches to state: %s", RmStateName(state));
......@@ -171,15 +155,11 @@ void NasMm::switchCmState(ECmState state)
if (m_base->nodeListener)
{
m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::CM,
CmStateName(oldState), CmStateName(m_cmState));
ToJson(oldState).str(), ToJson(m_cmState).str());
}
auto *statusUpdate = new NwUeStatusUpdate(NwUeStatusUpdate::CM_STATE);
statusUpdate->rmState = CmStateName(state);
m_base->appTask->push(statusUpdate);
if (state != oldState)
m_logger->info("UE switches to state: %s", CmStateName(state));
m_logger->info("UE switches to state: %s", ToJson(state).str().c_str());
triggerMmCycle();
}
......
......@@ -49,6 +49,8 @@ class NasMm
long m_lastPlmnSearchTrigger{};
OctetString m_sqn{};
friend class UeCmdHandler;
public:
NasMm(TaskBase *base, NtsTask *nas, UeTimers *timers);
......
......@@ -10,6 +10,7 @@
#include <asn/rrc/ASN_RRC_EstablishmentCause.h>
#include <nas/utils.hpp>
#include <ue/nas/enc.hpp>
#include <ue/rrc/task.hpp>
#include <ue/sm/sm.hpp>
namespace nr::ue
......
......@@ -7,7 +7,9 @@
//
#include "task.hpp"
#include <ue/app/task.hpp>
#include <ue/nts.hpp>
#include <ue/rrc/task.hpp>
#include <utils/common.hpp>
#include <utils/constants.hpp>
......
......@@ -32,6 +32,8 @@ class UeMrTask : public NtsTask
long m_lastPlmnSearchFailurePrinted;
friend class UeCmdHandler;
public:
explicit UeMrTask(TaskBase *base);
~UeMrTask() override = default;
......
......@@ -30,6 +30,8 @@ class NasTask : public NtsTask
NasMm *mm;
NasSm *sm;
friend class UeCmdHandler;
public:
explicit NasTask(TaskBase *base);
~NasTask() override = default;
......
This diff is collapsed.
......@@ -9,6 +9,7 @@
#include "task.hpp"
#include <rrc/encode.hpp>
#include <ue/mr/task.hpp>
#include <asn/rrc/ASN_RRC_RRCReject.h>
#include <asn/rrc/ASN_RRC_RRCSetup.h>
......
......@@ -9,6 +9,7 @@
#include "task.hpp"
#include <asn/utils/utils.hpp>
#include <rrc/encode.hpp>
#include <ue/nas/task.hpp>
#include <ue/nts.hpp>
#include <utils/common.hpp>
......
This diff is collapsed.
......@@ -51,6 +51,8 @@ class UeRrcTask : public NtsTask
ASN_RRC_InitialUE_Identity_t m_initialId{};
OctetString m_initialNasPdu{};
friend class UeCmdHandler;
public:
explicit UeRrcTask(TaskBase *base);
~UeRrcTask() override = default;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment