Commit 267c5c46 authored by aligungr's avatar aligungr

UE CLI improvements

parent 8ad0767a
...@@ -82,6 +82,18 @@ static std::map<std::string, bool> g_gnbCmdToHelpIfEmpty = { ...@@ -82,6 +82,18 @@ static std::map<std::string, bool> g_gnbCmdToHelpIfEmpty = {
{"info", false}, {"info", false},
}; };
static std::map<std::string, std::string> g_ueCmdToDescription = {
{"info", "Show some information about the UE"},
};
static std::map<std::string, std::string> g_ueCmdToUsage = {
{"info", "[option...]"},
};
static std::map<std::string, bool> g_ueCmdToHelpIfEmpty = {
{"info", false},
};
std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error, std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output) std::string &output)
{ {
...@@ -149,4 +161,46 @@ std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tok ...@@ -149,4 +161,46 @@ std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tok
return nullptr; 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;
return nullptr;
}
} // namespace app } // namespace app
...@@ -33,7 +33,21 @@ struct GnbCliCommand ...@@ -33,7 +33,21 @@ struct GnbCliCommand
} }
}; };
struct UeCliCommand
{
enum PR
{
} present;
explicit UeCliCommand(PR present) : present(present)
{
}
};
std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error, std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output); std::string &output);
std::unique_ptr<UeCliCommand> ParseUeCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output);
} // namespace app } // namespace app
\ No newline at end of file
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
static app::CliServer *g_cliServer = nullptr; static app::CliServer *g_cliServer = nullptr;
static nr::gnb::GnbConfig *g_refConfig = nullptr; static nr::gnb::GnbConfig *g_refConfig = nullptr;
static std::unordered_map<std::string, nr::gnb::GNodeB *> g_gnbMap{}; static std::unordered_map<std::string, nr::gnb::GNodeB *> g_gnbMap{};
static app::CliResponseTask *g_cliRespTask; static app::CliResponseTask *g_cliRespTask = nullptr;
static struct Options static struct Options
{ {
...@@ -78,7 +78,7 @@ static nr::gnb::GnbConfig *ReadConfigYaml() ...@@ -78,7 +78,7 @@ static nr::gnb::GnbConfig *ReadConfigYaml()
static void ReadOptions(int argc, char **argv) static void ReadOptions(int argc, char **argv)
{ {
opt::OptionsDescription desc{cons::Project, cons::Tag, "5G-SA gNB implementation", opt::OptionsDescription desc{cons::Project, cons::Tag, "5G-SA gNB implementation",
cons::Owner, "nr-cli", {"-c <config-file> [option...]"}, cons::Owner, "nr-gnb", {"-c <config-file> [option...]"},
true}; true};
opt::OptionItem itemConfigFile = {'c', "config", "Use specified configuration file for gNB", "config-file"}; opt::OptionItem itemConfigFile = {'c', "config", "Use specified configuration file for gNB", "config-file"};
......
...@@ -6,19 +6,37 @@ ...@@ -6,19 +6,37 @@
// and subject to the terms and conditions defined in LICENSE file. // and subject to the terms and conditions defined in LICENSE file.
// //
#include <utils/common.hpp> #include <app/base_app.hpp>
#include <utils/constants.hpp> #include <app/cli_base.hpp>
#include <cxxopts/cxxopts.hpp> #include <app/cli_cmd.hpp>
#include <app/proc_table.hpp>
#include <iostream> #include <iostream>
#include <ue/ue.hpp> #include <ue/ue.hpp>
#include <unistd.h> #include <unistd.h>
#include <yaml-cpp/yaml.h> #include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utils/options.hpp>
#include <utils/yaml_utils.hpp> #include <utils/yaml_utils.hpp>
#include <yaml-cpp/yaml.h>
static nr::ue::UeConfig *ReadConfigYaml(const std::string &file, bool configureRouting, bool prefixLogger) static app::CliServer *g_cliServer = nullptr;
nr::ue::UeConfig *g_refConfig = nullptr;
static std::unordered_map<std::string, nr::ue::UserEquipment *> g_ueMap{};
static app::CliResponseTask *g_cliRespTask = nullptr;
static struct Options
{
std::string configFile{};
bool noRoutingConfigs{};
bool disableCmd{};
std::string imsi{};
int count{};
} g_options{};
static nr::ue::UeConfig *ReadConfigYaml()
{ {
auto *result = new nr::ue::UeConfig(); auto *result = new nr::ue::UeConfig();
auto config = YAML::LoadFile(file); auto config = YAML::LoadFile(g_options.configFile);
result->plmn.mcc = yaml::GetInt32(config, "mcc", 1, 999); result->plmn.mcc = yaml::GetInt32(config, "mcc", 1, 999);
yaml::GetString(config, "mcc", 3, 3); yaml::GetString(config, "mcc", 3, 3);
...@@ -42,8 +60,10 @@ static nr::ue::UeConfig *ReadConfigYaml(const std::string &file, bool configureR ...@@ -42,8 +60,10 @@ static nr::ue::UeConfig *ReadConfigYaml(const std::string &file, bool configureR
result->amf = OctetString::FromHex(yaml::GetString(config, "amf", 4, 4)); result->amf = OctetString::FromHex(yaml::GetString(config, "amf", 4, 4));
result->emulationMode = true; result->emulationMode = true;
result->configureRouting = configureRouting; result->configureRouting = !g_options.noRoutingConfigs;
result->prefixLogger = prefixLogger;
// If we have multiple UEs in the same process, then log names should be separated.
result->prefixLogger = g_options.count > 1;
if (yaml::HasField(config, "supi")) if (yaml::HasField(config, "supi"))
result->supi = Supi::Parse(yaml::GetString(config, "supi")); result->supi = Supi::Parse(yaml::GetString(config, "supi"));
...@@ -108,76 +128,55 @@ static nr::ue::UeConfig *ReadConfigYaml(const std::string &file, bool configureR ...@@ -108,76 +128,55 @@ static nr::ue::UeConfig *ReadConfigYaml(const std::string &file, bool configureR
return result; return result;
} }
static nr::ue::UeConfig *GetConfig(const std::string &file, bool configureRouting, bool prefixLogger) static void ReadOptions(int argc, char **argv)
{
try
{
return ReadConfigYaml(file, configureRouting, prefixLogger);
}
catch (const std::runtime_error &e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
exit(1);
}
}
static void ReadOptions(int argc, char **argv, std::string &configFile, bool &noRoutingConfigs, std::string &imsi,
int &count)
{ {
try opt::OptionsDescription desc{cons::Project, cons::Tag, "5G-SA UE implementation",
{ cons::Owner, "nr-ue", {"-c <config-file> [option...]"},
cxxopts::Options options("nr-ue", "5G-SA UE implementation | Copyright (c) 2021 Ali Güngör"); true};
options.add_options()("c,config", "Use specified configuration file for UE", cxxopts::value<std::string>())(
"i,imsi", "Use specified IMSI number instead of provided one", cxxopts::value<std::string>())( opt::OptionItem itemConfigFile = {'c', "config", "Use specified configuration file for UE", "config-file"};
"n,num-of-UE", "Create specified number of UEs starting from the given IMSI", opt::OptionItem itemImsi = {'i', "imsi", "Use specified IMSI number instead of provided one", "imsi"};
cxxopts::value<int>())("r,no-routing-config", "Do not auto configure routing for UE TUN interface")( opt::OptionItem itemCount = {'n', "num-of-UE", "Generate specified number of UEs starting from the given IMSI",
"h,help", "Show this help message and exit"); "num"};
opt::OptionItem itemDisableCmd = {'l', "disable-cmd", "Disable command line functionality for this instance",
auto result = options.parse(argc, argv); std::nullopt};
opt::OptionItem itemDisableRouting = {'r', "no-routing-config",
if (result.arguments().empty() || result.count("help")) "Do not auto configure routing for UE TUN interface", std::nullopt};
desc.items.push_back(itemConfigFile);
desc.items.push_back(itemImsi);
desc.items.push_back(itemCount);
desc.items.push_back(itemDisableCmd);
desc.items.push_back(itemDisableRouting);
opt::OptionsResult opt{argc, argv, desc, false, nullptr};
g_options.configFile = opt.getOption(itemConfigFile);
g_options.noRoutingConfigs = opt.hasFlag(itemDisableRouting);
if (opt.hasFlag(itemCount))
{ {
std::cout << options.help() << std::endl; g_options.count = utils::ParseInt(opt.getOption(itemCount));
exit(0); if (g_options.count <= 0)
}
configFile = result["config"].as<std::string>();
noRoutingConfigs = result.count("no-routing-config");
if (result.count("num-of-UE"))
{
count = result["num-of-UE"].as<int>();
if (count <= 0)
throw std::runtime_error("Invalid number of UEs"); throw std::runtime_error("Invalid number of UEs");
if (count > 512) if (g_options.count > 512)
throw std::runtime_error("Number of UEs is too big"); throw std::runtime_error("Number of UEs is too big");
} }
else else
{ {
count = 1; g_options.count = 1;
} }
imsi = ""; g_options.imsi = {};
if (opt.hasFlag(itemImsi))
if (result.count("imsi"))
{
imsi = result["imsi"].as<std::string>();
Supi::Parse("imsi-" + imsi); // validate the string by parsing
}
}
catch (const cxxopts::OptionException &e)
{ {
std::cerr << "ERROR: " << e.what() << std::endl; g_options.imsi = opt.getOption(itemImsi);
exit(1); Supi::Parse("imsi-" + g_options.imsi); // validate the string by parsing
}
catch (const std::runtime_error &e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
exit(1);
} }
g_options.disableCmd = opt.hasFlag(itemDisableCmd);
} }
std::string LargeSum(std::string a, std::string b) static std::string LargeSum(std::string a, std::string b)
{ {
if (a.length() > b.length()) if (a.length() > b.length())
std::swap(a, b); std::swap(a, b);
...@@ -212,24 +211,24 @@ static void IncrementNumber(std::string &s, int delta) ...@@ -212,24 +211,24 @@ static void IncrementNumber(std::string &s, int delta)
s = LargeSum(s, std::to_string(delta)); s = LargeSum(s, std::to_string(delta));
} }
nr::ue::UeConfig *GetConfigByUe(nr::ue::UeConfig *refConfig, int ueIndex) static nr::ue::UeConfig *GetConfigByUe(int ueIndex)
{ {
auto *c = new nr::ue::UeConfig(); auto *c = new nr::ue::UeConfig();
c->emulationMode = refConfig->emulationMode; c->emulationMode = g_refConfig->emulationMode;
c->key = refConfig->key.copy(); c->key = g_refConfig->key.copy();
c->opC = refConfig->opC.copy(); c->opC = g_refConfig->opC.copy();
c->opType = refConfig->opType; c->opType = g_refConfig->opType;
c->amf = refConfig->amf.copy(); c->amf = g_refConfig->amf.copy();
c->imei = refConfig->imei; c->imei = g_refConfig->imei;
c->imeiSv = refConfig->imeiSv; c->imeiSv = g_refConfig->imeiSv;
c->supi = refConfig->supi; c->supi = g_refConfig->supi;
c->plmn = refConfig->plmn; c->plmn = g_refConfig->plmn;
c->nssais = refConfig->nssais; c->nssais = g_refConfig->nssais;
c->supportedAlgs = refConfig->supportedAlgs; c->supportedAlgs = g_refConfig->supportedAlgs;
c->gnbSearchList = refConfig->gnbSearchList; c->gnbSearchList = g_refConfig->gnbSearchList;
c->initSessions = refConfig->initSessions; c->initSessions = g_refConfig->initSessions;
c->configureRouting = refConfig->configureRouting; c->configureRouting = g_refConfig->configureRouting;
c->prefixLogger = refConfig->prefixLogger; c->prefixLogger = g_refConfig->prefixLogger;
if (c->supi.has_value()) if (c->supi.has_value())
IncrementNumber(c->supi->value, ueIndex); IncrementNumber(c->supi->value, ueIndex);
...@@ -241,29 +240,128 @@ nr::ue::UeConfig *GetConfigByUe(nr::ue::UeConfig *refConfig, int ueIndex) ...@@ -241,29 +240,128 @@ nr::ue::UeConfig *GetConfigByUe(nr::ue::UeConfig *refConfig, int ueIndex)
return c; return c;
} }
int main(int argc, char **argv) static void ReceiveCommand(app::CliMessage &msg)
{ {
std::cout << cons::Name << std::endl; if (msg.value.empty())
{
g_cliServer->sendMessage(app::CliMessage::Result(msg.clientAddr, ""));
return;
}
std::string configFile, imsi; std::vector<std::string> tokens{};
bool noRoutingConfigs;
int count;
ReadOptions(argc, argv, configFile, noRoutingConfigs, imsi, count); 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;
}
// If we have multiple UEs in the same process, then log names should be separated. if (tokens.empty())
bool prefixLogger = count > 1; {
g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Empty command"));
return;
}
nr::ue::UeConfig *refConfig = GetConfig(configFile, !noRoutingConfigs, prefixLogger); std::string error{}, output{};
if (imsi.length() > 0) auto cmd = app::ParseUeCliCommand(std::move(tokens), error, output);
refConfig->supi = Supi::Parse("imsi-" + imsi); if (!error.empty())
{
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;
}
for (int i = 0; i < count; i++) if (g_ueMap.count(msg.nodeName) == 0)
{ {
auto *ue = new nr::ue::UserEquipment(GetConfigByUe(refConfig, i), nullptr); g_cliServer->sendMessage(app::CliMessage::Error(msg.clientAddr, "Node not found: " + msg.nodeName));
ue->start(); return;
} }
while (true) auto *ue = g_ueMap[msg.nodeName];
ue->pushCommand(std::move(cmd), msg.clientAddr, g_cliRespTask);
}
static void Loop()
{
if (!g_cliServer)
{
::pause(); ::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();
try
{
ReadOptions(argc, argv);
g_refConfig = ReadConfigYaml();
if (g_options.imsi.length() > 0)
g_refConfig->supi = Supi::Parse("imsi-" + g_options.imsi);
}
catch (const std::runtime_error &e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
return 1;
}
std::cout << cons::Name << std::endl;
for (int i = 0; i < g_options.count; i++)
{
auto *config = GetConfigByUe(i);
auto *ue = new nr::ue::UserEquipment(config, nullptr);
g_ueMap[config->getNodeName()] = ue;
}
if (!g_options.disableCmd)
{
g_cliServer = new app::CliServer{};
app::CreateProcTable(g_ueMap, g_cliServer->assignedAddress().getPort());
g_cliRespTask = new app::CliResponseTask(g_cliServer);
g_cliRespTask->start();
}
for (auto &ue : g_ueMap)
ue.second->start();
while (true)
Loop();
} }
\ No newline at end of file
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#pragma once #pragma once
#include "types.hpp" #include "types.hpp"
#include "ue.hpp"
#include <app/cli_base.hpp>
#include <nas/timer.hpp> #include <nas/timer.hpp>
#include <rrc/rrc.hpp> #include <rrc/rrc.hpp>
#include <urs/rls/rls.hpp> #include <urs/rls/rls.hpp>
...@@ -253,4 +255,26 @@ struct NwUeStatusUpdate : NtsMessage ...@@ -253,4 +255,26 @@ struct NwUeStatusUpdate : NtsMessage
} }
}; };
struct NwUeCliCommand : NtsMessage
{
std::unique_ptr<app::UeCliCommand> cmd;
InetAddress address;
NtsTask *callbackTask;
NwUeCliCommand(std::unique_ptr<app::UeCliCommand> cmd, InetAddress address, NtsTask *callbackTask)
: NtsMessage(NtsMessageType::UE_CLI_COMMAND), cmd(std::move(cmd)), address(address), callbackTask(callbackTask)
{
}
void sendResult(const std::string &output) const
{
callbackTask->push(new app::NwCliSendResponse(address, output, false));
}
void sendError(const std::string &output) const
{
callbackTask->push(new app::NwCliSendResponse(address, output, true));
}
};
} // namespace nr::ue } // namespace nr::ue
\ No newline at end of file
...@@ -56,4 +56,10 @@ void UserEquipment::start() ...@@ -56,4 +56,10 @@ void UserEquipment::start()
taskBase->appTask->start(); taskBase->appTask->start();
} }
void UserEquipment::pushCommand(std::unique_ptr<app::UeCliCommand> cmd, const InetAddress &address,
NtsTask *callbackTask)
{
taskBase->appTask->push(new NwUeCliCommand(std::move(cmd), address, callbackTask));
}
} // namespace nr::ue } // namespace nr::ue
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
#pragma once #pragma once
#include "types.hpp" #include "types.hpp"
#include <app/cli_cmd.hpp>
#include <memory>
#include <utils/network.hpp>
#include <utils/nts.hpp>
namespace nr::ue namespace nr::ue
{ {
...@@ -24,6 +28,7 @@ class UserEquipment ...@@ -24,6 +28,7 @@ class UserEquipment
public: public:
void start(); void start();
void pushCommand(std::unique_ptr<app::UeCliCommand> cmd, const InetAddress &address, NtsTask *callbackTask);
}; };
} // namespace nr::ue } // namespace nr::ue
\ No newline at end of file
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