diff --git a/ccsrc/Protocols/sctp/client.cc b/ccsrc/Protocols/sctp/client.cc new file mode 100644 index 0000000000000000000000000000000000000000..9790bfac7c5d5d8cce913896ecd5ac3c6d1b3b89 --- /dev/null +++ b/ccsrc/Protocols/sctp/client.cc @@ -0,0 +1,88 @@ +// +// 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 &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(ppid), handler); + ReceiveMessage(sd, static_cast(ppid),sl); +} + +[[noreturn]] void sctp::SctpClient::ReceiverThread(sctp::SctpClient* client) + +//[[noreturn]] static void ReceiverThread(std::pair *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); +} diff --git a/ccsrc/Protocols/sctp/client.hh b/ccsrc/Protocols/sctp/client.hh new file mode 100644 index 0000000000000000000000000000000000000000..c4376b6800b30474cdb95ef6a835950e044578f3 --- /dev/null +++ b/ccsrc/Protocols/sctp/client.hh @@ -0,0 +1,45 @@ +// +// 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 +#include + +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 &data); + + //void receive(ISctpHandler *handler); + void receive(); + [[noreturn]] static void ReceiverThread(sctp::SctpClient *client); + //[[noreturn]] static void ReceiverThread(std::pair *args); + +}; + +} // namespace sctp diff --git a/ccsrc/Protocols/sctp/common.cc b/ccsrc/Protocols/sctp/common.cc new file mode 100644 index 0000000000000000000000000000000000000000..164b516c31ef6c70dce214b9375ee83ce860fc47 --- /dev/null +++ b/ccsrc/Protocols/sctp/common.cc @@ -0,0 +1,333 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 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(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 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 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(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 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 data(16); + if (!IPv6FromString(address.c_str(), data.data())) + return {}; + return OctetString(std::move(data)); + } + else + return {}; +} + +std::string utils::VectorToHexString(const std::vector &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(big < 10 ? '0' + big : 'A' + (big - 10)); + char littleChar = static_cast(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; +} diff --git a/ccsrc/Protocols/sctp/common.hh b/ccsrc/Protocols/sctp/common.hh new file mode 100644 index 0000000000000000000000000000000000000000..dbf598f485711e71ab73025c0869300488ef8b91 --- /dev/null +++ b/ccsrc/Protocols/sctp/common.hh @@ -0,0 +1,77 @@ +// +// 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 +#include +#include +#include +#include + +namespace utils +{ + +std::vector HexStringToVector(const std::string &hex); +std::string VectorToHexString(const std::vector &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 +inline void ClearAndDelete(std::vector &vector) +{ + for (T *item : vector) + delete item; + vector.clear(); +} + +template +inline void EraseWhere(std::vector &vector, P predicate) +{ + vector.erase(std::remove_if(vector.begin(), vector.end(), std::forward

(predicate)), vector.end()); +} + +template +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(i); + else + stream << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << i; + return stream.str(); +} + +template +inline void HashCombine(std::size_t &seed, const T &v) +{ + std::hash hasher{}; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +} // namespace utils diff --git a/ccsrc/Protocols/sctp/common_types.cc b/ccsrc/Protocols/sctp/common_types.cc new file mode 100644 index 0000000000000000000000000000000000000000..2778bb5f752d14c476862894a5bd763f9df9bf2a --- /dev/null +++ b/ccsrc/Protocols/sctp/common_types.cc @@ -0,0 +1,251 @@ +// +// 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 +#include +#include +#include + +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(this->tmsi)) | (static_cast(this->amfPointer) << 32LL) | + (static_cast(this->amfSetId) << 38LL); +} + +GutiMobileIdentity GutiMobileIdentity::FromSTmsi(int64_t sTmsi) +{ + GutiMobileIdentity res; + res.tmsi = octet4{static_cast(sTmsi & 0xFFFFFFFFLL)}; + res.amfPointer = static_cast(((sTmsi >> 32LL) & 0b111111LL)); + res.amfSetId = static_cast(((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::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::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::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(); +} diff --git a/ccsrc/Protocols/sctp/common_types.hh b/ccsrc/Protocols/sctp/common_types.hh new file mode 100644 index 0000000000000000000000000000000000000000..af3ae6ae712b22d7d3f1903df12fa770fd99f309 --- /dev/null +++ b/ccsrc/Protocols/sctp/common_types.hh @@ -0,0 +1,242 @@ +// +// 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 "json.hh" +#include "octet.hh" + +#include +#include +#include +#include +#include + +enum class EPagingDrx +{ + V32 = 32, + V64 = 64, + V128 = 128, + V256 = 256 +}; + +struct Plmn +{ + int mcc{}; + int mnc{}; + bool isLongMnc{}; + + [[nodiscard]] bool hasValue() const; +}; + +struct Tai +{ + Plmn plmn; + int tac; + + Tai(); + Tai(const Plmn &plmn, int tac); + Tai(int mcc, int mnc, bool longMnc, int tac); + + [[nodiscard]] bool hasValue() const; +}; + +struct SingleSlice +{ + octet sst{}; + std::optional sd{}; +}; + +struct NetworkSlice +{ + std::vector slices{}; + + void addIfNotExists(const SingleSlice &slice); +}; + +enum class PduSessionType +{ + IPv4, + IPv6, + IPv4v6, + ETHERNET, + UNSTRUCTURED +}; + +struct PlmnSupport +{ + Plmn plmn{}; + NetworkSlice sliceSupportList{}; +}; + +struct GutiMobileIdentity +{ + Plmn plmn; // Not used in TMSI + octet amfRegionId; // Not used in TMSI + int amfSetId; // 10-bit + int amfPointer; // 6-bit + octet4 tmsi; + + GutiMobileIdentity() : plmn{}, amfRegionId{}, amfSetId{}, amfPointer{}, tmsi{} + { + } + + GutiMobileIdentity(const Plmn &plmn, const octet &amfRegionId, int amfSetId, int amfPointer, const octet4 &tmsi) + : plmn(plmn), amfRegionId(amfRegionId), amfSetId(amfSetId), amfPointer(amfPointer), tmsi(tmsi) + { + } + + [[nodiscard]] int64_t toTmsiValue() const; + + static GutiMobileIdentity FromSTmsi(int64_t sTmsi); +}; + +struct ImsiMobileIdentity +{ + Plmn plmn; + std::string routingIndicator; + int protectionSchemaId; // 4-bit + octet homeNetworkPublicKeyIdentifier; + std::string schemeOutput; + + ImsiMobileIdentity() + : plmn{}, routingIndicator{}, protectionSchemaId{}, homeNetworkPublicKeyIdentifier{}, schemeOutput{} + { + } + + ImsiMobileIdentity(const Plmn &plmn, std::string routingIndicator, int protectionSchemaId, + const octet &homeNetworkPublicKeyIdentifier, std::string schemeOutput) + : plmn(plmn), routingIndicator(std::move(routingIndicator)), protectionSchemaId(protectionSchemaId), + homeNetworkPublicKeyIdentifier(homeNetworkPublicKeyIdentifier), schemeOutput(std::move(schemeOutput)) + { + } +}; + +struct Supi +{ + std::string type; + std::string value; + + Supi(std::string type, std::string value) : type(std::move(type)), value(std::move(value)) + { + } + + static Supi Parse(const std::string &supi); +}; + +enum class EDeregCause +{ + NORMAL, + SWITCH_OFF, + USIM_REMOVAL, + DISABLE_5G, + ECALL_INACTIVITY, +}; + +enum class EInitialRegCause +{ + EMERGENCY_SERVICES, + MM_DEREG_NORMAL_SERVICE, + T3346_EXPIRY, + DUE_TO_DEREGISTRATION, + DUE_TO_SERVICE_REJECT, + TAI_CHANGE_IN_ATT_REG, + PLMN_CHANGE_IN_ATT_REG, + T3346_EXPIRY_IN_ATT_REG, + T3502_EXPIRY_IN_ATT_REG, + T3511_EXPIRY_IN_ATT_REG, +}; + +struct GlobalNci +{ + Plmn plmn{}; + int64_t nci{}; + + GlobalNci() = default; + + GlobalNci(const Plmn &plmn, int64_t nci) : plmn(plmn), nci(nci) + { + } +}; + +enum class ECellCategory +{ + BARRED_CELL, + RESERVED_CELL, + ACCEPTABLE_CELL, + SUITABLE_CELL, +}; + +struct Vector3 +{ + int x{}; + int y{}; + int z{}; + + Vector3() = default; + + Vector3(int x, int y, int z) : x(x), y(y), z(z) + { + } +}; + +struct UacAiBarringSet +{ + bool ai1 = false; + bool ai2 = false; + bool ai11 = false; + bool ai12 = false; + bool ai13 = false; + bool ai14 = false; + bool ai15 = false; +}; + +bool operator==(const Plmn &lhs, const Plmn &rhs); +bool operator!=(const Plmn &lhs, const Plmn &rhs); + +bool operator==(const Tai &lhs, const Tai &rhs); +bool operator!=(const Tai &lhs, const Tai &rhs); + +bool operator==(const SingleSlice &lhs, const SingleSlice &rhs); +bool operator!=(const SingleSlice &lhs, const SingleSlice &rhs); + +bool operator==(const GlobalNci &lhs, const GlobalNci &rhs); +bool operator!=(const GlobalNci &lhs, const GlobalNci &rhs); + +Json ToJson(const Supi &v); +Json ToJson(const Plmn &v); +Json ToJson(const Tai &v); +Json ToJson(const SingleSlice &v); +Json ToJson(const NetworkSlice &v); +Json ToJson(const PlmnSupport &v); +Json ToJson(const EDeregCause &v); +Json ToJson(const ECellCategory &v); +Json ToJson(const EInitialRegCause &v); + +namespace std +{ + +template <> +struct hash +{ + std::size_t operator()(const Plmn &v) const noexcept; +}; + +template <> +struct hash +{ + std::size_t operator()(const Tai &v) const noexcept; +}; + +template <> +struct hash +{ + std::size_t operator()(const GlobalNci &v) const noexcept; +}; + +} // namespace std diff --git a/ccsrc/Protocols/sctp/constants.hh b/ccsrc/Protocols/sctp/constants.hh new file mode 100644 index 0000000000000000000000000000000000000000..f252ec79123700906cc9054a49375cc1be76db60 --- /dev/null +++ b/ccsrc/Protocols/sctp/constants.hh @@ -0,0 +1,41 @@ +// +// 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 + +struct cons +{ + // Version information + static constexpr const uint8_t Major = 3; + static constexpr const uint8_t Minor = 2; + static constexpr const uint8_t Patch = 6; + static constexpr const char *Project = "UERANSIM"; + static constexpr const char *Tag = "v3.2.6"; + static constexpr const char *Name = "UERANSIM v3.2.6"; + static constexpr const char *Owner = "ALİ GÜNGÖR"; + + // Some port values + static constexpr const uint16_t GtpPort = 2152; + static constexpr const uint16_t RadioLinkPort = 4997; + + // TUN interface + static constexpr const char *TunNamePrefix = "uesimtun"; + static constexpr const int TunMtu = 1400; + + // Constraints + static constexpr const int MinNodeName = 3; + static constexpr const int MaxNodeName = 1024; + + // Others + static constexpr const char *CMD_SERVER_IP = "127.0.0.1"; + static constexpr const char *PROC_TABLE_DIR = "/tmp/UERANSIM.proc-table/"; + static constexpr const char *PROCESS_DIR = "/proc/"; + static constexpr const char DIR_SEPARATOR = '/'; +}; diff --git a/ccsrc/Protocols/sctp/internal.cc b/ccsrc/Protocols/sctp/internal.cc new file mode 100644 index 0000000000000000000000000000000000000000..2c49a8de774dd35d9d4b8f6a83e5fb4d3bae758d --- /dev/null +++ b/ccsrc/Protocols/sctp/internal.cc @@ -0,0 +1,353 @@ +// +// 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 "internal.hh" +#include "types.hh" +//#include "loggers.hh" +#include "sctp_layer.hh" +#include + +#include +#include +#include +#include +#include +#include + +//#include +#include + +#define RECEIVE_BUFFER_SIZE 8192u + +namespace sctp +{ + +[[noreturn]] static void ThrowError(const std::string &prefix) +{ + throw SctpError(prefix); +} + +[[noreturn]] static void ThrowError(const std::string &prefix, int err) +{ + char *msg = strdup(strerror(err)); + std::string str(msg); + free(msg); + throw SctpError(prefix + str); +} + +int CreateSocket() +{ + int sd = socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP); + if (sd < 0) + ThrowError("SCTP socket could not be created"); + return sd; +} + +void BindSocket(int sd, const std::string &address, uint16_t port) +{ + sockaddr addr = {}; + std::memset(&addr, 0, sizeof(sockaddr_in)); + + int ipVersion = utils::GetIpVersion(address); + + if (ipVersion == 4) + { + auto addr4 = (sockaddr_in *)&addr; + addr4->sin_family = AF_INET; + addr4->sin_port = htons(port); + + if (inet_pton(AF_INET, address.c_str(), &(addr4->sin_addr)) != 1) + ThrowError("Bad IPv4 address."); + } + else if (ipVersion == 6) + { + auto addr6 = (sockaddr_in6 *)&addr; + addr6->sin6_family = AF_INET6; + addr6->sin6_port = htons(port); + if (inet_pton(AF_INET6, address.c_str(), &(addr6->sin6_addr)) != 1) + ThrowError("Bad IPv6 address."); + } + else + ThrowError("Bad IPv4 or IPv6 address."); + + if (sctp_bindx(sd, &addr, 1, SCTP_BINDX_ADD_ADDR)) + ThrowError("SCTP bind failed: ", errno); +} + +void SetInitOptions(int sd, int maxRxStreams, int maxTxStreams, int maxAttempts, int initTimeoutMs) +{ + sctp_initmsg init{}; + init.sinit_max_instreams = maxRxStreams; + init.sinit_num_ostreams = maxTxStreams; + init.sinit_max_attempts = maxAttempts; + init.sinit_max_init_timeo = initTimeoutMs; + + if (setsockopt(sd, IPPROTO_SCTP, SCTP_INITMSG, &init, (socklen_t)sizeof(struct sctp_initmsg)) < 0) + ThrowError("SCTP SCTP_INITMSG option failed"); +} + +void SetEventOptions(int sd) +{ + sctp_event_subscribe events{}; + events.sctp_data_io_event = 1; + events.sctp_association_event = 1; + events.sctp_address_event = 1; + events.sctp_send_failure_event = 1; + events.sctp_peer_error_event = 1; + events.sctp_shutdown_event = 1; + events.sctp_partial_delivery_event = 1; + + if (setsockopt(sd, IPPROTO_SCTP, SCTP_EVENTS, &events, 8) < 0) + ThrowError("SCTP SCTP_EVENTS option failed"); +} + +void StartListening(int sd) +{ + if (listen(sd, 10)) + ThrowError("SCTP socket could not listen: ", errno); +} + +void CloseSocket(int sd) +{ + close(sd); +} + +void Accept(int sd) +{ + sockaddr saddr{}; + socklen_t saddr_size{}; + + int clientSd = accept(sd, &saddr, &saddr_size); + if (clientSd < 0) + ThrowError("SCTP accept failure: ", errno); +} + +void Connect(int sd, const std::string &address, uint16_t port) +{ + addrinfo hints{}; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = 0; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + addrinfo *result, *rp; + + int s = getaddrinfo(address.c_str(), std::to_string(port).c_str(), &hints, &result); + if (s != 0) + ThrowError("SCTP getaddrinfo failure: ", errno); + + bool connected = false; + + for (rp = result; rp != nullptr; rp = rp->ai_next) + { + // TODO: make non blocking and set timeout (O_NONBLOCK etc) + if (connect(sd, rp->ai_addr, rp->ai_addrlen) != -1) + { + connected = true; + break; + } + } + + freeaddrinfo(result); + + if (!connected) + ThrowError("SCTP could not connect: ", errno); +} + +void SendMessage(int sd, const uint8_t *buffer, size_t length, int ppid, uint16_t stream) +{ + if (sctp_sendmsg(sd, buffer, length, nullptr, 0, htonl(ppid), 0, stream, 0, 0) < 0) + ThrowError("SCTP send message failure: ", errno); +} + + +void ReceiveMessage(int sd, uint32_t ppid, sctp_layer* sl) +{ + uint8_t buffer[RECEIVE_BUFFER_SIZE]; + sockaddr_in addr{}; + sctp_sndrcvinfo info{}; + int flags = 0; + + auto fromLen = (socklen_t)sizeof(sockaddr_in); + + std::memset((void *)&addr, 0, sizeof(sockaddr_in)); + std::memset((void *)&info, 0, sizeof(sctp_sndrcvinfo)); + + int r = sctp_recvmsg(sd, (void *)buffer, RECEIVE_BUFFER_SIZE, (sockaddr *)&addr, &fromLen, &info, &flags); + //loggers::get_instance().log(">>> client::receiveMessage: %d, len: %d",sd,r); + //loggers::get_instance().log_msg(">>> client::receiveMessage: payload=", static_castbuffer); + + if (r < 0) + { + if (errno == ECONNRESET) + { + //if (handler) + // handler->onConnectionReset(); + return; + } + ThrowError("SCTP receive message failure: ", errno); + } + + if (r == 0) + return; // no data + + if (!(flags & MSG_EOR)) + ThrowError("SCTP partial message received, which is not handled"); + + if (flags & MSG_NOTIFICATION) + { + auto *notification = (union sctp_notification *)buffer; + + if (notification->sn_header.sn_type == sctp_sn_type::SCTP_SHUTDOWN_EVENT) + { + //if (handler) + // handler->onAssociationShutdown(); + } + else if (notification->sn_header.sn_type == sctp_sn_type::SCTP_ASSOC_CHANGE) + { + auto *sac = ¬ification->sn_assoc_change; + if (sac) + { + if (sac->sac_state == sctp_sac_state::SCTP_COMM_UP) + { + sctp_status status{}; + std::memset(&status, 0, sizeof(sctp_status)); + auto socklen = (socklen_t)sizeof(sctp_status); + + if (getsockopt(sd, IPPROTO_SCTP, SCTP_STATUS, &status, &socklen) < 0) + ThrowError("SCTP_STATUS obtaining failed: ", errno); + + //if (handler) + // handler->onAssociationSetup(status.sstat_assoc_id, status.sstat_instrms, status.sstat_outstrms); + } + else if (sac->sac_state == sctp_sac_state::SCTP_COMM_LOST) + { + //if (handler) + // handler->onAssociationShutdown(); + } + else + { + //if (handler) + // handler->onUnhandledNotification(); + } + } + } + else + { + //if (handler) + // handler->onUnhandledNotification(); + } + } + else + { + if (info.sinfo_ppid != ppid) + { + // Perhaps we should ignore the message. But some core networks does not set this correctly. Therefore + // do nothing for now. + } + + //handler->onMessage(buffer, r, info.sinfo_stream); + + //loggers::get_instance().log(">>> client::receiveMessage: DO upper port RECEIVE!!!"); + sl->message_incoming(buffer, r, sd); + } +} + + + + + +void ReceiveMessage(int sd, uint32_t ppid, ISctpHandler *handler) +{ + uint8_t buffer[RECEIVE_BUFFER_SIZE]; + sockaddr_in addr{}; + sctp_sndrcvinfo info{}; + int flags = 0; + + auto fromLen = (socklen_t)sizeof(sockaddr_in); + + std::memset((void *)&addr, 0, sizeof(sockaddr_in)); + std::memset((void *)&info, 0, sizeof(sctp_sndrcvinfo)); + + int r = sctp_recvmsg(sd, (void *)buffer, RECEIVE_BUFFER_SIZE, (sockaddr *)&addr, &fromLen, &info, &flags); + + if (r < 0) + { + if (errno == ECONNRESET) + { + if (handler) + handler->onConnectionReset(); + return; + } + ThrowError("SCTP receive message failure: ", errno); + } + + if (r == 0) + return; // no data + + if (!(flags & MSG_EOR)) + ThrowError("SCTP partial message received, which is not handled"); + + if (flags & MSG_NOTIFICATION) + { + auto *notification = (union sctp_notification *)buffer; + + if (notification->sn_header.sn_type == sctp_sn_type::SCTP_SHUTDOWN_EVENT) + { + if (handler) + handler->onAssociationShutdown(); + } + else if (notification->sn_header.sn_type == sctp_sn_type::SCTP_ASSOC_CHANGE) + { + auto *sac = ¬ification->sn_assoc_change; + if (sac) + { + if (sac->sac_state == sctp_sac_state::SCTP_COMM_UP) + { + sctp_status status{}; + std::memset(&status, 0, sizeof(sctp_status)); + auto socklen = (socklen_t)sizeof(sctp_status); + + if (getsockopt(sd, IPPROTO_SCTP, SCTP_STATUS, &status, &socklen) < 0) + ThrowError("SCTP_STATUS obtaining failed: ", errno); + + if (handler) + handler->onAssociationSetup(status.sstat_assoc_id, status.sstat_instrms, status.sstat_outstrms); + } + else if (sac->sac_state == sctp_sac_state::SCTP_COMM_LOST) + { + if (handler) + handler->onAssociationShutdown(); + } + else + { + if (handler) + handler->onUnhandledNotification(); + } + } + } + else + { + if (handler) + handler->onUnhandledNotification(); + } + } + else + { + if (info.sinfo_ppid != ppid) + { + // Perhaps we should ignore the message. But some core networks does not set this correctly. Therefore + // do nothing for now. + } + + handler->onMessage(buffer, r, info.sinfo_stream); + } +} + +} // namespace sctp diff --git a/ccsrc/Protocols/sctp/internal.hh b/ccsrc/Protocols/sctp/internal.hh new file mode 100644 index 0000000000000000000000000000000000000000..8c7ca194147d349baf55f2ebb01052c336c375a7 --- /dev/null +++ b/ccsrc/Protocols/sctp/internal.hh @@ -0,0 +1,29 @@ +// +// 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 +namespace sctp +{ + +int CreateSocket(); +void BindSocket(int sd, const std::string &address, uint16_t port); +void SetInitOptions(int sd, int maxRxStreams, int maxTxStreams, int maxAttempts, int initTimeoutMs); +void SetEventOptions(int sd); +void StartListening(int sd); +void CloseSocket(int sd); +void Accept(int sd); +void Connect(int sd, const std::string &address, uint16_t port); +void SendMessage(int sd, const uint8_t *buffer, size_t length, int ppid, uint16_t stream); +void ReceiveMessage(int sd, uint32_t ppid, sctp_layer* sl); +void ReceiveMessage(int sd, uint32_t ppid, ISctpHandler *handler); + +} // namespace sctp diff --git a/ccsrc/Protocols/sctp/json.cc b/ccsrc/Protocols/sctp/json.cc new file mode 100644 index 0000000000000000000000000000000000000000..ff7da70ce7697d1e184ace68b9bd2c7640108c9f --- /dev/null +++ b/ccsrc/Protocols/sctp/json.cc @@ -0,0 +1,413 @@ +// +// 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 "json.hh" + +#include +#include + +static std::string EscapeJson(const std::string &str) +{ + std::string output; + output.reserve(str.length()); + + for (char i : str) + { + switch (i) + { + case '"': + output += "\\\""; + break; + case '/': + output += "\\/"; + break; + case '\b': + output += "\\b"; + break; + case '\f': + output += "\\f"; + break; + case '\n': + output += "\\n"; + break; + case '\r': + output += "\\r"; + break; + case '\t': + output += "\\t"; + break; + case '\\': + output += "\\\\"; + break; + default: + output += i; + break; + } + } + return output; +} + +static void AppendJson(const Json &json, std::stringstream &stream, int indentation) +{ + std::string indent(indentation, ' '); + + switch (json.type()) + { + case Json::Type::NULL_TYPE: + stream << "null"; + break; + case Json::Type::STRING: + stream << "\"" << EscapeJson(json.str()) << "\""; + break; + case Json::Type::NUMBER: + case Json::Type::BOOL: + stream << json.str(); + break; + case Json::Type::OBJECT: { + stream << "{\n"; + int index = 0; + for (auto &item : json) + { + stream << indent << " " << item.first << ": "; + AppendJson(item.second, stream, indentation + 1); + if (index == json.itemCount() - 1) + stream << "\n"; + else + stream << ",\n"; + index++; + } + stream << indent << "}"; + break; + } + case Json::Type::ARRAY: { + stream << "[\n"; + int index = 0; + for (auto &item : json) + { + stream << indent << " "; + AppendJson(item.second, stream, indentation + 1); + if (index == json.itemCount() - 1) + stream << "\n"; + else + stream << ",\n"; + index++; + } + stream << indent << "]"; + break; + } + } +} + +static void AppendYaml(const Json &json, std::stringstream &stream, int indentation, bool initialIndentation) +{ + std::string indent(indentation, ' '); + + switch (json.type()) + { + case Json::Type::NULL_TYPE: + stream << "null"; + break; + case Json::Type::STRING: + case Json::Type::NUMBER: + case Json::Type::BOOL: + stream << json.str(); // TODO: Escape YAML + break; + case Json::Type::OBJECT: { + int index = 0; + for (auto &item : json) + { + if (index > 0 || initialIndentation) + stream << indent; + stream << item.first << ": "; + + int initialPos = -1; + if (item.second.isPrimitive()) + AppendYaml(item.second, stream, indentation + 1, false); + else + { + stream << "\n"; + initialPos = static_cast(stream.tellp()); + AppendYaml(item.second, stream, indentation + 1, true); + } + if (index != json.itemCount() - 1 && initialPos != stream.tellp()) + stream << "\n"; + index++; + } + break; + } + case Json::Type::ARRAY: { + int index = 0; + for (auto &item : json) + { + if (index > 0 || initialIndentation) + stream << indent; + stream << "- "; + AppendYaml(item.second, stream, indentation + 2, false); + if (index != json.itemCount() - 1) + stream << "\n"; + index++; + } + break; + } + } +} + +Json::Json() : m_type{Type::NULL_TYPE} +{ +} + +Json::Json(std::nullptr_t) : m_type{Type::NULL_TYPE} +{ +} + +Json::Json(std::string str) : m_type{Type::STRING}, m_strVal(std::move(str)) +{ +} + +Json::Json(bool v) : m_type{Type::BOOL}, m_strVal(v ? "true" : "false"), m_intVal{v} +{ +} + +Json::Json(uint8_t v) : m_type{Type::NUMBER}, m_strVal(std::to_string(v)), m_intVal{static_cast(v)} +{ +} + +Json::Json(int8_t v) : m_type{Type::NUMBER}, m_strVal(std::to_string(v)), m_intVal{static_cast(v)} +{ +} + +Json::Json(uint16_t v) : m_type{Type::NUMBER}, m_strVal(std::to_string(v)), m_intVal{static_cast(v)} +{ +} + +Json::Json(int16_t v) : m_type{Type::NUMBER}, m_strVal(std::to_string(v)), m_intVal{static_cast(v)} +{ +} + +Json::Json(uint32_t v) : m_type{Type::NUMBER}, m_strVal(std::to_string(v)), m_intVal{static_cast(v)} +{ +} + +Json::Json(int32_t v) : m_type{Type::NUMBER}, m_strVal(std::to_string(v)), m_intVal{static_cast(v)} +{ +} + +Json::Json(int64_t v) : m_type{Type::NUMBER}, m_strVal(std::to_string(v)), m_intVal{v} +{ +} + +Json::Type Json::type() const +{ + return m_type; +} + +bool Json::isString() const +{ + return m_type == Type::STRING; +} + +bool Json::isBool() const +{ + return m_type == Type::BOOL; +} + +bool Json::isObject() const +{ + return m_type == Type::OBJECT; +} + +bool Json::isNumber() const +{ + return m_type == Type::NUMBER; +} + +bool Json::isArray() const +{ + return m_type == Type::ARRAY; +} + +bool Json::isNull() const +{ + return m_type == Type::NULL_TYPE; +} + +bool Json::isPrimitive() const +{ + return !isObject() && !isArray(); +} + +std::string Json::str() const +{ + return m_strVal; +} + +int Json::int32() const +{ + return static_cast(m_intVal); +} + +int64_t Json::int64() const +{ + return m_intVal; +} + +bool Json::boolean() const +{ + return m_intVal; +} + +int Json::itemCount() const +{ + return static_cast(m_children.size()); +} + +Json::iterator Json::begin() +{ + return m_children.begin(); +} + +Json::const_iterator Json::begin() const +{ + return m_children.begin(); +} + +Json::iterator Json::end() +{ + return m_children.end(); +} + +Json::const_iterator Json::end() const +{ + return m_children.end(); +} + +std::string Json::dumpJson() const +{ + std::stringstream ss{}; + AppendJson(*this, ss, 0); + return ss.str(); +} + +std::string Json::dumpYaml() const +{ + std::stringstream ss{}; + AppendYaml(*this, ss, 0, false); + return ss.str(); +} + +Json Json::Arr(std::initializer_list &&elements) +{ + Json json{}; + json.m_type = Type::ARRAY; + for (auto &item : elements) + json.push(item); + return json; +} + +Json Json::Arr(const std::vector &elements) +{ + Json json{}; + json.m_type = Type::ARRAY; + for (auto &item : elements) + json.push(item); + return json; +} + +Json Json::Arr(std::vector &&elements) +{ + Json json{}; + json.m_type = Type::ARRAY; + for (auto &item : elements) + json.push(std::move(item)); + return json; +} + +Json Json::Obj(std::initializer_list> &&elements) +{ + Json json{}; + json.m_type = Type::OBJECT; + for (auto &item : elements) + json.put(item.first, item.second); + return json; +} + +void Json::push(Json element) +{ + if (m_type != Type::ARRAY) + return; + + m_children.emplace_back(std::to_string(m_children.size()), std::move(element)); +} + +void Json::put(std::string key, Json value) +{ + if (m_type != Type::OBJECT) + return; + + // Replace if the key is already present + for (auto &item : m_children) + { + if (item.first == key) + { + item.second = std::move(value); + return; + } + } + + m_children.emplace_back(std::move(key), std::move(value)); +} + +Json ToJson(std::nullptr_t) +{ + return nullptr; +} + +Json ToJson(bool v) +{ + return v; +} + +Json ToJson(const std::string &v) +{ + return v; +} + +Json ToJson(uint8_t v) +{ + return v; +} + +Json ToJson(int8_t v) +{ + return v; +} + +Json ToJson(uint16_t v) +{ + return v; +} + +Json ToJson(int16_t v) +{ + return v; +} + +Json ToJson(uint32_t v) +{ + return v; +} + +Json ToJson(int32_t v) +{ + return v; +} + +Json ToJson(int64_t v) +{ + return v; +} diff --git a/ccsrc/Protocols/sctp/json.hh b/ccsrc/Protocols/sctp/json.hh new file mode 100644 index 0000000000000000000000000000000000000000..cff6aa7f3bca27e5c94ab6c604a658ac01fbb9fb --- /dev/null +++ b/ccsrc/Protocols/sctp/json.hh @@ -0,0 +1,153 @@ +// +// 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 +#include +#include +#include +#include +#include +#include + +class Json +{ + public: + enum class Type + { + NULL_TYPE, + STRING, + BOOL, + NUMBER, + OBJECT, + ARRAY, + }; + + private: + Type m_type{}; + std::string m_strVal{}; + int64_t m_intVal{}; + + // - Holding children via vector instead of map/unordered_map (because insertion order is needed to be preserved). + // Therefore add/remove operation is O(n), but performance is not a concern for JSONs. (Add operation is also + // O(n) since we compare to check the key is already present) + // - NOTE: We're using Json type itself here which is incomplete herein. But C++17 allows std::vector, std::list, + // and std::forward_list to have incomplete types. For older versions we would need pointer etc. + std::vector> m_children{}; + + private: + typedef decltype(m_children.begin()) iterator; + typedef decltype(const_cast> &>(m_children).begin()) const_iterator; + + public: + Json(); + /* no-explicit */ Json(std::nullptr_t v); + /* no-explicit */ Json(std::string str); + /* no-explicit */ Json(bool v); + /* no-explicit */ Json(uint8_t v); + /* no-explicit */ Json(int8_t v); + /* no-explicit */ Json(uint16_t v); + /* no-explicit */ Json(int16_t v); + /* no-explicit */ Json(uint32_t v); + /* no-explicit */ Json(int32_t v); + /* no-explicit */ Json(int64_t v); + /* no-explicit */ Json(uint64_t v) = delete; + + template + inline /* no-explicit */ Json(const char (&v)[N]) : Json(std::string(v)) + { + } + + template + Json(T) = delete; + + public: + static Json Arr(std::initializer_list &&elements); + static Json Arr(const std::vector &elements); + static Json Arr(std::vector &&elements); + static Json Obj(std::initializer_list> &&elements); + + public: + [[nodiscard]] Type type() const; + [[nodiscard]] bool isNull() const; + [[nodiscard]] bool isString() const; + [[nodiscard]] bool isBool() const; + [[nodiscard]] bool isNumber() const; + [[nodiscard]] bool isObject() const; + [[nodiscard]] bool isArray() const; + [[nodiscard]] bool isPrimitive() const; + [[nodiscard]] int itemCount() const; + + public: + [[nodiscard]] std::string str() const; + [[nodiscard]] int int32() const; + [[nodiscard]] int64_t int64() const; + [[nodiscard]] bool boolean() const; + + public: + void push(Json element); + void put(std::string key, Json value); + + public: + [[nodiscard]] iterator begin(); + [[nodiscard]] const_iterator begin() const; + [[nodiscard]] iterator end(); + [[nodiscard]] const_iterator end() const; + + public: + [[nodiscard]] std::string dumpJson() const; + [[nodiscard]] std::string dumpYaml() const; +}; + +Json ToJson(std::nullptr_t); +Json ToJson(bool v); +Json ToJson(const std::string &v); +Json ToJson(uint8_t v); +Json ToJson(int8_t v); +Json ToJson(uint16_t v); +Json ToJson(int16_t v); +Json ToJson(uint32_t v); +Json ToJson(int32_t v); +Json ToJson(int64_t v); + +template +inline Json ToJson(const std::optional &v) +{ + return v.has_value() ? ToJson(*v) : Json{nullptr}; +} + +template +inline Json ToJson(T *v) +{ + return v != nullptr ? ToJson(*v) : Json{nullptr}; +} + +template +inline Json ToJson(const std::unique_ptr &v) +{ + return v != nullptr ? ToJson(*v) : Json{nullptr}; +} + +template +inline Json ToJson(const T *v) +{ + return v != nullptr ? ToJson(*v) : Json{nullptr}; +} + +template +inline Json ToJson(const std::vector &v) +{ + Json j = Json::Arr({}); + for (auto &item : v) + j.push(ToJson(item)); + return j; +} + +template +Json ToJson(T) = delete; diff --git a/ccsrc/Protocols/sctp/module.mk b/ccsrc/Protocols/sctp/module.mk new file mode 100644 index 0000000000000000000000000000000000000000..327f24ea7e1907314ac3759da103ec4eff3bc4ee --- /dev/null +++ b/ccsrc/Protocols/sctp/module.mk @@ -0,0 +1,3 @@ +sources := client.cc common_types.cc json.cc octet_string.cc sctp_layer.cc time_stamp.cc unique_buffer.cc common.cc internal.cc octet.cc sctp.cc server.cc types.cc scoped_thread.cc +includes := . + diff --git a/ccsrc/Protocols/sctp/octet.cc b/ccsrc/Protocols/sctp/octet.cc new file mode 100644 index 0000000000000000000000000000000000000000..434de98304a0cc898f3267ecd314c1fc009b4a88 --- /dev/null +++ b/ccsrc/Protocols/sctp/octet.cc @@ -0,0 +1,39 @@ +// +// 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 "octet.hh" +#include "common.hh" + +#include + +Json ToJson(const octet &v) +{ + return "0x" + utils::IntToHex((uint8_t)v); +} + +Json ToJson(const octet2 &v) +{ + return "0x" + utils::IntToHex((uint16_t)v); +} + +Json ToJson(const octet3 &v) +{ + std::stringstream stream; + stream << std::setfill('0') << std::setw(6) << std::hex << (uint32_t)v; + return "0x" + stream.str(); +} + +Json ToJson(const octet4 &v) +{ + return "0x" + utils::IntToHex((uint32_t)v); +} + +Json ToJson(const octet8 &v) +{ + return "0x" + utils::IntToHex((uint64_t)v); +} diff --git a/ccsrc/Protocols/sctp/octet.hh b/ccsrc/Protocols/sctp/octet.hh new file mode 100644 index 0000000000000000000000000000000000000000..910f2e0f14382116bafa98eb57b4bd909fc43588 --- /dev/null +++ b/ccsrc/Protocols/sctp/octet.hh @@ -0,0 +1,244 @@ +// +// 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 "json.hh" + +#include +#include +#include +#include + +struct octet +{ + private: + uint8_t value; + + public: + octet() noexcept : value(0) + { + } + + /* no explicit */ octet(int32_t value) noexcept : value(static_cast(value & 0xFF)) + { + } + + /* no explicit */ octet(uint32_t value) noexcept : value(static_cast(value & 0xFF)) + { + } + + /* no explicit */ constexpr operator uint8_t() const + { + return value; + } + + explicit constexpr operator int32_t() const + { + return static_cast(value); + } + + [[nodiscard]] inline bool bit(int index) const + { + assert(index >= 0 && index <= 7); + std::bitset<8> bitset = value; + return bitset[index]; + } +}; + +struct octet2 +{ + private: + uint16_t value; + + public: + octet2() noexcept : value(0) + { + } + + explicit octet2(int32_t value) noexcept : value(static_cast(value & 0xFFFF)) + { + } + + explicit octet2(uint32_t value) noexcept : value(static_cast(value & 0xFFFF)) + { + } + + octet2(uint8_t octet0, uint8_t octet1) noexcept + : value{static_cast((static_cast(octet0) << 8U) | (static_cast(octet1)))} + { + } + + inline uint8_t operator[](int index) const + { + assert(index >= 0 && index <= 1); + return (value >> (8 - index * 8)) & 0xFF; + } + + explicit constexpr operator uint32_t() const + { + return static_cast(value); + } + + explicit constexpr operator int32_t() const + { + return static_cast(value); + } + + explicit constexpr operator uint16_t() const + { + return value; + } +}; + +struct octet3 +{ + private: + uint32_t value; + + public: + octet3() noexcept : value(0) + { + } + + explicit octet3(int32_t value) noexcept : value(static_cast(value & 0xFFFFFF)) + { + } + + explicit octet3(uint32_t value) noexcept : value(value & 0xFFFFFF) + { + } + + octet3(uint8_t octet0, uint8_t octet1, uint8_t octet2) noexcept + : value{(static_cast(octet0) << 16U) | (static_cast(octet1) << 8U) | + (static_cast(octet2))} + { + } + + inline uint8_t operator[](int index) const + { + assert(index >= 0 && index <= 2); + return (value >> (16 - index * 8)) & 0xFF; + } + + explicit constexpr operator int32_t() const + { + return static_cast(value); + } + + explicit constexpr operator uint32_t() const + { + return value; + } +}; + +struct octet4 +{ + private: + uint32_t value; + + public: + octet4() noexcept : value(0) + { + } + + explicit octet4(int32_t value) noexcept : value(static_cast(value)) + { + } + + explicit octet4(uint32_t value) noexcept : value(value) + { + } + + octet4(uint8_t octet0, uint8_t octet1, uint8_t octet2, uint8_t octet3) noexcept + : value{(static_cast(octet0) << 24U) | (static_cast(octet1) << 16U) | + ((static_cast(octet2) << 8U)) | (static_cast(octet3))} + { + } + + inline uint8_t operator[](int index) const + { + assert(index >= 0 && index <= 3); + return (value >> (24 - index * 8)) & 0xFF; + } + + inline explicit constexpr operator int32_t() const + { + return static_cast(value); + } + + inline explicit constexpr operator uint32_t() const + { + return value; + } + + inline explicit constexpr operator int64_t() const + { + return static_cast(value); + } + + inline explicit constexpr operator uint64_t() const + { + return static_cast(value); + } + + inline bool operator==(const octet4 &other) const + { + return value == other.value; + } +}; + +struct octet8 +{ + private: + uint64_t value; + + public: + octet8() noexcept : value(0) + { + } + + explicit octet8(int64_t value) noexcept : value(static_cast(value)) + { + } + + explicit octet8(uint64_t value) noexcept : value(value) + { + } + + octet8(uint8_t octet0, uint8_t octet1, uint8_t octet2, uint8_t octet3, uint8_t octet4, uint8_t octet5, + uint8_t octet6, uint8_t octet7) noexcept + : value{(static_cast(octet0) << 56U) | (static_cast(octet1) << 48U) | + ((static_cast(octet2) << 40U)) | (static_cast(octet3) << 32U) | + (static_cast(octet4) << 24U) | (static_cast(octet5) << 16U) | + (static_cast(octet6) << 8U) | (static_cast(octet7))} + { + } + + inline uint8_t operator[](int index) const + { + assert(index >= 0 && index <= 7); + return (value >> (56 - index * 8)) & 0xFF; + } + + explicit constexpr operator int64_t() const + { + return static_cast(value); + } + + explicit constexpr operator uint64_t() const + { + return value; + } +}; + +Json ToJson(const octet &v); +Json ToJson(const octet2 &v); +Json ToJson(const octet3 &v); +Json ToJson(const octet4 &v); +Json ToJson(const octet8 &v); diff --git a/ccsrc/Protocols/sctp/octet_string.cc b/ccsrc/Protocols/sctp/octet_string.cc new file mode 100644 index 0000000000000000000000000000000000000000..786a5efde1f7d13e618837b6caee7f24ddb175a5 --- /dev/null +++ b/ccsrc/Protocols/sctp/octet_string.cc @@ -0,0 +1,330 @@ +// +// 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 "octet_string.hh" +#include "common.hh" + +#include + +void OctetString::append(const OctetString &v) +{ + m_data.insert(m_data.end(), v.m_data.begin(), v.m_data.end()); +} + +void OctetString::appendUtf8(const std::string &v) +{ + m_data.insert(m_data.end(), v.begin(), v.end()); +} + +void OctetString::appendOctet(uint8_t v) +{ + m_data.push_back(v); +} + +void OctetString::appendOctet(int v) +{ + m_data.push_back(v & 0xFF); +} + +void OctetString::appendOctet2(octet2 v) +{ + m_data.push_back(v[0]); + m_data.push_back(v[1]); +} + +void OctetString::appendOctet2(uint16_t v) +{ + appendOctet(static_cast(v >> 8 & 0xFF)); + appendOctet(static_cast(v & 0xFF)); +} + +void OctetString::appendOctet2(int v) +{ + appendOctet2(octet2{v}); +} + +void OctetString::appendOctet3(octet3 v) +{ + m_data.push_back(v[0]); + m_data.push_back(v[1]); + m_data.push_back(v[2]); +} + +void OctetString::appendOctet3(int v) +{ + appendOctet3(octet3{v}); +} + +void OctetString::appendOctet4(octet4 v) +{ + m_data.push_back(v[0]); + m_data.push_back(v[1]); + m_data.push_back(v[2]); + m_data.push_back(v[3]); +} + +void OctetString::appendOctet8(octet8 v) +{ + m_data.push_back(v[0]); + m_data.push_back(v[1]); + m_data.push_back(v[2]); + m_data.push_back(v[3]); + m_data.push_back(v[4]); + m_data.push_back(v[5]); + m_data.push_back(v[6]); + m_data.push_back(v[7]); +} + +void OctetString::appendOctet8(int64_t v) +{ + appendOctet8(octet8{v}); +} + +void OctetString::appendOctet8(uint64_t v) +{ + appendOctet8(octet8{v}); +} + +void OctetString::appendOctet4(int v) +{ + appendOctet4(octet4{v}); +} + +void OctetString::appendOctet4(uint32_t v) +{ + appendOctet4(octet4{v}); +} + +int OctetString::length() const +{ + return static_cast(m_data.size()); +} + +void OctetString::appendOctet(int bigHalf, int littleHalf) +{ + bigHalf &= 0xF; + littleHalf &= 0xF; + appendOctet(bigHalf << 4 | littleHalf); +} + +const uint8_t *OctetString::data() const +{ + return m_data.data(); +} + +uint8_t *OctetString::data() +{ + return m_data.data(); +} + +void OctetString::appendPadding(int length) +{ + for (int i = 0; i < length; i++) + appendOctet(0); +} + +OctetString OctetString::FromHex(const std::string &hex) +{ + return OctetString{utils::HexStringToVector(hex)}; +} + +std::string OctetString::toHexString() const +{ + return utils::VectorToHexString(m_data); +} + +OctetString OctetString::subCopy(int index) const +{ + return subCopy(index, length() - index); +} + +OctetString OctetString::subCopy(int index, int length) const +{ + return OctetString{std::vector{m_data.data() + index, m_data.data() + index + length}}; +} + +octet OctetString::get(int index) const +{ + return m_data[index]; +} + +octet2 OctetString::get2(int index) const +{ + return octet2{get(index), get(index + 1)}; +} + +octet3 OctetString::get3(int index) const +{ + return octet3{get(index), get(index + 1), get(index + 2)}; +} + +octet4 OctetString::get4(int index) const +{ + return octet4{get(index), get(index + 1), get(index + 2), get(index + 3)}; +} + +octet8 OctetString::get8(int index) const +{ + return octet8{get(index), get(index + 1), get(index + 2), get(index + 3), + get(index + 4), get(index + 5), get(index + 6), get(index + 7)}; +} + +int OctetString::getI(int index) const +{ + return get(index); +} + +int OctetString::get2I(int index) const +{ + return static_cast(get2(index)); +} + +int OctetString::get3I(int index) const +{ + return static_cast(get3(index)); +} + +int OctetString::get4I(int index) const +{ + return static_cast(get4(index)); +} + +uint32_t OctetString::get4UI(int index) const +{ + return static_cast(get4(index)); +} + +int64_t OctetString::get8L(int index) const +{ + return static_cast(get8(index)); +} + +uint64_t OctetString::get8UL(int index) const +{ + return static_cast(get8(index)); +} + +OctetString OctetString::Concat(const OctetString &a, const OctetString &b) +{ + std::vector v1 = a.m_data; + std::vector v2 = b.m_data; + std::vector v3; + + v3.reserve(v1.size() + v2.size()); + v3.insert(v3.end(), v1.begin(), v1.end()); + v3.insert(v3.end(), v2.begin(), v2.end()); + + return OctetString{std::move(v3)}; +} + +OctetString OctetString::FromOctet(uint8_t value) +{ + std::vector v(1); + v[0] = value; + return OctetString{std::move(v)}; +} + +OctetString OctetString::FromOctet(int value) +{ + return FromOctet(static_cast(value & 0xFF)); +} + +OctetString OctetString::FromOctet2(octet2 value) +{ + std::vector v(2); + v[0] = value[0]; + v[1] = value[1]; + return OctetString{std::move(v)}; +} + +OctetString OctetString::FromOctet2(int value) +{ + return FromOctet2(octet2{value}); +} + +OctetString OctetString::FromOctet4(octet4 value) +{ + std::vector v(4); + v[0] = value[0]; + v[1] = value[1]; + v[2] = value[2]; + v[3] = value[3]; + return OctetString{std::move(v)}; +} + +OctetString OctetString::FromOctet4(int value) +{ + return FromOctet4(octet4{value}); +} + +OctetString OctetString::FromOctet4(uint32_t value) +{ + return FromOctet4(octet4{value}); +} + +OctetString OctetString::FromOctet8(octet8 value) +{ + std::vector v(8); + v[0] = value[0]; + v[1] = value[1]; + v[2] = value[2]; + v[3] = value[3]; + v[4] = value[4]; + v[5] = value[5]; + v[6] = value[6]; + v[7] = value[7]; + return OctetString{std::move(v)}; +} + +OctetString OctetString::FromOctet8(int64_t value) +{ + return FromOctet8(octet8{value}); +} + +OctetString OctetString::FromOctet8(uint64_t value) +{ + return FromOctet8(octet8{value}); +} + +OctetString OctetString::copy() const +{ + return subCopy(0); +} + +OctetString OctetString::FromAscii(const std::string &ascii) +{ + return OctetString{std::vector{ascii.c_str(), ascii.c_str() + ascii.length()}}; +} + +OctetString OctetString::FromSpare(int length) +{ + return OctetString{std::vector(length)}; +} + +OctetString OctetString::Xor(const OctetString &a, const OctetString &b) +{ + const OctetString &min = a.length() < b.length() ? a : b; + const OctetString &other = a.length() < b.length() ? b : a; + + OctetString res = min.copy(); + for (int i = 0; i < res.length(); i++) + res.data()[i] ^= other.data()[i]; + return res; +} + +OctetString OctetString::Empty() +{ + return FromSpare(0); +} + +OctetString OctetString::FromArray(const uint8_t *arr, size_t len) +{ + std::vector v(len); + std::memcpy(v.data(), arr, len); + return OctetString(std::move(v)); +} diff --git a/ccsrc/Protocols/sctp/octet_string.hh b/ccsrc/Protocols/sctp/octet_string.hh new file mode 100644 index 0000000000000000000000000000000000000000..9a4567cb057497daf93c748f8250753e16efb06e --- /dev/null +++ b/ccsrc/Protocols/sctp/octet_string.hh @@ -0,0 +1,116 @@ +// +// 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 "octet.hh" + +#include +#include +#include +#include + +class OctetString +{ + private: + std::vector m_data; + + public: + OctetString() : m_data() + { + } + + explicit OctetString(std::vector &&data) : m_data(std::move(data)) + { + } + + OctetString(OctetString &&octetString) noexcept : m_data(std::move(octetString.m_data)) + { + } + + public: + void append(const OctetString &v); + void appendUtf8(const std::string &v); + void appendOctet(uint8_t v); + void appendOctet(int v); + void appendOctet(int bigHalf, int littleHalf); + void appendOctet2(octet2 v); + void appendOctet2(uint16_t v); + void appendOctet2(int v); + void appendOctet3(octet3 v); + void appendOctet3(int v); + void appendOctet4(octet4 v); + void appendOctet4(int v); + void appendOctet4(uint32_t v); + void appendOctet8(octet8 v); + void appendOctet8(int64_t v); + void appendOctet8(uint64_t v); + void appendPadding(int length); + + public: + [[nodiscard]] const uint8_t *data() const; + [[nodiscard]] int length() const; + uint8_t *data(); + + public: + [[nodiscard]] octet get(int index) const; + [[nodiscard]] octet2 get2(int index) const; + [[nodiscard]] octet3 get3(int index) const; + [[nodiscard]] octet4 get4(int index) const; + [[nodiscard]] octet8 get8(int index) const; + [[nodiscard]] int getI(int index) const; + [[nodiscard]] int get2I(int index) const; + [[nodiscard]] int get3I(int index) const; + [[nodiscard]] int get4I(int index) const; + [[nodiscard]] uint32_t get4UI(int index) const; + [[nodiscard]] int64_t get8L(int index) const; + [[nodiscard]] uint64_t get8UL(int index) const; + + public: + [[nodiscard]] std::string toHexString() const; + [[nodiscard]] OctetString copy() const; + [[nodiscard]] OctetString subCopy(int index) const; + [[nodiscard]] OctetString subCopy(int index, int length) const; + + public: + inline OctetString &operator=(OctetString &&other) noexcept + { + m_data = std::move(other.m_data); + return *this; + } + + inline bool operator==(const OctetString &other) + { + return m_data == other.m_data; + } + + inline bool operator!=(const OctetString &other) + { + return m_data != other.m_data; + } + + public: + static OctetString Empty(); + static OctetString FromHex(const std::string &hex); + static OctetString FromAscii(const std::string &ascii); + static OctetString FromArray(const uint8_t *arr, size_t len); + static OctetString FromSpare(int length); + static OctetString FromOctet(uint8_t value); + static OctetString FromOctet(int value); + static OctetString FromOctet2(octet2 value); + static OctetString FromOctet2(int value); + static OctetString FromOctet4(octet4 value); + static OctetString FromOctet4(int value); + static OctetString FromOctet4(uint32_t value); + static OctetString FromOctet8(octet8 value); + static OctetString FromOctet8(int64_t value); + static OctetString FromOctet8(uint64_t value); + + static OctetString Concat(const OctetString &a, const OctetString &b); + static OctetString Xor(const OctetString &a, const OctetString &b); +}; diff --git a/ccsrc/Protocols/sctp/readme.txt b/ccsrc/Protocols/sctp/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..90fd1a16cc6358d43f1da8e40bd6afc16dc82f85 --- /dev/null +++ b/ccsrc/Protocols/sctp/readme.txt @@ -0,0 +1,5 @@ +At project main Makefile need to use/add Linux system sctp library +example: +... +libs += -lsctp +... diff --git a/ccsrc/Protocols/sctp/scoped_thread.cc b/ccsrc/Protocols/sctp/scoped_thread.cc new file mode 100644 index 0000000000000000000000000000000000000000..70965c47120310764f4be70dd2a7b1f081dfac22 --- /dev/null +++ b/ccsrc/Protocols/sctp/scoped_thread.cc @@ -0,0 +1,71 @@ +// +// 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 "scoped_thread.hh" + +#include +#include + +#include + +struct Arguments +{ + void *routine; + void *arg; +}; + +static void *StartRoutine(void *pthreadArg) +{ + auto arguments = (Arguments *)pthreadArg; + + auto routine = (void (*)(void *))(arguments->routine); + auto arg = arguments->arg; + + delete arguments; + + if (routine != nullptr) + routine(arg); + + return nullptr; +} + +ScopedThread::ScopedThread(void (*routine)(void *), void *arg) +{ + auto arguments = new Arguments; + arguments->routine = (void *)routine; + arguments->arg = arg; + + pthread_t threadId; + + if (pthread_create(&threadId, nullptr, StartRoutine, arguments)) + { + delete arguments; + throw std::runtime_error("Thread could not be created."); + } + + m_threadId = threadId; +} + +ScopedThread::~ScopedThread() +{ + int r = pthread_cancel(m_threadId); + if (r != 0) + { + std::cerr << "Thread stopping failure, pthread_cancel failed." << std::endl; + std::terminate(); + } + + void *res = nullptr; + + r = pthread_join(m_threadId, &res); + if (r != 0) + { + std::cerr << "Thread stopping failure, pthread_join failed." << std::endl; + std::terminate(); + } +} diff --git a/ccsrc/Protocols/sctp/scoped_thread.hh b/ccsrc/Protocols/sctp/scoped_thread.hh new file mode 100644 index 0000000000000000000000000000000000000000..b4b7f1c8c9a88687ffc49fdb60444994a7bd1662 --- /dev/null +++ b/ccsrc/Protocols/sctp/scoped_thread.hh @@ -0,0 +1,21 @@ +// +// 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 + +class ScopedThread +{ + private: + pthread_t m_threadId; + + public: + ScopedThread(void (*routine)(void *), void *arg); + ~ScopedThread(); +}; diff --git a/ccsrc/Protocols/sctp/sctp.cc b/ccsrc/Protocols/sctp/sctp.cc new file mode 100644 index 0000000000000000000000000000000000000000..56abeda59c7bfab4d2c9a73577ba546c58a84959 --- /dev/null +++ b/ccsrc/Protocols/sctp/sctp.cc @@ -0,0 +1,14 @@ +// +// 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 "sctp.hh" + +//sctp::PayloadProtocolId sctp::convert(const std::string& str) +//{ +// if(str == "60") return NGAP; +//} diff --git a/ccsrc/Protocols/sctp/sctp.hh b/ccsrc/Protocols/sctp/sctp.hh new file mode 100644 index 0000000000000000000000000000000000000000..d7feaacc6d55907ce1b7e6bf385dcfdb32419e2d --- /dev/null +++ b/ccsrc/Protocols/sctp/sctp.hh @@ -0,0 +1,16 @@ +// +// 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 "client.hh" +#include "server.hh" +#include "types.hh" + +//sctp::PayloadProtocolId sctp::convert(const std::string& str) + diff --git a/ccsrc/Protocols/sctp/sctp_layer.cc b/ccsrc/Protocols/sctp/sctp_layer.cc new file mode 100644 index 0000000000000000000000000000000000000000..1b1ce362bd8f6fe29711bc080111bddd49b551a9 --- /dev/null +++ b/ccsrc/Protocols/sctp/sctp_layer.cc @@ -0,0 +1,338 @@ +/*! + * \file sctp_layer.cc + * \brief CC file for SCTP socket based protocol port layer. + * \author ETSI TTF041 + * \copyright ETSI Copyright Notification + * No part may be reproduced except as authorized by written permission. + * The copyright and the foregoing restriction extend to reproduction in all media. + * All rights reserved. + * \version 0.1 + */ + +#include +#include +#include +#include +#include + +#include "sctp_layer_factory.hh" +#include "sctp.hh" +#include "loggers.hh" +#include "scoped_thread.hh" + +sctp::SctpClient *client; +ScopedThread *receiverThread; + +sctp_layer::sctp_layer(const std::string& p_type, const std::string& param) : layer(p_type), SSL_Socket(), PORT(p_type.c_str()), _params(), _client_id{-1}, _time_key("sctp_layer::Handle_Fd_Event_Readable"), _reconnect_on_send{false} { + loggers::get_instance().log(">>> sctp_layer::sctp_layer (1): '%s', %s", to_string().c_str(), param.c_str()); + // Setup parameters + params::convert(_params, param); + _params.log(); + + init(); } + +sctp_layer::sctp_layer(const std::string& p_type, const params & param) : layer(p_type), SSL_Socket(), PORT(p_type.c_str()), _params(), _client_id{-1}, _time_key("sctp_layer::Handle_Fd_Event_Readable"), _reconnect_on_send{false} { + loggers::get_instance().log(">>> sctp_layer::sctp_layer (2): '%s'", to_string().c_str()); + // Setup parameters + _params = param; + + init(); +} + +void sctp_layer::init() { + loggers::get_instance().log(">>> sctp_layer::init"); + + set_socket_debugging(false); + params::const_iterator it = _params.find(params::debug); + if (it == _params.cend()) { + _params.insert(std::pair(std::string("debug"), "0")); + } else if (it->second.compare("1") == 0) { + set_socket_debugging(true); + } + /*it = _params.find(std::string("sctp_fragmented")); + if (it == _params.cend()) { + _params.insert(std::pair(std::string("sctp_fragmented"), "0")); + } else { + _params.insert(std::pair(std::string("sctp_fragmented"), "1")); + }*/ + + it = _params.find("src_ip"); + if (it == _params.cend()) { + _params.insert(std::pair(std::string("src_ip"), "127.0.0.1")); + } + it = _params.find("src_port"); + if (it == _params.cend()) { + _params.insert(std::pair(std::string("src_port"), "0")); // Dynamic binding requested + } + + bool server_mode = false; + it = _params.find(params::server_mode); + if (it != _params.cend()) { + server_mode = (1 == std::stoi(it->second)); + } else { + _params.insert(std::pair(std::string("server_mode"), "0")); + } + it = _params.find(params::server); + if (it == _params.cend()) { + _params.insert(std::pair(std::string("server"), "127.0.0.1")); // TODO Try using params::server instead of std::string("server") + } + if (!parameter_set(params::server.c_str(), _params[params::server].c_str())) { + loggers::get_instance().warning("sctp_layer::set_parameter: Unprocessed parameter: '%s'", params::server.c_str()); + } + bool ssl_mode = false; + /* = _params.find(params::use_ssl); + if (it == _params.cend()) { + _params.insert(std::pair(std::string("use_ssl"), "0")); + } else if (it->second.compare("1") == 0) { + _params.insert(std::pair(std::string("use_ssl"), "1")); + ssl_mode = true; + } + set_ssl_use_ssl(ssl_mode);*/ + it = _params.find(params::port); + if (it == _params.cend()) { + if (_params[params::use_ssl].compare("0") == 0) { // Use standard HTTP port + _params.insert(std::pair(std::string("port"), "80")); + } else { // Use standard HTTPS port + _params.insert(std::pair(std::string("port"), "443")); + } + } + if (!parameter_set(remote_port_name(), _params[params::port].c_str())) { + loggers::get_instance().warning("sctp_layer::set_parameter: Unprocessed parameter: '%s'", params::port.c_str()); + } + it = _params.find(params::local_port); + if (it == _params.cend()) { + if (_params[params::use_ssl].compare("0") == 0) { // Use standard HTTP local_port + _params.insert(std::pair(std::string("local_port"), "80")); + } else { // Use standard HTTPS local_port + _params.insert(std::pair(std::string("local_port"), "443")); + } + } + if (!parameter_set(local_port_name(), _params[params::local_port].c_str())) { + loggers::get_instance().warning("sctp_layer::set_parameter: Unprocessed parameter: '%s'", params::local_port.c_str()); + } + + if (!parameter_set(local_port_name(), _params[params::local_port].c_str())) { + loggers::get_instance().warning("sctp_layer::set_parameter: Unprocessed parameter: '%s'", params::local_port.c_str()); + } + + it = _params.find("ppid"); + if (it == _params.cend()) { + _params.insert(std::pair(std::string("ppid"), "0")); // Dynamic binding requested + } + + + parameter_set(use_connection_ASPs_name(), (!server_mode) ? "yes" : "no"); + loggers::get_instance().warning("sctp_layer::set_parameter: Limit to one simultanneous accepted connection (server_backlog == 1)"); + parameter_set(server_backlog_name(), "1"); // Limit to one simultanneous accepted connection + loggers::get_instance().log("sctp_layer::init: server_mode=%x", server_mode); + set_server_mode(server_mode); + if (server_mode) { + parameter_set("serverPort", _params[params::local_port].c_str()); + } + /* if (ssl_mode) { // Add certificate bundle + // Check mutual authentication param + _params.insert(std::pair(std::string("mutual_tls"), "0")); + parameter_set(ssl_verifycertificate_name(), "no"); + it = _params.find(params::mutual_auth); + if (it != _params.cend()) { + if (_params[params::mutual_auth].compare("1") == 0) { // Use mutual authentication + parameter_set(ssl_verifycertificate_name(), "yes"); + _params.insert(std::pair(std::string("mutual_tls"), "1")); + } + } + // Set trusted CA file + it = _params.find(params::trusted_ca_list); + if (it != _params.cend()) { + parameter_set(ssl_trustedCAlist_file_name(), it->second.c_str()); + } else { + // Use Let's Encrypt to generate your certificates + // https://manpages.ubuntu.com/manpages/impish/en/man1/certbot.1.html + loggers::get_instance().error("sctp_layer::set_parameter: Trusted CA list is required for TLS"); + } + // Set additional certificates + it = _params.find(params::privkey); + if (it != _params.cend()) { + parameter_set(ssl_private_key_file_name(), it->second.c_str()); + } else { + // Use Let's Encrypt to generate your certificates + // https://manpages.ubuntu.com/manpages/impish/en/man1/certbot.1.html + loggers::get_instance().error("sctp_layer::set_parameter: Certificate private key is required for TLS"); + } + it = _params.find(params::certificate); + if (it != _params.cend()) { + parameter_set(ssl_certificate_file_name(), it->second.c_str()); + } else { + // Use Let's Encrypt to generate your certificates + // https://manpages.ubuntu.com/manpages/impish/en/man1/certbot.1.html + loggers::get_instance().error("sctp_layer::set_parameter: Certificate is required for TLS"); + } + }*/ + set_ttcn_buffer_usercontrol(false); + set_handle_half_close(true); + + map_user(); + + //parameter_set(client_SCTP_reconnect_name(), "yes"); + + if (server_mode == 0) { + loggers::get_instance().log("sctp_layer::init: Establish connection: '%s'/%s", _params[params::server].c_str(), _params[params::port].c_str()); + //open_client_connection(_params[params::server].c_str(), _params[params::port].c_str(), NULL, NULL); + //auto *client = new sctp::SctpClient(sctp::convert(_params["ppid"].c_str)) ; + /*auto*/ client = new sctp::SctpClient(sctp::PayloadProtocolId::NGAP,this) ; + client->bind(_params["src_ip"].c_str(), std::stoul(_params["src_port"].c_str())); + client->connect( _params[params::server].c_str(), std::stoul(_params[params::port].c_str(),nullptr,0)); + + receiverThread = new ScopedThread( [](void *arg) { sctp::SctpClient::ReceiverThread(reinterpret_cast(arg)); }, (client)); + //receiverThread = new ScopedThread( [](void *arg) { sctp::SctpClient::ReceiverThread(reinterpret_cast *>(arg)); }, new std::pair(client,this)); + + } +} + +sctp_layer::~sctp_layer() { + loggers::get_instance().log(">>> sctp_layer::~sctp_layer: %d", _client_id); + if (_client_id != -1) { + remove_client(_client_id); + } + + unmap_user(); +} + +void sctp_layer::Handle_Fd_Event(int fd, boolean is_readable, boolean is_writable, boolean is_error) +{ + loggers::get_instance().log(">>> sctp_layer::Handle_Fd_Event: %d - _client_id: %d", fd, _client_id); + Handle_Socket_Event(fd, is_readable, is_writable, is_error); + log_debug("<<< sctp_layer::Handle_Fd_Event"); +} + +void sctp_layer::Handle_Timeout(double time_since_last_call) +{ + loggers::get_instance().log(">>> sctp_layer::Handle_Timeout: %f", time_since_last_call); + Handle_Timeout_Event(time_since_last_call); + loggers::get_instance().log("<<< sctp_layer::Handle_Timeout"); +} + + +////////////void sctp_layer::sendMsg(const NGAP__PDU__Descriptions::NGAP__PDU& p, params &p_params) { + //loggers::get_instance().log(">>> sctp_layer::sendMsg"); + // params.log(); + + // Encode PDU +///////////// OCTETSTRING data = int2oct(0,1); + //_codec.encode(p.msgOut(), data); +//////////// send_data(data, p_params); +//////////////} + + +void sctp_layer::send_data(OCTETSTRING& data, params& params) { + //loggers::get_instance().log_msg(">>> sctp_layer::send_data: ", data); + + //loggers::get_instance().log("sctp_layer::send_data: SSL mode: %x", get_ssl_use_ssl()); + //loggers::get_instance().log("sctp_layer::send_data: server_mode: '%s'", _params[params::server_mode].c_str()); + //loggers::get_instance().log("sctp_layer::send_data: peer_list_get_nr_of_peers: %d", peer_list_get_nr_of_peers()); + + /*if ((_params[params::server_mode].compare("0") == 0) && (peer_list_get_nr_of_peers() == 0)) { + // Reconnect (e.g. HTTP connection lost + loggers::get_instance().log("sctp_layer::send_data: Re-establish connection: '%s'/%s", _params[params::server].c_str(), _params[params::port].c_str()); + open_client_connection(_params[params::server].c_str(), _params[params::port].c_str(), NULL, NULL); + }*/ + //send_outgoing(static_cast(data), data.lengthof(), _client_id); + + //client->send( static_cast(data),data.lengthof()); + client->send( static_cast(data),data.lengthof()); + +} + +void sctp_layer::receive_data(OCTETSTRING& data, params& params) { + //loggers::get_instance().log_msg(">>> sctp_layer::receive_data: ", data); + + //NGAP__PDU__Descriptions::NGAP__PDU ngap_pdu; + //incomming_message(ngap_pdu); + receive_to_all_layers(data, params); + //loggers::get_instance().log(">>> sctp_layer::receive_data:to_all_layers... "); +} + +void sctp_layer::message_incoming(const unsigned char* message_buffer, int length, int client_id) { + //loggers::get_instance().log(">>> sctp_layer::message_incoming"); + //loggers::get_instance().log_to_hexa("sctp_layer::message_incoming: ", message_buffer, length); + + //float duration; + //loggers::get_instance().set_start_time(_time_key); + OCTETSTRING data(length, message_buffer); + params params; + params.insert(std::pair( + std::string("timestamp"), + std::to_string(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()))); + this->receive_data(data, params); // TODO Check execution time for decoding operation + //loggers::get_instance().set_stop_time(_time_key, duration); +} + +void sctp_layer::client_connection_opened(int p_client_id) +{ + loggers::get_instance().log(">>> sctp_layer::client_connection_opened - _client_id: %d: %d", p_client_id, _client_id); +} + +bool sctp_layer::add_user_data(int p_client_id) +{ + loggers::get_instance().log(">>> sctp_layer::add_user_data: %d - _client_id: %d", p_client_id, _client_id); + _client_id = p_client_id; + if (_params[params::use_ssl].compare("0") == 0) { + loggers::get_instance().log("sctp_layer::add_user_data: Non secured mode"); + return Abstract_Socket::add_user_data(p_client_id); + } + loggers::get_instance().log("sctp_layer::add_user_data: SSL mode"); + return SSL_Socket::add_user_data(p_client_id); +} + +int sctp_layer::send_message_on_fd(int p_client_id, const unsigned char * message_buffer, int length_of_message) +{ + loggers::get_instance().log(">>> sctp_layer::send_message_on_fd: %d", p_client_id); + + if(get_user_data(p_client_id)) { + loggers::get_instance().log("sctp_layer::send_message_on_fd: SSL mode"); + return SSL_Socket::send_message_on_fd(p_client_id, message_buffer, length_of_message); + } + + loggers::get_instance().log("sctp_layer::send_message_on_fd: Non secured mode"); + return Abstract_Socket::send_message_on_fd(p_client_id, message_buffer, length_of_message); +} + +int sctp_layer::send_message_on_nonblocking_fd(int p_client_id, const unsigned char * message_buffer, int length_of_message) +{ + loggers::get_instance().log(">>> sctp_layer::send_message_on_nonblocking_fd: %d", p_client_id); + + if(get_user_data(p_client_id)) { + loggers::get_instance().log("sctp_layer::send_message_on_nonblocking_fd: SSL mode"); + return SSL_Socket::send_message_on_nonblocking_fd(p_client_id, message_buffer, length_of_message); + } + + loggers::get_instance().log("sctp_layer::send_message_on_nonblocking_fd: Non secured mode"); + return Abstract_Socket::send_message_on_nonblocking_fd(p_client_id, message_buffer, length_of_message); +} + +int sctp_layer::receive_message_on_fd(int p_client_id) +{ + loggers::get_instance().log(">>> sctp_layer::receive_message_on_fd: %d", p_client_id); + + if(get_user_data(p_client_id)) { + // INFO: it is assumed that only SSL_Socket assigns user data to each peer + loggers::get_instance().log("sctp_layer::receive_message_on_fd: SSL mode"); + return SSL_Socket::receive_message_on_fd(p_client_id); + } + + loggers::get_instance().log("sctp_layer::receive_message_on_fd: Non secured mode"); + if (_params[std::string("sctp_fragmented")].compare("1") == 0) { + sleep(4); // FIXME When HTTP paquet is fragmented into several SCTP packets, a timer is required. This is a Q&D solution + } + return Abstract_Socket::receive_message_on_fd(p_client_id); +} + +void sctp_layer::peer_disconnected(int p_client_id) +{ + loggers::get_instance().log(">>> sctp_layer::peer_disconnected: %d", p_client_id); + + Abstract_Socket::peer_disconnected(p_client_id); + _client_id = -1; +} + +sctp_layer_factory sctp_layer_factory::_f; + diff --git a/ccsrc/Protocols/sctp/sctp_layer.hh b/ccsrc/Protocols/sctp/sctp_layer.hh new file mode 100644 index 0000000000000000000000000000000000000000..6fa44385e59b4f7de8780592f16aaa4c33eaad29 --- /dev/null +++ b/ccsrc/Protocols/sctp/sctp_layer.hh @@ -0,0 +1,113 @@ +/*! + * \file sctp_layer.hh + * \brief Header file for SCTP socket based protocol port layer. + * \author ETSI TTF041 + * \copyright ETSI Copyright Notification + * No part may be reproduced except as authorized by written permission. + * The copyright and the foregoing restriction extend to reproduction in all media. + * All rights reserved. + * \version 0.1 + */ +#pragma once + +#include "layer.hh" + +using namespace std; // Required for isnan() +#include "Abstract_Socket.hh" + + +class PORT; //! Forward declaration of TITAN class + +/*! + * \class sctp_layer + * \brief This class provides description of ITS SCTP port protocol layer + */ +class sctp_layer : public layer, public SSL_Socket, public PORT { + params _params; //! Layer parameters + int _client_id; //! Connection identifier + std::string _time_key; //! \todo + bool _reconnect_on_send; //! Set to true if connection shall be done when sending data. Otherwise, connection is established by the \see constructor + +public: //! \publicsection + //sctp_layer(): PORT("SCTP") {}; + /*! + * \brief Specialised constructor + * Create a new instance of the sctp_layer class + * \param[in] p_type \todo + * \param[in] p_param \todo + */ + sctp_layer(const std::string& p_type, const std::string& p_param); + /*! + * \brief Specialised constructor + * Create a new instance of the sctp_layer class + * \param[in] p_type \todo + * \param[in] p_param \todo + */ + sctp_layer(const std::string& p_type, const params &p_param); + /*! + * \brief Default destructor + * \remark If \see _reconnect_on_send is set to false, the disconnection is done by the destructor + */ + virtual ~sctp_layer(); + + + //////////virtual void sendMsg(const NGAP__PDU__Descriptions::NGAP__PDU& p, params &p_params); + + + /*! + * \virtual + * \fn void send_data(OCTETSTRING& data, params& params); + * \brief Send bytes formated data to the lower layers + * \param[in] p_data The data to be sent + * \param[in] p_params Some parameters to overwrite default value of the lower layers parameters + * \virtual + */ + virtual void send_data(OCTETSTRING &data, params ¶ms); + /*! + * \virtual + * \fn void receive_data(OCTETSTRING& data, params& params); + * \brief Receive bytes formated data from the lower layers + * \param[in] p_data The bytes formated data received + * \param[in] p_params Some lower layers parameters values when data was received + */ + virtual void receive_data(OCTETSTRING &data, params &info); + + /*! + * \virtual + * \fn void message_incoming(const unsigned char* message_buffer, int length, int client_id = -1); + * \brief Receive bytes formated data from the lower layers + * \param[in] p_buffer The bytes formated data received + * \param[in] p_length The number of bytes received + * \param[in] p_client_id The connection identifier.Default: -1 + */ + virtual void message_incoming(const unsigned char *p_buffer, int p_length, int p_client_id = -1); + +protected: //! \protectedsection + void init(); + + void Add_Fd_Read_Handler(int fd) { Handler_Add_Fd_Read(fd); }; + void Add_Fd_Write_Handler(int fd) { Handler_Add_Fd_Write(fd); }; + void Remove_Fd_Read_Handler(int fd) { Handler_Remove_Fd_Read(fd); }; + void Remove_Fd_Write_Handler(int fd) { Handler_Remove_Fd_Write(fd); }; + void Remove_Fd_All_Handlers(int fd) { Handler_Remove_Fd(fd); }; + void Handler_Uninstall() { Uninstall_Handler(); } + void Timer_Set_Handler(double call_interval, boolean is_timeout = TRUE, boolean call_anyway = TRUE, boolean is_periodic = TRUE) { + Handler_Set_Timer(call_interval, is_timeout, call_anyway, is_periodic); + }; + + const char *remote_address_name() { return params::server.c_str(); }; + const char *remote_port_name() { return params::port.c_str(); }; + const char *socket_debugging_name() { return params::debug.c_str(); }; + const char *ssl_use_ssl_name() { return params::use_ssl.c_str(); }; + + void client_connection_opened(int p_client_id); + bool add_user_data(int p_client_id); + int send_message_on_fd(int p_client_id, const unsigned char *message_buffer, int length_of_message); + int send_message_on_nonblocking_fd(int client_id, const unsigned char *message_buffer, int length_of_message); + int receive_message_on_fd(int p_client_id); + void peer_disconnected(int p_client_id); + +private: //! \privatesection + void Handle_Fd_Event(int fd, boolean is_readable, boolean is_writable, boolean is_error); + void Handle_Timeout(double time_since_last_call); +}; // End of class sctp_layer diff --git a/ccsrc/Protocols/sctp/sctp_layer_factory.hh b/ccsrc/Protocols/sctp/sctp_layer_factory.hh new file mode 100644 index 0000000000000000000000000000000000000000..1f85764280eec5f3b3f135c067ad02c8a44c1610 --- /dev/null +++ b/ccsrc/Protocols/sctp/sctp_layer_factory.hh @@ -0,0 +1,41 @@ +/*! + * \file sctp_layer_factory.hh + * \brief Header file for SCTP socket based protocol layer factory. + * \author ETSI TTF041 + * \copyright ETSI Copyright Notification + * No part may be reproduced except as authorized by written permission. + * The copyright and the foregoing restriction extend to reproduction in all media. + * All rights reserved. + * \version 0.1 + */ +#pragma once + +#include "layer_stack_builder.hh" + +#include "sctp_layer.hh" + +/*! + * \class sctp_layer_factory + * \brief This class provides a factory class to create an sctp_layer class instance + */ +class sctp_layer_factory : public layer_factory { + static sctp_layer_factory _f; //! Reference to the unique instance of this class +public: //! \publicsection + /*! + * \brief Default constructor + * Create a new instance of the sctp_layer class + * \remark The SCTP socket based layer identifier is SCTP + */ + sctp_layer_factory() { + // Register factory + layer_stack_builder::register_layer_factory("SCTP", this); + }; + /*! + * \fn layer* create_layer(const std::string& type, const std::string& param); + * \brief Create the layers stack based on the provided layers stack description + * \param[in] p_type The provided layers stack description + * \param[in] p_params Optional parameters + * \return 0 on success, -1 otherwise + */ + inline virtual layer *create_layer(const std::string& p_type, const std::string& p_param) { return new sctp_layer(p_type, p_param); }; +}; // End of class sctp_layer_factory diff --git a/ccsrc/Protocols/sctp/server.cc b/ccsrc/Protocols/sctp/server.cc new file mode 100644 index 0000000000000000000000000000000000000000..ad2a3f0a646e8547c1d025b0de3f9eef5a2869b8 --- /dev/null +++ b/ccsrc/Protocols/sctp/server.cc @@ -0,0 +1,37 @@ +// +// 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 "server.hh" +#include "internal.hh" + +sctp::SctpServer::SctpServer(const std::string &address, uint16_t port) : sd(0) +{ + try + { + sd = CreateSocket(); + BindSocket(sd, address, port); + SetInitOptions(sd, 10, 10, 10, 10 * 1000); + SetEventOptions(sd); + StartListening(sd); + } + catch (const SctpError &e) + { + CloseSocket(sd); + throw; + } +} + +sctp::SctpServer::~SctpServer() +{ + CloseSocket(sd); +} + +void sctp::SctpServer::start() +{ + Accept(sd); +} diff --git a/ccsrc/Protocols/sctp/server.hh b/ccsrc/Protocols/sctp/server.hh new file mode 100644 index 0000000000000000000000000000000000000000..edf60edd6af77090c318324df27ffc5fc25864f8 --- /dev/null +++ b/ccsrc/Protocols/sctp/server.hh @@ -0,0 +1,30 @@ +// +// 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 + +namespace sctp +{ + +class SctpServer +{ + private: + int sd; + + public: + SctpServer(const std::string &address, uint16_t port); + ~SctpServer(); + + void start(); + + // TODO: Other functionalities +}; + +} // namespace sctp \ No newline at end of file diff --git a/ccsrc/Protocols/sctp/task.cpp b/ccsrc/Protocols/sctp/task.cpp new file mode 100644 index 0000000000000000000000000000000000000000..263c6a45e63228f9bbf881589b9457b58e0e8a2f --- /dev/null +++ b/ccsrc/Protocols/sctp/task.cpp @@ -0,0 +1,335 @@ +// +// 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 "task.hpp" + +#include +#include +#include + +// #define MOCKED_PACKETS + +#ifdef MOCKED_PACKETS +static std::string MOCK_LIST[] = { + std::string( + "201500320000040001000e05806f70656e3567732d616d663000600008000009f10702004000564001ff005000080009f10700000008"), + + std::string("0004403e000003000a000200060055000200010026002b2a7e005600020000215660aed893ca993801ad60ba50575cb020101c" + "016820d4f980" + "004ff8987b0bfcbbb8"), + + std::string("00044029000003000a0002000600550002000100260016157e03cbc05c7b007e005d020004f0f0f0f0e1360102"), + + std::string("000e00809e000009000a00020006005500020001006e000a0c3e800000303e800000001c00070009f107020040000000020001" + "007700091c00" + "0e000700038000005e002091f9908eefe559d08b9f958b4d7c1a94094918dedcf40bb57c55428a00d4dee90022400835693803" + "56fffff00026" + "402f2e7e02687a0bd3017e0042010177000bf209f107020040e700acbe54072009f10700000115020101210201005e0129"), + + std::string("0004403c000003000a0002000600550002000100260029287e02f41c3d78027e0054430f10004f00700065006e003500470053" + "462147121062" + "41547021490100"), +}; +static int MOCK_INDEX = -1; +#endif + +namespace nr::gnb +{ + +class SctpHandler : public sctp::ISctpHandler +{ + private: + SctpTask *const sctpTask; + int clientId; + + public: + SctpHandler(SctpTask *const sctpTask, int clientId) : sctpTask(sctpTask), clientId(clientId) + { + } + + private: + void onAssociationSetup(int associationId, int inStreams, int outStreams) override + { + auto w = std::make_unique(NmGnbSctp::ASSOCIATION_SETUP); + w->clientId = clientId; + w->associationId = associationId; + w->inStreams = inStreams; + w->outStreams = outStreams; + sctpTask->push(std::move(w)); + } + + void onAssociationShutdown() override + { + auto w = std::make_unique(NmGnbSctp::ASSOCIATION_SHUTDOWN); + w->clientId = clientId; + sctpTask->push(std::move(w)); + } + + void onMessage(const uint8_t *buffer, size_t length, uint16_t stream) override + { + auto *data = new uint8_t[length]; + std::memcpy(data, buffer, length); + + auto w = std::make_unique(NmGnbSctp::RECEIVE_MESSAGE); + w->clientId = clientId; + w->buffer = UniqueBuffer{data, length}; + w->stream = stream; + sctpTask->push(std::move(w)); + } + + void onUnhandledNotification() override + { + auto w = std::make_unique(NmGnbSctp::UNHANDLED_NOTIFICATION); + w->clientId = clientId; + sctpTask->push(std::move(w)); + } + + void onConnectionReset() override + { + auto w = std::make_unique(NmGnbSctp::UNHANDLED_NOTIFICATION); + w->clientId = clientId; + sctpTask->push(std::move(w)); + } +}; + +[[noreturn]] static void ReceiverThread(std::pair *args) +{ + sctp::SctpClient *client = args->first; + sctp::ISctpHandler *handler = args->second; + + delete args; + + while (true) + client->receive(handler); +} + +SctpTask::SctpTask(TaskBase *base) : m_base{base}, m_clients{} +{ + m_logger = base->logBase->makeUniqueLogger("sctp"); +} + +void SctpTask::onStart() +{ +} + +void SctpTask::onLoop() +{ + auto msg = take(); + if (!msg) + return; + + switch (msg->msgType) + { + case NtsMessageType::GNB_SCTP: { + auto& w = dynamic_cast(*msg); + switch (w.present) + { + case NmGnbSctp::CONNECTION_REQUEST: { + receiveSctpConnectionSetupRequest(w.clientId, w.localAddress, w.localPort, w.remoteAddress, + w.remotePort, w.ppid, w.associatedTask); + break; + } + case NmGnbSctp::CONNECTION_CLOSE: { + receiveConnectionClose(w.clientId); + break; + } + case NmGnbSctp::ASSOCIATION_SETUP: { + receiveAssociationSetup(w.clientId, w.associationId, w.inStreams, w.outStreams); + break; + } + case NmGnbSctp::ASSOCIATION_SHUTDOWN: { + receiveAssociationShutdown(w.clientId); + break; + } + case NmGnbSctp::RECEIVE_MESSAGE: { + receiveClientReceive(w.clientId, w.stream, std::move(w.buffer)); + break; + } + case NmGnbSctp::SEND_MESSAGE: { + receiveSendMessage(w.clientId, w.stream, std::move(w.buffer)); + break; + } + case NmGnbSctp::UNHANDLED_NOTIFICATION: { + receiveUnhandledNotification(w.clientId); + break; + } + default: + m_logger->unhandledNts(*msg); + break; + } + break; + } + default: + m_logger->unhandledNts(*msg); + break; + } +} + +void SctpTask::onQuit() +{ + for (auto &client : m_clients) + { + ClientEntry *entry = client.second; + DeleteClientEntry(entry); + } + m_clients.clear(); +} + +void SctpTask::DeleteClientEntry(ClientEntry *entry) +{ + entry->associatedTask = nullptr; + delete entry->receiverThread; + delete entry->client; + delete entry->handler; + delete entry; +} + +void SctpTask::receiveSctpConnectionSetupRequest(int clientId, const std::string &localAddress, uint16_t localPort, + const std::string &remoteAddress, uint16_t remotePort, + sctp::PayloadProtocolId ppid, NtsTask *associatedTask) +{ + m_logger->info("Trying to establish SCTP connection... (%s:%d)", remoteAddress.c_str(), remotePort); + + auto *client = new sctp::SctpClient(ppid); + + try + { + client->bind(localAddress, localPort); + } + catch (const sctp::SctpError &exc) + { + m_logger->err("Binding to %s:%d failed. %s", localAddress.c_str(), localPort, exc.what()); + delete client; + return; + } + + try + { + client->connect(remoteAddress, remotePort); + } + catch (const sctp::SctpError &exc) + { + m_logger->err("Connecting to %s:%d failed. %s", remoteAddress.c_str(), remotePort, exc.what()); + delete client; + return; + } + + m_logger->info("SCTP connection established (%s:%d)", remoteAddress.c_str(), remotePort); + + sctp::ISctpHandler *handler = new SctpHandler(this, clientId); + + auto *entry = new ClientEntry; + m_clients[clientId] = entry; + + entry->id = clientId; + entry->client = client; + entry->handler = handler; + entry->associatedTask = associatedTask; + entry->receiverThread = new ScopedThread( + [](void *arg) { ReceiverThread(reinterpret_cast *>(arg)); }, + new std::pair(client, handler)); +} + +void SctpTask::receiveAssociationSetup(int clientId, int associationId, int inStreams, int outStreams) +{ + m_logger->debug("SCTP association setup ascId[%d]", associationId); + + ClientEntry *entry = m_clients[clientId]; + if (entry == nullptr) + { + m_logger->warn("Client entry not found for id: %d", clientId); + return; + } + + // Notify the relevant task + auto msg = std::make_unique(NmGnbSctp::ASSOCIATION_SETUP); + msg->clientId = clientId; + msg->associationId = associationId; + msg->inStreams = inStreams; + msg->outStreams = outStreams; + entry->associatedTask->push(std::move(msg)); +} + +void SctpTask::receiveAssociationShutdown(int clientId) +{ + m_logger->debug("SCTP association shutdown (clientId: %d)", clientId); + + ClientEntry *entry = m_clients[clientId]; + if (entry == nullptr) + { + m_logger->warn("Client entry not found for id: %d", clientId); + return; + } + + // Notify the relevant task + auto msg = std::make_unique(NmGnbSctp::ASSOCIATION_SHUTDOWN); + msg->clientId = clientId; + entry->associatedTask->push(std::move(msg)); +} + +void SctpTask::receiveClientReceive(int clientId, uint16_t stream, UniqueBuffer &&buffer) +{ + ClientEntry *entry = m_clients[clientId]; + if (entry == nullptr) + { + m_logger->warn("Client entry not found for id: %d", clientId); + return; + } + + // Notify the relevant task + auto msg = std::make_unique(NmGnbSctp::RECEIVE_MESSAGE); + msg->clientId = clientId; + msg->stream = stream; + msg->buffer = std::move(buffer); + entry->associatedTask->push(std::move(msg)); +} + +void SctpTask::receiveUnhandledNotification(int clientId) +{ + // NOTE: For unhandled notifications, "clientId" may be invalid for some notifications. + // Because some notification may be received after shutdown. + + // Print warning + m_logger->warn("Unhandled SCTP notification received"); +} + +void SctpTask::receiveConnectionClose(int clientId) +{ + ClientEntry *entry = m_clients[clientId]; + if (entry == nullptr) + { + m_logger->warn("Client entry not found for id: %d", clientId); + return; + } + + DeleteClientEntry(entry); +} + +void SctpTask::receiveSendMessage(int clientId, uint16_t stream, UniqueBuffer &&buffer) +{ + ClientEntry *entry = m_clients[clientId]; + if (entry == nullptr) + { + m_logger->warn("Client entry not found for id: %d", clientId); + return; + } + +#ifdef MOCKED_PACKETS + { + std::string ss = MOCK_LIST[++MOCK_INDEX]; + OctetString data = OctetString::FromHex(ss); + auto *copy = new uint8_t[data.length()]; + std::memcpy(copy, data.data(), data.length()); + receiveClientReceive(clientId, 0, copy, data.length()); + } +#else + entry->client->send(stream, buffer.data(), 0, buffer.size()); +#endif +} + +} // namespace nr::gnb diff --git a/ccsrc/Protocols/sctp/task.hpp b/ccsrc/Protocols/sctp/task.hpp new file mode 100644 index 0000000000000000000000000000000000000000..811501227ede6d1090d7dc9906b6f1b243a3c17e --- /dev/null +++ b/ccsrc/Protocols/sctp/task.hpp @@ -0,0 +1,68 @@ +// +// 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace nr::gnb +{ + +class SctpTask : public NtsTask +{ + private: + struct ClientEntry + { + int id; + sctp::SctpClient *client; + ScopedThread *receiverThread; + sctp::ISctpHandler *handler; + NtsTask *associatedTask; + }; + + private: + TaskBase *m_base; + std::unique_ptr m_logger; + std::unordered_map m_clients; + + friend class GnbCmdHandler; + + public: + explicit SctpTask(TaskBase *base); + ~SctpTask() override = default; + + protected: + void onStart() override; + void onLoop() override; + void onQuit() override; + + private: + static void DeleteClientEntry(ClientEntry *entry); + + private: + void receiveSctpConnectionSetupRequest(int clientId, const std::string &localAddress, uint16_t localPort, + const std::string &remoteAddress, uint16_t remotePort, + sctp::PayloadProtocolId ppid, NtsTask *associatedTask); + void receiveAssociationSetup(int clientId, int associationId, int inStreams, int outStreams); + void receiveAssociationShutdown(int clientId); + void receiveClientReceive(int clientId, uint16_t stream, UniqueBuffer &&buffer); + void receiveUnhandledNotification(int clientId); + void receiveConnectionClose(int clientId); + void receiveSendMessage(int clientId, uint16_t stream, UniqueBuffer &&buffer); +}; + +} // namespace nr::gnb \ No newline at end of file diff --git a/ccsrc/Protocols/sctp/time_stamp.cc b/ccsrc/Protocols/sctp/time_stamp.cc new file mode 100644 index 0000000000000000000000000000000000000000..14fbd45090cf789b71ad9910f662954498b30488 --- /dev/null +++ b/ccsrc/Protocols/sctp/time_stamp.cc @@ -0,0 +1,9 @@ +// +// 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 "time_stamp.hh" diff --git a/ccsrc/Protocols/sctp/time_stamp.hh b/ccsrc/Protocols/sctp/time_stamp.hh new file mode 100644 index 0000000000000000000000000000000000000000..b17deb6d9f9d594d1c17f886852aa422a14a90aa --- /dev/null +++ b/ccsrc/Protocols/sctp/time_stamp.hh @@ -0,0 +1,45 @@ +// +// 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 + +struct TimeStamp +{ + const int64_t ntpTime; + + explicit TimeStamp(const int64_t ntpTime) : ntpTime(ntpTime) + { + } + + [[nodiscard]] inline int64_t ntpValue() const + { + return ntpTime; + } + + [[nodiscard]] inline int64_t seconds64() const + { + return (ntpTime >> 32) & 0xffffffffLL; + } + + [[nodiscard]] inline int64_t fraction64() const + { + return ntpTime & 0xffffffffLL; + } + + [[nodiscard]] inline int seconds32() const + { + return static_cast((ntpTime >> 32) & 0xffffffffLL); + } + + [[nodiscard]] inline int fraction32() const + { + return static_cast(ntpTime & 0xffffffffLL); + } +}; \ No newline at end of file diff --git a/ccsrc/Protocols/sctp/types.cc b/ccsrc/Protocols/sctp/types.cc new file mode 100644 index 0000000000000000000000000000000000000000..5ef4833318acd52dd72decbab45c6277ae8dd3fc --- /dev/null +++ b/ccsrc/Protocols/sctp/types.cc @@ -0,0 +1,9 @@ +// +// 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 "types.hh" diff --git a/ccsrc/Protocols/sctp/types.hh b/ccsrc/Protocols/sctp/types.hh new file mode 100644 index 0000000000000000000000000000000000000000..c9b270be5eb479c7b9e31c8ecf08a7147456e8ed --- /dev/null +++ b/ccsrc/Protocols/sctp/types.hh @@ -0,0 +1,49 @@ +// +// 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 +#include +#include + +//#include +#include + +namespace sctp +{ + +enum class PayloadProtocolId +{ + NGAP = 60 +}; + +class SctpError : public std::runtime_error +{ + public: + explicit SctpError(const std::string &what) : std::runtime_error(what) + { + } + explicit SctpError(const char *what) : std::runtime_error(what) + { + } +}; + +class ISctpHandler +{ + public: + virtual ~ISctpHandler() = default; + + virtual void onAssociationSetup(int associationId, int inStreams, int outStreams) = 0; + virtual void onAssociationShutdown() = 0; + virtual void onConnectionReset() = 0; + virtual void onMessage(const uint8_t *buffer, size_t length, uint16_t stream) = 0; + virtual void onUnhandledNotification() = 0; +}; + +} // namespace sctp diff --git a/ccsrc/Protocols/sctp/unique_buffer.cc b/ccsrc/Protocols/sctp/unique_buffer.cc new file mode 100644 index 0000000000000000000000000000000000000000..07da4370035831a5512c614408f306d413560c5e --- /dev/null +++ b/ccsrc/Protocols/sctp/unique_buffer.cc @@ -0,0 +1,9 @@ +// +// 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 "unique_buffer.hh" diff --git a/ccsrc/Protocols/sctp/unique_buffer.hh b/ccsrc/Protocols/sctp/unique_buffer.hh new file mode 100644 index 0000000000000000000000000000000000000000..531858236774f6b81ee462ed4716b6d7fd51e2b0 --- /dev/null +++ b/ccsrc/Protocols/sctp/unique_buffer.hh @@ -0,0 +1,70 @@ +// +// 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 +#include + +class UniqueBuffer +{ + uint8_t *m_data; + size_t m_size; + + public: + inline UniqueBuffer() noexcept : m_data(nullptr), m_size(0) + { + } + + inline UniqueBuffer(uint8_t *data, size_t length) noexcept : m_data(data), m_size(length) + { + } + + inline ~UniqueBuffer() noexcept + { + delete[] m_data; + m_data = nullptr; + m_size = 0; + } + + UniqueBuffer(const UniqueBuffer &m) = delete; + + inline UniqueBuffer(UniqueBuffer &&m) noexcept : m_data(m.m_data), m_size(m.m_size) + { + m.m_data = nullptr; + m.m_size = 0; + } + + UniqueBuffer &operator=(const UniqueBuffer &m) = delete; + + inline UniqueBuffer &operator=(UniqueBuffer &&m) noexcept + { + m_data = m.m_data; + m_size = m.m_size; + + m.m_data = nullptr; + m.m_size = 0; + + return *this; + } + + [[nodiscard]] inline const uint8_t *data() const + { + return m_data; + } + + [[nodiscard]] inline const uint8_t *data() + { + return m_data; + } + + [[nodiscard]] inline size_t size() const + { + return m_size; + } +};