Commit 433b0585 authored by juvancic's avatar juvancic
Browse files

Added SCTP protocol/layer support to Titan framework, 1-st draft version

parent 9d95325e
Loading
Loading
Loading
Loading
+88 −0
Original line number Diff line number Diff line
//
// 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 "client.hh"
#include "internal.hh"

#include "loggers.hh"

sctp::SctpClient::SctpClient(PayloadProtocolId ppid, sctp_layer* sl) :  sd(CreateSocket()), ppid(ppid), sl(sl)
{
    try
    {
        SetInitOptions(sd, 10, 10, 10, 10 * 1000);
        SetEventOptions(sd);
    }
    catch (const std::exception &e)
    {
        CloseSocket(sd);
        throw;
    }
//	 loggers::get_instance().log(">>> client::client: %d",sd);
}

sctp::SctpClient::~SctpClient()
{
    CloseSocket(sd);
}

void sctp::SctpClient::connect(const std::string &address, uint16_t port) const
{
    Connect(sd, address, port);
}

void sctp::SctpClient::send(uint16_t stream, const uint8_t *buffer, size_t offset, size_t length)
{
    SendMessage(sd, buffer + offset, length, (int)ppid, stream);
}

void sctp::SctpClient::send(uint16_t stream, const uint8_t *buffer, size_t length)
{
    send(stream, buffer, 0, length);
}

void sctp::SctpClient::send( const uint8_t *buffer, size_t length)
{
//	 loggers::get_instance().log(">>> client::send: %d",sd);

    send(sd, buffer, 0, length);
}

void sctp::SctpClient::send(uint16_t stream, const std::vector<uint8_t> &data)
{
    send(stream, data.data(), data.size());
}


//void sctp::SctpClient::receive(ISctpHandler *handler)
void sctp::SctpClient::receive()
{
//     loggers::get_instance().log(">>> client::receive: %d",sd);
    //ReceiveMessage(sd, static_cast<uint32_t>(ppid), handler);
    ReceiveMessage(sd, static_cast<uint32_t>(ppid),sl);
}

[[noreturn]]  void sctp::SctpClient::ReceiverThread(sctp::SctpClient* client)

//[[noreturn]] static void ReceiverThread(std::pair<sctp::SctpClient *, sctp_layer *> *args)	
{
  //  sctp::SctpClient *client = args->first;
  //  sctp_layer *handler = args->second;

    //delete args;

    while (true)
       // receive();
       client->receive();
}

void sctp::SctpClient::bind(const std::string &address, uint16_t port)
{
//	 loggers::get_instance().log(">>> client::bind: %d",sd);
    BindSocket(sd, address, port);
}
+45 −0
Original line number Diff line number Diff line
//
// 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 "types.hh"
#include "sctp_layer.hh"
#include <string>
#include <vector>

namespace sctp
{

class SctpClient
{
  private:
    const int sd;
    const PayloadProtocolId ppid;
    sctp_layer* sl;

  public:
    explicit SctpClient(PayloadProtocolId ppid, sctp_layer* sl);
    ~SctpClient();

    void bind(const std::string &address, uint16_t port);
    void connect(const std::string &address, uint16_t port) const;

    void send(uint16_t stream, const uint8_t *buffer, size_t offset, size_t length);
    void send(uint16_t stream, const uint8_t *buffer, size_t length);
    void send(const uint8_t *buffer, size_t length);
    void send(uint16_t stream, const std::vector<uint8_t> &data);

    //void receive(ISctpHandler *handler);
    void receive();
    [[noreturn]] static void ReceiverThread(sctp::SctpClient *client);
    //[[noreturn]] static void ReceiverThread(std::pair<sctp::SctpClient *, sctp_layer *> *args);

};

} // namespace sctp
+333 −0
Original line number Diff line number Diff line
//
// 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 "common.hh"
#include "constants.hh"

#include <algorithm>
#include <atomic>
#include <cctype>
#include <chrono>
#include <cstring>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <thread>

#include <arpa/inet.h>
#include <unistd.h>

static_assert(sizeof(char) == sizeof(uint8_t));
static_assert(sizeof(int) == sizeof(uint32_t));
static_assert(sizeof(long) == sizeof(uint32_t) || sizeof(long) == sizeof(uint64_t));
static_assert(sizeof(float) == sizeof(uint32_t));
static_assert(sizeof(double) == sizeof(uint64_t));
static_assert(sizeof(long long) == sizeof(uint64_t));

static std::atomic<int> g_idCounter = 1;

static bool IPv6FromString(const char *szAddress, uint8_t *address)
{
    auto asciiToHex = [](char c) -> int {
        c |= 0x20;
        if (c >= '0' && c <= '9')
            return c - '0';
        else if (c >= 'a' && c <= 'f')
            return (c - 'a') + 10;
        else
            return -1;
    };

    uint16_t acc = 0;
    uint8_t colons = 0;
    uint8_t pos = 0;

    memset(address, 0, 16);

    for (uint8_t i = 1; i <= 39; i++)
    {
        if (szAddress[i] == ':')
        {
            if (szAddress[i - 1] == ':')
                colons = 14;
            else if (colons)
                colons -= 2;
        }
        else if (szAddress[i] == '\0')
            break;
    }
    for (uint8_t i = 0; i <= 39 && pos < 16; i++)
    {
        if (szAddress[i] == ':' || szAddress[i] == '\0')
        {
            address[pos] = acc >> 8;
            address[pos + 1] = acc;
            acc = 0;

            if (colons && i && szAddress[i - 1] == ':')
                pos = colons;
            else
                pos += 2;
        }
        else
        {
            int val = asciiToHex(szAddress[i]);
            if (val == -1)
                return false;
            else
            {
                acc <<= 4;
                acc |= static_cast<uint8_t>(val);
            }
        }
        if (szAddress[i] == '\0')
            break;
    }
    return true;
}

int utils::GetIpVersion(const std::string &address)
{
    const std::regex regex4(R"(\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b)");
    const std::regex regex6(
        "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-"
        "9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-"
        "fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-"
        "9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|"
        "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,"
        "1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:(("
        "25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))");

    if (std::regex_match(address, regex4))
        return 4;
    if (std::regex_match(address, regex6))
        return 6;
    return 0;
}

std::vector<uint8_t> utils::HexStringToVector(const std::string &hex)
{
    if (hex.length() % 2 != 0)
        throw std::runtime_error("hex string has an odd length");

    for (char c : hex)
    {
        if (c >= '0' && c <= '9')
            continue;
        if (c >= 'a' && c <= 'f')
            continue;
        if (c >= 'A' && c <= 'F')
            continue;
        throw std::runtime_error("hex string contains invalid characters");
    }

    std::vector<uint8_t> bytes;
    for (unsigned int i = 0; i < hex.length(); i += 2)
    {
        std::string byteString = hex.substr(i, 2);
        char byte = (char)strtol(byteString.c_str(), nullptr, 16);
        bytes.push_back(byte);
    }
    return bytes;
}

int utils::NextId()
{
    int res = ++g_idCounter;
    if (res == 0)
    {
        // ID counter overflows.
        std::terminate();
    }
    return res;
}

int64_t utils::CurrentTimeMillis()
{
    auto time = std::chrono::system_clock::now();
    auto sinceEpoch = time.time_since_epoch();
    auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(sinceEpoch);
    int64_t now = millis.count();
    return now;
}

TimeStamp utils::CurrentTimeStamp()
{
    int64_t tms = CurrentTimeMillis();

    int64_t baseTime;
    if (tms < 2085978496000LL)
        baseTime = tms - (-2208988800000LL);
    else
        baseTime = tms - 2085978496000LL;

    int64_t seconds = baseTime / 1000;
    int64_t fraction = ((baseTime % 1000) * 0x100000000LL) / 1000;

    if (tms < 2085978496000LL)
        seconds |= 0x80000000LL;

    int64_t time = (seconds << 32LL) | fraction;
    return TimeStamp(time);
}

OctetString utils::IpToOctetString(const std::string &address)
{
    int ipVersion = GetIpVersion(address);
    if (ipVersion == 4)
    {
        int bytes[4];
        char dot;

        std::stringstream ss(address);
        ss >> bytes[0] >> dot >> bytes[1] >> dot >> bytes[2] >> dot >> bytes[3] >> dot;

        std::vector<uint8_t> data(4);
        data[0] = bytes[0];
        data[1] = bytes[1];
        data[2] = bytes[2];
        data[3] = bytes[3];

        return OctetString(std::move(data));
    }
    else if (ipVersion == 6)
    {
        std::vector<uint8_t> data(16);
        if (!IPv6FromString(address.c_str(), data.data()))
            return {};
        return OctetString(std::move(data));
    }
    else
        return {};
}

std::string utils::VectorToHexString(const std::vector<uint8_t> &hex)
{
    std::string str(hex.size() * 2, '0');
    for (size_t i = 0; i < hex.size(); i++)
    {
        uint8_t octet = hex[i];
        int big = (octet >> 4) & 0xF;
        int little = octet & 0xF;

        char bigChar = static_cast<char>(big < 10 ? '0' + big : 'A' + (big - 10));
        char littleChar = static_cast<char>(little < 10 ? '0' + little : 'A' + (little - 10));

        str[i * 2] = bigChar;
        str[i * 2 + 1] = littleChar;
    }
    return str;
}

bool utils::TryParseInt(const std::string &str, int &output)
{
    return TryParseInt(str.c_str(), output);
}

bool utils::TryParseInt(const char *str, int &output)
{
    int base = 10;
    if (strlen(str) > 2)
    {
        if (str[0] == '0' && str[1] == 'x')
            base = 16;
        else if (str[0] == '0' && str[1] == 'b')
            base = 2;
    }

    try
    {
        output = std::stoi(str, nullptr, base);
        return true;
    }
    catch (...)
    {
        return false;
    }
}

int utils::ParseInt(const std::string &str)
{
    return ParseInt(str.c_str());
}

int utils::ParseInt(const char *str)
{
    int n = 0;
    TryParseInt(str, n);
    return n;
}

void utils::Sleep(int ms)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

std::string utils::OctetStringToIp(const OctetString &address)
{
    if (address.length() == 4)
    {
        char buffer[20] = {0};
        sprintf(buffer, "%d.%d.%d.%d", address.getI(0), address.getI(1), address.getI(2), address.getI(3));
        return std::string{buffer};
    }
    return address.toHexString();
}

bool utils::IsRoot()
{
    return geteuid() == 0;
}

void utils::AssertNodeName(const std::string &str)
{
    if (str.length() < cons::MinNodeName)
        throw std::runtime_error("Node name assertion failed: string'" + str + "' is too short");
    if (str.length() > cons::MaxNodeName)
        throw std::runtime_error("Node name assertion failed: string'" + str + "' is too long");

    for (char c : str)
    {
        if (c >= '0' && c <= '9')
            continue;
        if (c >= 'a' && c <= 'z')
            continue;
        if (c >= 'A' && c <= 'Z')
            continue;
        if (c == '-' || c == '_')
            continue;
        throw std::runtime_error("Node name assertion failed: string '" + str +
                                 "' contains illegal character: " + std::string(1, c));
    }
}

bool utils::IsNumeric(const std::string &str)
{
    return !str.empty() && std::all_of(str.begin(), str.end(), [](char c) { return (c >= '0' && c <= '9'); });
}

void utils::Trim(std::string &s)
{
    if (s.length() == 0)
        return;
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
    s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end());
}

void utils::Trim(std::stringstream &s)
{
    std::string str{};
    str = s.str();
    Trim(str);
    s.str(str);
}

bool utils::IsLittleEndian()
{
    return htonl(1453) != 1453;
}
+77 −0
Original line number Diff line number Diff line
//
// 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 "common_types.hh"
#include "octet.hh"
#include "octet_string.hh"
#include "time_stamp.hh"

#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

namespace utils
{

std::vector<uint8_t> HexStringToVector(const std::string &hex);
std::string VectorToHexString(const std::vector<uint8_t> &hex);
int GetIpVersion(const std::string &address);
OctetString IpToOctetString(const std::string &address);
std::string OctetStringToIp(const OctetString &address);
int64_t CurrentTimeMillis();
TimeStamp CurrentTimeStamp();
int NextId();
int ParseInt(const std::string &str);
int ParseInt(const char *str);
bool TryParseInt(const std::string &str, int &output);
bool TryParseInt(const char *str, int &output);
void Sleep(int ms);
bool IsRoot();
bool IsNumeric(const std::string &str);
void AssertNodeName(const std::string &str);
void Trim(std::string &str);
void Trim(std::stringstream &str);
bool IsLittleEndian();

template <typename T>
inline void ClearAndDelete(std::vector<T *> &vector)
{
    for (T *item : vector)
        delete item;
    vector.clear();
}

template <typename T, typename P>
inline void EraseWhere(std::vector<T> &vector, P predicate)
{
    vector.erase(std::remove_if(vector.begin(), vector.end(), std::forward<P>(predicate)), vector.end());
}

template <typename T>
static std::string IntToHex(T i)
{
    std::stringstream stream;
    if constexpr (sizeof(T) == 1)
        stream << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << static_cast<int>(i);
    else
        stream << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << i;
    return stream.str();
}

template <class T>
inline void HashCombine(std::size_t &seed, const T &v)
{
    std::hash<T> hasher{};
    seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

} // namespace utils
+251 −0
Original line number Diff line number Diff line
//
// 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 "common_types.hh"
#include "common.hh"

#include <algorithm>
#include <iomanip>
#include <sstream>
#include <stdexcept>

Supi Supi::Parse(const std::string &supi)
{
    if (supi[0] == 'i' && supi[1] == 'm' && supi[2] == 's' && supi[3] == 'i' && supi[4] == '-')
    {
        std::string val = supi.substr(5);
        if (val.size() < 4u || val.size() > 15u)
            throw std::runtime_error("invalid IMSI value");
        for (char c : val)
            if (c < '0' || c > '9')
                throw std::runtime_error("invalid IMSI value");
        return Supi{"imsi", val};
    }
    throw std::runtime_error("invalid SUPI value");
}

int64_t GutiMobileIdentity::toTmsiValue() const
{
    return (static_cast<int64_t>(this->tmsi)) | (static_cast<int64_t>(this->amfPointer) << 32LL) |
           (static_cast<int64_t>(this->amfSetId) << 38LL);
}

GutiMobileIdentity GutiMobileIdentity::FromSTmsi(int64_t sTmsi)
{
    GutiMobileIdentity res;
    res.tmsi = octet4{static_cast<uint32_t>(sTmsi & 0xFFFFFFFFLL)};
    res.amfPointer = static_cast<int>(((sTmsi >> 32LL) & 0b111111LL));
    res.amfSetId = static_cast<int>(((sTmsi >> 38LL) & 0b1111111111LL));
    return res;
}

Json ToJson(const Supi &v)
{
    return v.type + "-" + v.value;
}

Json ToJson(const Plmn &v)
{
    if (!v.hasValue())
        return nullptr;

    std::stringstream ss{};
    ss << std::setfill('0') << std::setw(3) << v.mcc << "/";
    ss << std::setfill('0') << std::setw(v.isLongMnc ? 3 : 2) << v.mnc;
    return ss.str();
}

Json ToJson(const SingleSlice &v)
{
    return Json::Obj({{"sst", ToJson(v.sst)}, {"sd", ToJson(v.sd)}});
}

Json ToJson(const NetworkSlice &v)
{
    return ToJson(v.slices);
}

Json ToJson(const PlmnSupport &v)
{
    return Json::Obj({{"plmn", ToJson(v.plmn)}, {"nssai", ToJson(v.sliceSupportList)}});
}

Json ToJson(const ECellCategory &v)
{
    switch (v)
    {
    case ECellCategory::BARRED_CELL:
        return "BARRED";
    case ECellCategory::RESERVED_CELL:
        return "RESERVED";
    case ECellCategory::ACCEPTABLE_CELL:
        return "ACCEPTABLE";
    case ECellCategory::SUITABLE_CELL:
        return "SUITABLE";
    default:
        return "?";
    }
}

bool operator==(const SingleSlice &lhs, const SingleSlice &rhs)
{
    if ((int)lhs.sst != (int)rhs.sst)
        return false;
    if (lhs.sd.has_value() != rhs.sd.has_value())
        return false;
    if (!lhs.sd.has_value())
        return true;
    return ((int)*lhs.sd) == ((int)*rhs.sd);
}

bool operator!=(const SingleSlice &lhs, const SingleSlice &rhs)
{
    return !(lhs == rhs);
}

bool operator==(const Plmn &lhs, const Plmn &rhs)
{
    if (lhs.mcc != rhs.mcc)
        return false;
    if (lhs.mnc != rhs.mnc)
        return false;
    return lhs.isLongMnc == rhs.isLongMnc;
}

bool operator!=(const Plmn &lhs, const Plmn &rhs)
{
    return !(lhs == rhs);
}

bool operator==(const GlobalNci &lhs, const GlobalNci &rhs)
{
    return lhs.plmn == rhs.plmn && lhs.nci == rhs.nci;
}

bool operator!=(const GlobalNci &lhs, const GlobalNci &rhs)
{
    return !(lhs == rhs);
}

bool operator==(const Tai &lhs, const Tai &rhs)
{
    return lhs.plmn == rhs.plmn && lhs.tac == rhs.tac;
}

bool operator!=(const Tai &lhs, const Tai &rhs)
{
    return !(lhs == rhs);
}

Json ToJson(const EDeregCause &v)
{
    switch (v)
    {
    case EDeregCause::NORMAL:
        return "NORMAL";
    case EDeregCause::SWITCH_OFF:
        return "SWITCH-OFF";
    case EDeregCause::USIM_REMOVAL:
        return "USIM-REMOVAL";
    case EDeregCause::DISABLE_5G:
        return "DISABLE-5G";
    case EDeregCause::ECALL_INACTIVITY:
        return "ECALL-INACTIVITY";
    default:
        return "?";
    }
}

Json ToJson(const EInitialRegCause &v)
{
    switch (v)
    {
    case EInitialRegCause::EMERGENCY_SERVICES:
        return "EMERGENCY-SERVICES";
    case EInitialRegCause::MM_DEREG_NORMAL_SERVICE:
        return "MM-DEREG-NORMAL-SERVICE";
    case EInitialRegCause::T3346_EXPIRY:
        return "T3346-EXPIRY";
    case EInitialRegCause::DUE_TO_DEREGISTRATION:
        return "DUE-TO-DEREGISTRATION";
    case EInitialRegCause::DUE_TO_SERVICE_REJECT:
        return "DUE-TO-SERVICE_REJECT";
    case EInitialRegCause::TAI_CHANGE_IN_ATT_REG:
        return "TAI-CHANGE-IN-ATT-REG";
    case EInitialRegCause::PLMN_CHANGE_IN_ATT_REG:
        return "PLMN-CHANGE-IN-ATT-REG";
    case EInitialRegCause::T3346_EXPIRY_IN_ATT_REG:
        return "T3346-EXPIRY-IN-ATT-REG";
    case EInitialRegCause::T3502_EXPIRY_IN_ATT_REG:
        return "T3502-EXPIRY-IN-ATT-REG";
    case EInitialRegCause::T3511_EXPIRY_IN_ATT_REG:
        return "T3511-EXPIRY-IN-ATT-REG";
    default:
        return "?";
    }
}

Json ToJson(const Tai &v)
{
    if (!v.hasValue())
        return nullptr;
    return "PLMN[" + ToJson(v.plmn).str() + "] TAC[" + std::to_string(v.tac) + "]";
}

void NetworkSlice::addIfNotExists(const SingleSlice &slice)
{
    if (!std::any_of(slices.begin(), slices.end(), [&slice](auto &s) { return s == slice; }))
        slices.push_back(slice);
}

std::size_t std::hash<Plmn>::operator()(const Plmn &v) const noexcept
{
    std::size_t h = 0;
    utils::HashCombine(h, v.mcc);
    utils::HashCombine(h, v.mnc);
    utils::HashCombine(h, v.isLongMnc);
    return h;
}

std::size_t std::hash<GlobalNci>::operator()(const GlobalNci &v) const noexcept
{
    std::size_t h = 0;
    utils::HashCombine(h, v.plmn);
    utils::HashCombine(h, v.nci);
    return h;
}

std::size_t std::hash<Tai>::operator()(const Tai &v) const noexcept
{
    std::size_t h = 0;
    utils::HashCombine(h, v.plmn);
    utils::HashCombine(h, v.tac);
    return h;
}

bool Plmn::hasValue() const
{
    return this->mcc != 0;
}

Tai::Tai() : plmn{}, tac{}
{
}

Tai::Tai(const Plmn &plmn, int tac) : plmn{plmn}, tac{tac}
{
}

Tai::Tai(int mcc, int mnc, bool longMnc, int tac) : plmn{mcc, mnc, longMnc}, tac{tac}
{
}

bool Tai::hasValue() const
{
    return plmn.hasValue();
}
Loading