Skip to content

Commit

Permalink
Merge pull request #278 from aligungr/dev
Browse files Browse the repository at this point in the history
v3.1.6
  • Loading branch information
aligungr authored Mar 31, 2021
2 parents 8cceeae + cf9d1eb commit 0325c3f
Show file tree
Hide file tree
Showing 28 changed files with 861 additions and 191 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<a href="https://github.com/aligungr/UERANSIM"><img src="/.github/logo.png" width="75" title="UERANSIM"></a>
</p>
<p align="center">
<img src="https://img.shields.io/badge/UERANSIM-v3.1.5-blue" />
<img src="https://img.shields.io/badge/UERANSIM-v3.1.6-blue" />
<img src="https://img.shields.io/badge/3GPP-R15-orange" />
<img src="https://img.shields.io/badge/License-GPL--3.0-green"/>
</p>
Expand Down
280 changes: 178 additions & 102 deletions src/app/cli_cmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,18 @@
#include <map>
#include <optional>
#include <sstream>
#include <utility>
#include <utils/common.hpp>
#include <utils/constants.hpp>
#include <utils/options.hpp>
#include <utils/ordered_map.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 {{}, {}, desc, {}, subCommand, {usage}, helpIfEmpty, true};
}

class OptionsHandler : public opt::IOptionsHandler
{
public:
Expand All @@ -44,87 +40,64 @@ class OptionsHandler : public opt::IOptionsHandler
}
};

static std::string DumpCommands(const std::map<std::string, std::string> &descTable)
struct CmdEntry
{
size_t maxLength = 0;
for (auto &item : descTable)
maxLength = std::max(maxLength, item.first.size());
using DescType = opt::OptionsDescription (*)(const std::string &, const CmdEntry &);

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
{
std::string descriptionText;
std::string usageText;
DescType descriptionFunc;
bool helpIfEmpty;

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"},
};
CmdEntry() = delete;

static std::map<std::string, std::string> g_gnbCmdToUsage = {
{"status", ""}, {"info", ""}, {"amf-list", ""}, {"amf-info", "<amf-id>"}, {"ue-list", ""}, {"ue-count", ""},
CmdEntry(std::string descriptionText, std::string usageText, DescType descriptionFunc, bool helpIfEmpty)
: descriptionText(std::move(descriptionText)), usageText(std::move(usageText)),
descriptionFunc(descriptionFunc), helpIfEmpty(helpIfEmpty)
{
}
};

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"},
{"deregister", "Perform de-registration by the UE"},
};
static std::string DumpCommands(const OrderedMap<std::string, CmdEntry> &entryTable)
{
size_t maxLength = 0;
for (auto &item : entryTable)
maxLength = std::max(maxLength, item.size());

static std::map<std::string, std::string> g_ueCmdToUsage = {
{"info", ""},
{"status", ""},
{"timers", ""},
{"deregister", "<normal|disable-5g|switch-off|remove-sim>"},
};
std::stringstream ss{};
for (auto &item : entryTable)
ss << item << std::string(maxLength - item.size(), ' ') << " | " << entryTable[item].descriptionText << "\n";
std::string output = ss.str();

static std::map<std::string, bool> g_ueCmdToHelpIfEmpty = {
{"info", false},
{"status", false},
{"timers", false},
{"deregister", true},
};
utils::Trim(output);
return output;
}

std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output)
static std::optional<opt::OptionsResult> ParseCliCommandCommon(OrderedMap<std::string, CmdEntry> &cmdEntries,
std::vector<std::string> &&tokens, std::string &error,
std::string &output, std::string &subCmd)
{
if (tokens.empty())
{
error = "Empty command";
return nullptr;
return std::nullopt;
}

std::string &subCmd = tokens[0];
subCmd = tokens[0];

if (subCmd == "commands")
{
output = DumpCommands(g_gnbCmdToDescription);
return nullptr;
output = DumpCommands(cmdEntries);
return std::nullopt;
}

if (g_gnbCmdToDescription.count(subCmd) == 0 || g_gnbCmdToUsage.count(subCmd) == 0 ||
g_gnbCmdToHelpIfEmpty.count(subCmd) == 0)
if (cmdEntries.count(subCmd) == 0)
{
error = "Command not recognized: " + subCmd;
return nullptr;
return std::nullopt;
}

opt::OptionsDescription desc =
Desc(subCmd, g_gnbCmdToDescription[subCmd], g_gnbCmdToUsage[subCmd], g_gnbCmdToHelpIfEmpty[subCmd]);
opt::OptionsDescription desc = cmdEntries[subCmd].descriptionFunc(subCmd, cmdEntries[subCmd]);

OptionsHandler handler{};

Expand All @@ -136,8 +109,64 @@ std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tok
utils::Trim(output);

if (!error.empty() || !output.empty())
return nullptr;
return {};

return options;
}

//======================================================================================================
// IMPLEMENTATION
//======================================================================================================

static opt::OptionsDescription DefaultDesc(const std::string &subCommand, const CmdEntry &entry)
{
return {{}, {}, entry.descriptionText, {}, subCommand, {entry.usageText}, {}, entry.helpIfEmpty, true};
}

static opt::OptionsDescription DescForPsEstablish(const std::string &subCommand, const CmdEntry &entry)
{
std::string example1 = "IPv4 --sst 1 --sd 1 --dnn internet";
std::string example2 = "IPv4 --emergency";

auto res = opt::OptionsDescription{
{}, {}, entry.descriptionText, {}, subCommand, {entry.usageText}, {example1, example2}, entry.helpIfEmpty,
true};

res.items.emplace_back(std::nullopt, "sst", "SST value of the PDU session", "value");
res.items.emplace_back(std::nullopt, "sd", "SD value of the PDU session", "value");
res.items.emplace_back('n', "dnn", "DNN/APN value of the PDU session", "apn");
res.items.emplace_back('e', "emergency", "Request as an emergency session", std::nullopt);

return res;
}

namespace app
{

static OrderedMap<std::string, CmdEntry> g_gnbCmdEntries = {
{"info", {"Show some information about the gNB", "", DefaultDesc, false}},
{"status", {"Show some status information about the gNB", "", DefaultDesc, false}},
{"amf-list", {"List all AMFs associated with the gNB", "", DefaultDesc, false}},
{"amf-info", {"Show some status information about the given AMF", "<amf-id>", DefaultDesc, true}},
{"ue-list", {"List all UEs associated with the gNB", "", DefaultDesc, false}},
{"ue-count", {"Print the total number of UEs connected the this gNB", "", DefaultDesc, false}},
};

static OrderedMap<std::string, CmdEntry> g_ueCmdEntries = {
{"info", {"Show some information about the UE", "", DefaultDesc, false}},
{"status", {"Show some status information about the UE", "", DefaultDesc, false}},
{"timers", {"Dump current status of the timers in the UE", "", DefaultDesc, false}},
{"ps-establish",
{"Trigger a PDU session establishment procedure", "<session-type> [options]", DescForPsEstablish, true}},
{"ps-release", {"Trigger a PDU session release procedure", "<pdu-session-id>...", DefaultDesc, true}},
{"ps-release-all", {"Trigger PDU session release procedures for all active sessions", "", DefaultDesc, false}},
{"deregister",
{"Perform a de-registration by the UE", "<normal|disable-5g|switch-off|remove-sim>", DefaultDesc, true}},
};

static std::unique_ptr<GnbCliCommand> GnbCliParseImpl(const std::string &subCmd, const opt::OptionsResult &options,
std::string &error)
{
if (subCmd == "info")
{
return std::make_unique<GnbCliCommand>(GnbCliCommand::INFO);
Expand Down Expand Up @@ -174,45 +203,9 @@ std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tok
return nullptr;
}

std::unique_ptr<UeCliCommand> ParseUeCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output)
static std::unique_ptr<UeCliCommand> UeCliParseImpl(const std::string &subCmd, const opt::OptionsResult &options,
std::string &error)
{
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);
Expand Down Expand Up @@ -245,8 +238,91 @@ std::unique_ptr<UeCliCommand> ParseUeCliCommand(std::vector<std::string> &&token
"\"remove-sim\"")
return cmd;
}
else if (subCmd == "ps-release")
{
auto cmd = std::make_unique<UeCliCommand>(UeCliCommand::PS_RELEASE);
if (options.positionalCount() == 0)
CMD_ERR("At least one PDU session ID is expected")
if (options.positionalCount() > 15)
CMD_ERR("Too many PDU session IDs")
cmd->psCount = options.positionalCount();
for (int i = 0; i < cmd->psCount; i++)
{
int n = 0;
if (!utils::TryParseInt(options.getPositional(i), n))
CMD_ERR("Invalid PDU session ID value")
if (n <= 0)
CMD_ERR("PDU session IDs must be positive integer")
if (n > 15)
CMD_ERR("PDU session IDs cannot be greater than 15")
cmd->psIds[i] = static_cast<int8_t>(n);
}
return cmd;
}
else if (subCmd == "ps-release-all")
{
return std::make_unique<UeCliCommand>(UeCliCommand::PS_RELEASE_ALL);
}
else if (subCmd == "ps-establish")
{
auto cmd = std::make_unique<UeCliCommand>(UeCliCommand::PS_ESTABLISH);
if (options.positionalCount() == 0)
CMD_ERR("PDU session type is expected")
if (options.positionalCount() > 15)
CMD_ERR("Only one PDU session type is expected")
std::string type = options.getPositional(0);
if (type != "IPv4" && type != "ipv4" && type != "IPV4" && type != "Ipv4" && type != "IpV4")
CMD_ERR("Only IPv4 is supported for now")
cmd->isEmergency = options.hasFlag('e', "emergency");
if (cmd->isEmergency)
{
if (options.hasFlag(std::nullopt, "sst") || options.hasFlag(std::nullopt, "sd") ||
options.hasFlag('n', "dnn"))
CMD_ERR("SST, SD, and DNN parameters cannot be used for emergency PDU sessions")
}
if (options.hasFlag('n', "dnn"))
cmd->apn = options.getOption('n', "dnn");
if (options.hasFlag(std::nullopt, "sd") && !options.hasFlag(std::nullopt, "sst"))
CMD_ERR("SST is also required in case of an SD is provided")
if (options.hasFlag(std::nullopt, "sst"))
{
int n = 0;
if (!utils::TryParseInt(options.getOption(std::nullopt, "sst"), n) || n <= 0 || n >= 256)
CMD_ERR("Invalid SST value")
cmd->sNssai = SingleSlice{};
cmd->sNssai->sst = static_cast<uint8_t>(n);

if (options.hasFlag(std::nullopt, "sd"))
{
if (!utils::TryParseInt(options.getOption(std::nullopt, "sd"), n) || n <= 0 || n > 0xFFFFFF)
CMD_ERR("Invalid SD value")
cmd->sNssai->sd = octet3{n};
}
}
return cmd;
}

return nullptr;
}

std::unique_ptr<GnbCliCommand> ParseGnbCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output)
{
std::string subCmd{};
auto options = ParseCliCommandCommon(g_gnbCmdEntries, std::move(tokens), error, output, subCmd);
if (options.has_value())
return GnbCliParseImpl(subCmd, *options, error);
return nullptr;
}

std::unique_ptr<UeCliCommand> ParseUeCliCommand(std::vector<std::string> &&tokens, std::string &error,
std::string &output)
{
std::string subCmd{};
auto options = ParseCliCommandCommon(g_ueCmdEntries, std::move(tokens), error, output, subCmd);
if (options.has_value())
return UeCliParseImpl(subCmd, *options, error);
return nullptr;
}

} // namespace app
13 changes: 13 additions & 0 deletions src/app/cli_cmd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#pragma once

#include <array>
#include <memory>
#include <string>
#include <utils/common_types.hpp>
Expand Down Expand Up @@ -43,12 +44,24 @@ struct UeCliCommand
INFO,
STATUS,
TIMERS,
PS_ESTABLISH,
PS_RELEASE,
PS_RELEASE_ALL,
DE_REGISTER,
} present;

// DE_REGISTER
EDeregCause deregCause{};

// PS_RELEASE
std::array<int8_t, 16> psIds{};
int psCount{};

// PS_ESTABLISH
std::optional<SingleSlice> sNssai{};
std::optional<std::string> apn{};
bool isEmergency{};

explicit UeCliCommand(PR present) : present(present)
{
}
Expand Down
Loading

0 comments on commit 0325c3f

Please sign in to comment.