diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b57ae100..5ed014562 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,8 @@ option(ENABLE_SOAPYSDR "Enable SoapySDR" ON) option(ENABLE_ZEROMQ "Enable ZeroMQ" ON) option(ENABLE_HARDSIM "Enable support for SIM cards" ON) +option(ENABLE_TTCN3 "Enable TTCN3 test binaries" OFF) + option(BUILD_STATIC "Attempt to statically link external deps" OFF) option(RPATH "Enable RPATH" OFF) option(ENABLE_ASAN "Enable gcc/clang address sanitizer" OFF) @@ -227,6 +229,14 @@ if(ENABLE_GUI) endif(SRSGUI_FOUND) endif(ENABLE_GUI) +if (ENABLE_TTCN3) + find_package(RapidJSON REQUIRED) + add_definitions(-DENABLE_TTCN3) + include_directories(${RAPIDJSON_INCLUDE_DIRS}) + link_directories(${RAPIDJSON_LIBRARY_DIRS}) + message(STATUS "Building with TTCN3 binaries") +endif (ENABLE_TTCN3) + ######################################################################## # Install Dirs ######################################################################## diff --git a/srsue/hdr/phy/ue_lte_phy_base.h b/srsue/hdr/phy/ue_lte_phy_base.h index b1b6005db..1c5ce809f 100644 --- a/srsue/hdr/phy/ue_lte_phy_base.h +++ b/srsue/hdr/phy/ue_lte_phy_base.h @@ -28,6 +28,7 @@ #define SRSUE_UE_LTE_PHY_BASE_H #include "srslte/common/log_filter.h" +#include "srslte/interfaces/common_interfaces.h" #include "srsue/hdr/phy/ue_phy_base.h" namespace srsue { diff --git a/srsue/test/CMakeLists.txt b/srsue/test/CMakeLists.txt index 01136b8d5..8e430cbdc 100644 --- a/srsue/test/CMakeLists.txt +++ b/srsue/test/CMakeLists.txt @@ -20,6 +20,10 @@ add_subdirectory(upper) +if (ENABLE_TTCN3) + add_subdirectory(ttcn3) +endif (ENABLE_TTCN3) + add_executable(metrics_test metrics_test.cc ../src/metrics_stdout.cc ../src/metrics_csv.cc) target_link_libraries(metrics_test srslte_phy srslte_common) add_test(metrics_test metrics_test -o ${CMAKE_CURRENT_BINARY_DIR}/ue_metrics.csv) diff --git a/srsue/test/ttcn3/CMakeLists.txt b/srsue/test/ttcn3/CMakeLists.txt new file mode 100644 index 000000000..b9add7c2e --- /dev/null +++ b/srsue/test/ttcn3/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright 2013-2019 Software Radio Systems Limited +# +# This file is part of srsLTE +# +# srsLTE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# srsLTE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# A copy of the GNU Affero General Public License can be found in +# the LICENSE file in the top-level directory of this distribution +# and at http://www.gnu.org/licenses/. +# + +add_subdirectory(src) +add_subdirectory(test) diff --git a/srsue/test/ttcn3/README.md b/srsue/test/ttcn3/README.md new file mode 100644 index 000000000..4364d7222 --- /dev/null +++ b/srsue/test/ttcn3/README.md @@ -0,0 +1,23 @@ +srsUE conformance testing +========================= + +This folder includes an srsUE extension that allows to test +the upper-layer protocol stack against the UE conformance tests specified +by 3GPP/ETSI in TS 36.523 [1]. + +The tester itself is built with Eclipse Titan [2] using the 3GPP EUTRA (LTE/EPC) UE Test Suites [3]. It is, however, +not part of the srsLTE repository. + +The extensions present in this folder add a basic system simulator (SS) that talks over an ideal PHY to the +fully unmodified UE upper-layer stack. It SS itself interfaces over multiple TCP/IP sockets to the TTCN3 tester, i.e., +implements (a subset of) the interfaces specified in [1]. + + +Resources +--------- + +[1] https://www.etsi.org/deliver/etsi_ts/136500_136599/13652301 + +[2] https://projects.eclipse.org/projects/tools.titan + +[3] http://www.ttcn-3.org/index.php/downloads/publicts/publicts-3gpp diff --git a/srsue/test/ttcn3/hdr/dut_utils.h b/srsue/test/ttcn3/hdr/dut_utils.h new file mode 100644 index 000000000..eb7d3d7c0 --- /dev/null +++ b/srsue/test/ttcn3/hdr/dut_utils.h @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +/*! \brief Utility functions for the DUT. + * + */ + +#ifndef SRSUE_TTCN3_DUT_UTILS_H +#define SRSUE_TTCN3_DUT_UTILS_H + +#include +#include + +std::string get_filename_with_tc_name(const std::string& str, const uint32_t run_id, const std::string tc_name) +{ + // split base path into components + std::set delims; + delims.insert('/'); + std::vector result; + char const* pch = str.c_str(); + char const* start = pch; + for (; *pch; ++pch) { + if (delims.find(*pch) != delims.end()) { + if (start != pch) { + std::string str(start, pch); + result.push_back(str); + } else { + result.push_back(""); + } + start = pch + 1; + } + } + result.push_back(start); + + // prepend TC name to last element (the actual filename) + stringstream filename_ss; + filename_ss << tc_name << "_" + << "run" << run_id << "_" << result.back(); + + std::string final_path; + std::vector::iterator path_it; + for (path_it = result.begin() + 1; path_it != --result.end(); ++path_it) { + final_path += "/"; + final_path += *path_it; + } + final_path += "/"; + final_path += filename_ss.str(); + + return final_path; +} + +std::string get_tc_name(const std::string& str) +{ + // split after dot + std::string::size_type pos = str.find('.'); + return (pos == str.npos ? str : str.substr(pos + 1, -1)); +} + +#endif // SRSUE_TTCN3_DUT_UTILS_H diff --git a/srsue/test/ttcn3/hdr/lte_ttcn3_phy.h b/srsue/test/ttcn3/hdr/lte_ttcn3_phy.h new file mode 100644 index 000000000..d9f91377d --- /dev/null +++ b/srsue/test/ttcn3/hdr/lte_ttcn3_phy.h @@ -0,0 +1,149 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_LTE_PHY_H +#define SRSUE_TTCN3_LTE_PHY_H + +#include "srsue/hdr/phy/ue_lte_phy_base.h" +#include "srsue/hdr/ue.h" +#include "ttcn3_interfaces.h" +#include +#include + +using namespace srsue; +using namespace srslte; + +namespace srsue { + +class lte_ttcn3_phy : public ue_lte_phy_base +{ +public: + typedef struct { + srslte_cell_t info; + float power; + uint32_t earfcn; + } cell_t; + typedef std::vector cell_list_t; + + lte_ttcn3_phy(srslte::logger* logger_); + ~lte_ttcn3_phy(); + + int init(const phy_args_t& args_, stack_interface_phy_lte* stack_, syssim_interface_phy* syssim_); + + int init(const phy_args_t& args_, stack_interface_phy_lte* stack_, radio_interface_phy* radio_); + + // ue_phy_base interface + int init(const phy_args_t& args_); + void stop(); + void set_earfcn(std::vector earfcns); + void force_freq(float dl_freq, float ul_freq); + void wait_initialize(); + void start_plot(); + void get_metrics(phy_metrics_t* m); + std::string get_type(); + + // The interface for the SS + void set_cell_map(const cell_list_t& cells_); + + // phy_interface_rrc_lte + void get_current_cell(srslte_cell_t* cell_, uint32_t* earfcn_ = NULL); + uint32_t get_current_earfcn(); + uint32_t get_current_pci(); + void set_config_scell(asn1::rrc::scell_to_add_mod_r10_s* scell_config); + void enable_pregen_signals(bool enable); + void set_activation_deactivation_scell(uint32_t cmd); + void + set_config(srslte::phy_cfg_t& config, uint32_t cc_idx = 0, uint32_t earfcn = 0, srslte_cell_t* cell_info = nullptr); + void set_config_tdd(srslte_tdd_config_t& tdd_config); + void set_config_mbsfn_sib2(asn1::rrc::sib_type2_s* sib2); + void set_config_mbsfn_sib13(asn1::rrc::sib_type13_r9_s* sib13); + void set_config_mbsfn_mcch(asn1::rrc::mcch_msg_s* mcch); + + // Measurements interface + void meas_reset(); + int meas_start(uint32_t earfcn, int pci = -1); + int meas_stop(uint32_t earfcn, int pci = -1); + + // phy_interface_mac_lte + void set_mch_period_stop(uint32_t stop); + + // Cell search and selection procedures + cell_search_ret_t cell_search(phy_cell_t* found_cell); + bool cell_select(phy_cell_t* cell); + bool cell_is_camping(); + void reset(); + + // phy_interface_mac_lte + void configure_prach_params(); + void prach_send(uint32_t preamble_idx, int allowed_subframe, float target_power_dbm); + prach_info_t prach_get_info(); + void sr_send(); + int sr_last_tx_tti(); + + // phy_interface_mac_common + void set_crnti(uint16_t rnti); + void set_timeadv_rar(uint32_t ta_cmd); + void set_timeadv(uint32_t ta_cmd); + void set_rar_grant(uint8_t grant_payload[SRSLTE_RAR_GRANT_LEN], uint16_t rnti); + uint32_t get_current_tti(); + float get_phr(); + float get_pathloss_db(); + + // phy_interface_syssim + void set_current_tti(uint32_t tti); + + void new_grant_ul(mac_interface_phy_lte::mac_grant_ul_t ul_mac_grant); + void new_tb(const srsue::mac_interface_phy_lte::mac_grant_dl_t, const uint8_t* data); + + // Radio interface + void radio_overflow(); + void radio_failure(); + + void run_tti(); + +private: + srslte::logger* logger = nullptr; + srslte::log_filter log; + + // The current cell + cell_list_t cells; + cell_t pcell = {}; + + phy_cfg_t phy_cfg = {}; + + uint32_t current_tti = 0; + uint16_t current_temp_rnti = 0; + uint32_t cc_idx = 0; + + int prach_tti_tx = -1; + + int sr_tx_tti = -1; + bool sr_pending = false; + + uint32_t ra_trans_cnt = 0; + + stack_interface_phy_lte* stack = nullptr; + syssim_interface_phy* syssim = nullptr; +}; + +} // namespace srsue + +#endif \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_helpers.h b/srsue/test/ttcn3/hdr/ttcn3_helpers.h new file mode 100644 index 000000000..940e45edd --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_helpers.h @@ -0,0 +1,360 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +/*! \brief This class provides helpers for encoding JSON-formated responses + * for TTCN3. + * + */ + +#ifndef SRSUE_TTCN3_HELPERS_H +#define SRSUE_TTCN3_HELPERS_H + +#include "rapidjson/document.h" // rapidjson's DOM-style API +#include "rapidjson/prettywriter.h" // for stringify JSON +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace rapidjson; + +class ttcn3_helpers +{ +public: + typedef struct { + bool rb_is_srb; + uint8_t rb_id; + uint32_t ul_value; + uint32_t dl_value; + } pdcp_count_t; + + static std::string get_ctrl_cnf(const std::string protocol_, const std::string version_, const std::string addr_) + { + Document resp; + resp.SetObject(); + + // Create members of common object + Value ctrl(kObjectType); + + // The connection ID object + Value conn_id(kObjectType); + + // Protocol + Value protocol(protocol_.c_str(), resp.GetAllocator()); + conn_id.AddMember("Protocol", protocol, resp.GetAllocator()); + + // Version + + Value ipAddr(kObjectType); + Value version(version_.c_str(), resp.GetAllocator()); + Value addr(addr_.c_str(), resp.GetAllocator()); + + if (version_ == "V4") { + Value v4(kObjectType); + v4.AddMember("Addr", addr, resp.GetAllocator()); + ipAddr.AddMember("V4", v4, resp.GetAllocator()); + } else if (version_ == "V6") { + Value v6(kObjectType); + v6.AddMember("Addr", addr, resp.GetAllocator()); + ipAddr.AddMember("V6", v6, resp.GetAllocator()); + } else { + fprintf(stderr, "Unsupported protocol version: %s.\n", version_.c_str()); + return std::string(""); + } + + // Local + Value local(kObjectType); + local.AddMember("IpAddr", ipAddr, resp.GetAllocator()); + + if (protocol_ == "udp") { + local.AddMember("Port", 33, resp.GetAllocator()); + } + + // Remote + Value rem(kObjectType); + + // Add members to Connection ID + conn_id.AddMember("Local", local, resp.GetAllocator()); + conn_id.AddMember("Remote", rem, resp.GetAllocator()); + + // The Indication object + Value ind(kObjectType); + Value ind_protocol_val(kObjectType); + ind_protocol_val.AddMember("SocketCnf", true, resp.GetAllocator()); + if (protocol_ == "udp") { + ind.AddMember("UDP", ind_protocol_val, resp.GetAllocator()); + } else if (protocol_ == "icmpv6") { + ind.AddMember("ICMP", ind_protocol_val, resp.GetAllocator()); + } else { + fprintf(stderr, "Unsupported protocol %s.\n", protocol_.c_str()); + return std::string(""); + } + + ctrl.AddMember("ConnectionId", conn_id, resp.GetAllocator()); + ctrl.AddMember("Ind", ind, resp.GetAllocator()); + + resp.AddMember("CTRL", ctrl, resp.GetAllocator()); + + // JSON-ize + StringBuffer buffer; + Writer writer(buffer); + resp.Accept(writer); + + // Return as std::string + return std::string(buffer.GetString()); + } + + static std::string get_drbmux_common_ind_cnf() + { + Document resp; + resp.SetObject(); + + resp.AddMember("Confirm", true, resp.GetAllocator()); + + // JSON-ize + StringBuffer buffer; + Writer writer(buffer); + resp.Accept(writer); + + // Return as std::string + return std::string(buffer.GetString()); + } + + static std::string get_pdcp_count_response(const std::string cell_, + const std::vector& bearers) + { + Document resp; + resp.SetObject(); + + // Create members of common object + + // Cell + Value cell(cell_.c_str(), resp.GetAllocator()); + + // RoutingInfo + Value routing_info(kObjectType); + routing_info.AddMember("None", true, resp.GetAllocator()); + + // TimingInfo + Value timing_info(kObjectType); + timing_info.AddMember("Now", true, resp.GetAllocator()); + + // Result + Value result(kObjectType); + result.AddMember("Success", true, resp.GetAllocator()); + + // Now, create the common object itself and add members + Value common(kObjectType); + common.AddMember("CellId", cell, resp.GetAllocator()); + common.AddMember("RoutingInfo", routing_info, resp.GetAllocator()); + common.AddMember("TimingInfo", timing_info, resp.GetAllocator()); + common.AddMember("Result", result, resp.GetAllocator()); + + // Add all bearers + rapidjson::Value bearer_array(rapidjson::kArrayType); + std::vector::const_iterator it; + for (it = bearers.begin(); it != bearers.end(); ++it) { + // SRB0 + Value radio_bearer_id(kObjectType); + radio_bearer_id.AddMember(it->rb_is_srb ? "Srb" : "Drb", it->rb_id, resp.GetAllocator()); + + Value ul(kObjectType); + Value rb_format_ul(it->rb_is_srb ? "PdcpCount_Srb" : "PdcpCount_DrbLongSQN", resp.GetAllocator()); + ul.AddMember("Format", rb_format_ul, resp.GetAllocator()); + + // convert int to sstream, then to RapidJSON value and add as member + stringstream value_ss; + value_ss << std::bitset<32>(it->ul_value); + Value ul_value(value_ss.str().c_str(), resp.GetAllocator()); + ul.AddMember("Value", ul_value, resp.GetAllocator()); + + Value dl(kObjectType); + Value rb_format_dl(it->rb_is_srb ? "PdcpCount_Srb" : "PdcpCount_DrbLongSQN", resp.GetAllocator()); + dl.AddMember("Format", rb_format_dl, resp.GetAllocator()); + + // do the same conversion for the DL value + value_ss.str(""); + value_ss.clear(); + value_ss << std::bitset<32>(it->dl_value); + Value dl_value(value_ss.str().c_str(), resp.GetAllocator()); + dl.AddMember("Value", dl_value, resp.GetAllocator()); + + // The Get object combines the information of all requested bearer + Value get_element(kObjectType); + get_element.AddMember("RadioBearerId", radio_bearer_id, resp.GetAllocator()); + get_element.AddMember("UL", ul, resp.GetAllocator()); + get_element.AddMember("DL", dl, resp.GetAllocator()); + + bearer_array.PushBack(get_element, resp.GetAllocator()); + } + + // The confirm object + Value confirm_key(kObjectType); + + // Add array + confirm_key.AddMember("Get", bearer_array, resp.GetAllocator()); + + Value confirm(kObjectType); + confirm.AddMember("PdcpCount", confirm_key, resp.GetAllocator()); + + resp.AddMember("Common", common, resp.GetAllocator()); + resp.AddMember("Confirm", confirm, resp.GetAllocator()); + + // JSON-ize + StringBuffer buffer; + Writer writer(buffer); + resp.Accept(writer); + + // Return as std::string + return std::string(buffer.GetString()); + } + + static std::string get_basic_sys_req_cnf(const std::string cell_, const std::string confirm_key_) + { + Document resp; + resp.SetObject(); + + // Create members of common object + + // Cell + Value cell(cell_.c_str(), resp.GetAllocator()); + + // RoutingInfo + Value routing_info(kObjectType); + routing_info.AddMember("None", true, resp.GetAllocator()); + + // TimingInfo + Value timing_info(kObjectType); + timing_info.AddMember("Now", true, resp.GetAllocator()); + + // Result + Value result(kObjectType); + result.AddMember("Success", true, resp.GetAllocator()); + + // Now, create the common object itself and add members + Value common(kObjectType); + common.AddMember("CellId", cell, resp.GetAllocator()); + common.AddMember("RoutingInfo", routing_info, resp.GetAllocator()); + common.AddMember("TimingInfo", timing_info, resp.GetAllocator()); + common.AddMember("Result", result, resp.GetAllocator()); + + // The confirm object + Value confirm_key(confirm_key_.c_str(), resp.GetAllocator()); + Value confirm_val(true); + Value confirm(kObjectType); + confirm.AddMember(confirm_key, confirm_val, resp.GetAllocator()); + + resp.AddMember("Common", common, resp.GetAllocator()); + resp.AddMember("Confirm", confirm, resp.GetAllocator()); + + // JSON-ize + StringBuffer buffer; + Writer writer(buffer); + resp.Accept(writer); + + // Return as std::string + return std::string(buffer.GetString()); + } + + static std::string + get_sys_req_cnf_with_time(const std::string cell_, const std::string confirm_key_, const uint32_t tti) + { + Document resp; + resp.SetObject(); + + // Create members of common object + + // Cell + Value cell(cell_.c_str(), resp.GetAllocator()); + + // RoutingInfo + Value routing_info(kObjectType); + routing_info.AddMember("None", true, resp.GetAllocator()); + + // TimingInfo + + // SFN + uint32_t sfn = tti / 10; + Value sfn_key(kObjectType); + sfn_key.AddMember("Number", sfn, resp.GetAllocator()); + + // Actual subframe index + uint32_t sf_idx = tti % 10; + Value sf_idx_key(kObjectType); + sf_idx_key.AddMember("Number", sf_idx, resp.GetAllocator()); + + // Put it all together + Value subframe_key(kObjectType); + subframe_key.AddMember("SFN", sfn_key, resp.GetAllocator()); + subframe_key.AddMember("Subframe", sf_idx_key, resp.GetAllocator()); + + Value timing_info(kObjectType); + timing_info.AddMember("SubFrame", subframe_key, resp.GetAllocator()); + + // Result + Value result(kObjectType); + result.AddMember("Success", true, resp.GetAllocator()); + + // Now, create the common object itself and add members + Value common(kObjectType); + common.AddMember("CellId", cell, resp.GetAllocator()); + common.AddMember("RoutingInfo", routing_info, resp.GetAllocator()); + common.AddMember("TimingInfo", timing_info, resp.GetAllocator()); + common.AddMember("Result", result, resp.GetAllocator()); + + // The confirm object + Value confirm_key(confirm_key_.c_str(), resp.GetAllocator()); + Value confirm_val(true); + Value confirm(kObjectType); + confirm.AddMember(confirm_key, confirm_val, resp.GetAllocator()); + + resp.AddMember("Common", common, resp.GetAllocator()); + resp.AddMember("Confirm", confirm, resp.GetAllocator()); + + // JSON-ize + StringBuffer buffer; + Writer writer(buffer); + resp.Accept(writer); + + // Return as std::string + return std::string(buffer.GetString()); + } + + static bool requires_confirm(Document& document) + { + const Value& a = document["Common"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + const Value& config_flag = b["CnfFlag"]; + assert(config_flag.IsBool()); + + return config_flag.GetBool(); + } +}; + +#endif // SRSUE_TTCN3_HELPERS_H diff --git a/srsue/test/ttcn3/hdr/ttcn3_interfaces.h b/srsue/test/ttcn3/hdr/ttcn3_interfaces.h new file mode 100644 index 000000000..48984cfdf --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_interfaces.h @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_INTERFACES_H +#define SRSUE_TTCN3_INTERFACES_H + +#include "srslte/common/common.h" +#include "srslte/interfaces/ue_interfaces.h" + +// Interface used by system interface to communicate with main component +class syssim_interface +{ +public: + virtual void tc_start(const char* name) = 0; + virtual void tc_end() = 0; + virtual void power_off_ue() = 0; + virtual void switch_on_ue() = 0; + virtual void switch_off_ue() = 0; + virtual void set_cell_config(std::string cell_name, uint32_t earfcn, srslte_cell_t cell, const float power) = 0; + virtual void set_cell_attenuation(std::string cell_name, const float attenuation) = 0; + virtual void add_bcch_pdu(srslte::unique_byte_buffer_t pdu) = 0; + virtual void add_ccch_pdu(srslte::unique_byte_buffer_t pdu) = 0; + virtual void add_dcch_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu) = 0; + virtual void add_pch_pdu(srslte::unique_byte_buffer_t pdu) = 0; + virtual void add_srb(uint32_t lcid, srslte::pdcp_config_t pdcp_config) = 0; + virtual void del_srb(uint32_t lcid) = 0; + virtual uint32_t get_tti() = 0; + virtual int set_as_security(const uint32_t lcid, + const std::array k_rrc_enc, + const std::array k_rrc_int, + const std::array k_up_enc, + const srslte::CIPHERING_ALGORITHM_ID_ENUM cipher_algo, + const srslte::INTEGRITY_ALGORITHM_ID_ENUM integ_algo) = 0; +}; + +class syssim_interface_phy +{ +public: + virtual void prach_indication(uint32_t preamble_index, const uint32_t& cell_id) = 0; + virtual void sr_req(uint32_t tti_tx) = 0; + // 0; virtual void + virtual void tx_pdu(const uint8_t* payload, const int len, const uint32_t tx_tti) = 0; +}; + +class phy_interface_syssim +{ +public: + virtual void set_current_tti(uint32_t tti) = 0; + virtual uint16_t get_dl_sched_rnti(uint32_t tti) = 0; + virtual void new_grant_ul(const srsue::mac_interface_phy_lte::mac_grant_ul_t grant) = 0; + virtual void new_tb(const srsue::mac_interface_phy_lte::mac_grant_dl_t mac_grant, const uint8_t* data) = 0; +}; + +#endif // SRSUE_TTCN3_INTERFACES_H \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_ip_ctrl_interface.h b/srsue/test/ttcn3/hdr/ttcn3_ip_ctrl_interface.h new file mode 100644 index 000000000..78f0ee1f0 --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_ip_ctrl_interface.h @@ -0,0 +1,125 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_IP_CTRL_INTERFACE_H +#define SRSUE_TTCN3_IP_CTRL_INTERFACE_H + +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "srslte/common/netsource_handler.h" +#include "srslte/phy/io/netsource.h" +#include "ttcn3_helpers.h" + +using namespace rapidjson; + +// The IP CTRL interface to the IP_PTC +class ttcn3_ip_ctrl_interface : public netsource_handler +{ +public: + ttcn3_ip_ctrl_interface() : netsource_handler("TTCN3_IP_CTRL_IF"){}; + ~ttcn3_ip_ctrl_interface(){}; + + void init(srslte::log* log_, std::string net_ip_, uint32_t net_port_) + { + net_ip = net_ip_; + net_port = net_port_; + log = log_; + initialized = true; + log->debug("Initialized.\n"); + } + +private: + void run_thread() + { + // open TCP socket + if (srslte_netsource_init(&net_source, net_ip.c_str(), net_port, SRSLTE_NETSOURCE_TCP)) { + fprintf(stderr, "Error creating input TCP socket at port %d\n", net_port); + exit(-1); + } + + log->info("Listening on %s:%d for incoming connections ..\n", net_ip.c_str(), net_port); + + running = true; + + int n; + while (run_enable) { + log->debug("Reading from IP_CTRL port ..\n"); + n = srslte_netsource_read(&net_source, rx_buf->begin(), RX_BUF_SIZE); + if (n > 0) { + rx_buf->at(n) = '\0'; + + Document document; + if (document.Parse((char*)rx_buf->begin()).HasParseError()) { + log->error_hex(rx_buf->begin(), n, "Error parsing incoming data.\n"); + break; + } + assert(document.IsObject()); + + // Pretty-print + StringBuffer buffer; + PrettyWriter writer(buffer); + document.Accept(writer); + log->info("Received %d bytes\n%s\n", n, (char*)buffer.GetString()); + + // Get message + if (document.HasMember("RoutingInfo")) { + log->info("Received RoutingInfo\n"); + handle_routing_info(document); + } else { + log->error("Received unknown request.\n"); + } + } else if (n == 0) { + log->error("Receiving null from network\n"); + } else { + log->error("Error receiving from network\n"); + } + } + + running = false; + + srslte_netsource_free(&net_source); + } + + void handle_routing_info(Document& document) + { + // get CTRL + const Value& routes = document["RoutingInfo"]; + assert(routes.IsArray()); + + // iterate over all routes + for (Value::ConstValueIterator itr = routes.Begin(); itr != routes.End(); ++itr) { + assert(itr->HasMember("IpInfo")); + + // printf("Configuring attenuation of %s to %ddB\n", id.GetString(), att["Value"].GetInt()); + + // FIXME: actually do configuration + } + + // What else to check? + + std::string resp = ttcn3_helpers::get_drbmux_common_ind_cnf(); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } +}; + +#endif // SRSUE_TTCN3_IP_CTRL_INTERFACE_H \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_ip_sock_interface.h b/srsue/test/ttcn3/hdr/ttcn3_ip_sock_interface.h new file mode 100644 index 000000000..00951f056 --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_ip_sock_interface.h @@ -0,0 +1,134 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_IP_SOCK_INTERFACE_H +#define SRSUE_TTCN3_IP_SOCK_INTERFACE_H + +// The IP Socket interface to the IP_PTC +class ttcn3_ip_sock_interface : public netsource_handler +{ +public: + ttcn3_ip_sock_interface() : netsource_handler("TTCN3_IP_SOCK_IF"){}; + ~ttcn3_ip_sock_interface(){}; + + void init(srslte::log* log_, std::string net_ip_, uint32_t net_port_) + { + net_ip = net_ip_; + net_port = net_port_; + log = log_; + initialized = true; + log->debug("Initialized.\n"); + } + +private: + void run_thread() + { + if (!initialized) { + fprintf(stderr, "IP_SOCK interface not initialized. Exiting.\n"); + exit(-1); + } + + // open TCP socket + if (srslte_netsource_init(&net_source, net_ip.c_str(), net_port, SRSLTE_NETSOURCE_TCP)) { + fprintf(stderr, "Error creating input TCP socket at port %d\n", net_port); + exit(-1); + } + + log->info("Listening on %s:%d for incoming connections ..\n", net_ip.c_str(), net_port); + + running = true; + + int n; + while (run_enable) { + log->debug("Reading from IP_SOCK port ..\n"); + n = srslte_netsource_read(&net_source, rx_buf->begin(), RX_BUF_SIZE); + if (n > 0) { + rx_buf->at(n) = '\0'; + + Document document; + if (document.Parse((char*)rx_buf->begin()).HasParseError()) { + log->error_hex(rx_buf->begin(), n, "Error parsing incoming data.\n"); + break; + } + assert(document.IsObject()); + + // Pretty-print + StringBuffer buffer; + PrettyWriter writer(buffer); + document.Accept(writer); + log->info("Received %d bytes\n%s\n", n, (char*)buffer.GetString()); + + // Get message + if (document.HasMember("CTRL")) { + log->info("Received CTRL command.\n"); + handle_ctrl(document); + } else { + log->error("Received unknown request.\n"); + } + } else if (n == 0) { + log->error("Receiving null from network\n"); + } else { + log->error("Error receiving from network\n"); + // exit(-1); + } + } + + running = false; + + srslte_netsource_free(&net_source); + } + + void handle_ctrl(Document& document) + { + // get CTRL + const Value& ctrl = document["CTRL"]; + assert(ctrl.HasMember("ConnectionId")); + + const Value& conn_id = ctrl["ConnectionId"]; + assert(conn_id.HasMember("Protocol")); + + const Value& protocol = conn_id["Protocol"]; + assert(protocol.IsString()); + + // Get IP version and addr + std::string ip_version; + const Value& local = conn_id["Local"]; + assert(local.HasMember("IpAddr")); + const Value& ipAddr = local["IpAddr"]; + if (ipAddr.HasMember("V4")) { + ip_version = "V4"; + } else if (ipAddr.HasMember("V6")) { + ip_version = "V6"; + } + assert(ipAddr[ip_version.c_str()].HasMember("Addr")); + const Value& addr = ipAddr[ip_version.c_str()]["Addr"]; + + // Todo: Handle command + + // Send response + string resp = ttcn3_helpers::get_ctrl_cnf(protocol.GetString(), ip_version, addr.GetString()); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } +}; + +#endif // SRSUE_TTCN3_IP_SOCK_INTERFACE_H \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_srb_interface.h b/srsue/test/ttcn3/hdr/ttcn3_srb_interface.h new file mode 100644 index 000000000..0f2cf8dad --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_srb_interface.h @@ -0,0 +1,173 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_SRB_INTERFACE_H +#define SRSUE_TTCN3_SRB_INTERFACE_H + +#include "srslte/common/buffer_pool.h" +#include "srslte/common/common.h" +#include "srslte/common/pdu.h" +#include "ttcn3_interfaces.h" +#include + +using namespace srslte; + +// The SRB interface +class ttcn3_srb_interface : public netsource_handler +{ +public: + ttcn3_srb_interface() : syssim(nullptr), pool(byte_buffer_pool::get_instance()), netsource_handler("TTCN3_SRB_IF"){}; + ~ttcn3_srb_interface(){}; + + void init(syssim_interface* syssim_, srslte::log* log_, std::string net_ip_, uint32_t net_port_) + { + syssim = syssim_; + log = log_; + net_ip = net_ip_; + net_port = net_port_; + + initialized = true; + log->debug("Initialized.\n"); + } + + void tx(unique_byte_buffer_t pdu) + { + if (running) { + log->info_hex(pdu->msg, pdu->N_bytes, "Sending %d B to Titan\n", pdu->N_bytes); + srslte_netsource_write(&net_source, (void*)pdu->msg, pdu->N_bytes); + } else { + log->error("Trying to transmit but port not connected.\n"); + } + } + +private: + void run_thread() + { + if (!initialized) { + fprintf(stderr, "SRB interface not initialized. Exiting.\n"); + exit(-1); + } + + // open TCP socket + if (srslte_netsource_init(&net_source, net_ip.c_str(), net_port, SRSLTE_NETSOURCE_TCP)) { + fprintf(stderr, "Error creating input TCP socket at port %d\n", net_port); + exit(-1); + } + + running = true; + + int n; + while (run_enable) { + log->debug("Reading from SRB port ..\n"); + n = srslte_netsource_read(&net_source, rx_buf->begin(), RX_BUF_SIZE); + if (n > 0) { + rx_buf->at(n) = '\0'; + + log->debug_hex(rx_buf->begin(), n, "Received %d B from remote.\n", n); + + // Chop incoming msg, first two bytes are length of the JSON + // (see IPL4_EUTRA_SYSTEM_Definitions.ttcn + uint16_t json_len = ((uint16_t)rx_buf->at(0) << 8) | rx_buf->at(1); + + // Copy JSON from received buffer and null-terminate + char json[json_len + 1]; + memcpy(json, &rx_buf->at(2), json_len); + json[json_len] = '\0'; + + // The data part after the JSON starts right here but handling + // is done in the respective functions + uint16_t rx_buf_offset = json_len + 2; + + Document document; + if (document.Parse(json).HasParseError()) { + log->error_hex((uint8*)json, json_len, "Error parsing incoming data.\n"); + break; + } + assert(document.IsObject()); + + // Pretty-print + StringBuffer buffer; + PrettyWriter writer(buffer); + document.Accept(writer); + log->info("Received JSON with %d B\n%s\n", json_len, (char*)buffer.GetString()); + + // check for common + assert(document.HasMember("Common")); + assert(document["Common"].IsObject()); + + // Check for request type + assert(document.HasMember("RrcPdu")); + assert(document["RrcPdu"].IsObject()); + + // Get request type + const Value& rrcpdu = document["RrcPdu"]; + if (rrcpdu.HasMember("Ccch")) { + rx_buf_offset += 2; + handle_ccch_pdu(document, &rx_buf->at(rx_buf_offset), n - rx_buf_offset); + } else if (rrcpdu.HasMember("Dcch")) { + rx_buf_offset += 2; + uint32_t lcid = 1; + handle_dcch_pdu(document, lcid, &rx_buf->at(rx_buf_offset), n - rx_buf_offset); + } else { + log->error("Received unknown request.\n"); + } + } else if (n == 0) { + log->error("Receiving null from network\n"); + } else { + log->error("Error receiving from network\n"); + } + } + running = false; + + srslte_netsource_free(&net_source); + } + + // Todo: move to SYSSIM + void handle_ccch_pdu(Document& document, const uint8_t* payload, const uint16_t len) + { + log->info_hex(payload, len, "Received CCCH RRC PDU\n"); + + // pack into byte buffer + unique_byte_buffer_t pdu = pool_allocate_blocking; + pdu->N_bytes = len; + memcpy(pdu->msg, payload, pdu->N_bytes); + + syssim->add_ccch_pdu(std::move(pdu)); + } + + // Todo: move to SYSSIM + void handle_dcch_pdu(Document& document, const uint16_t lcid, const uint8_t* payload, const uint16_t len) + { + log->info_hex(payload, len, "Received DCCH RRC PDU\n"); + + // pack into byte buffer + unique_byte_buffer_t pdu = pool_allocate_blocking; + pdu->N_bytes = len; + memcpy(pdu->msg, payload, pdu->N_bytes); + + syssim->add_dcch_pdu(lcid, std::move(pdu)); + } + + syssim_interface* syssim = nullptr; + byte_buffer_pool* pool = nullptr; +}; + +#endif // SRSUE_TTCN3_SRB_INTERFACE_H \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_sys_interface.h b/srsue/test/ttcn3/hdr/ttcn3_sys_interface.h new file mode 100644 index 000000000..c85bb3252 --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_sys_interface.h @@ -0,0 +1,603 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_SYS_INTERFACE_H +#define SRSUE_TTCN3_SYS_INTERFACE_H + +#include "srslte/common/buffer_pool.h" +#include "ttcn3_helpers.h" +#include "ttcn3_interfaces.h" + +using namespace srslte; + +// The EUTRA.SYS interface +class ttcn3_sys_interface : public netsource_handler +{ +public: + ttcn3_sys_interface() : netsource_handler("TTCN3_SYS_IF"){}; + ~ttcn3_sys_interface(){}; + + void init(syssim_interface* syssim_, srslte::log* log_, std::string net_ip_, uint32_t net_port_) + { + syssim = syssim_; + net_ip = net_ip_; + net_port = net_port_; + log = log_; + initialized = true; + log->debug("Initialized.\n"); + pool = byte_buffer_pool::get_instance(); + } + +private: + void handle_request_cell_basic(Document& document, const uint8_t* payload, const uint16_t len) + { + if (document["Request"]["Cell"]["AddOrReconfigure"]["Basic"].HasMember("StaticCellInfo")) { + // Extract EARFCN + const Value& earfcn = + document["Request"]["Cell"]["AddOrReconfigure"]["Basic"]["StaticCellInfo"]["Downlink"]["Earfcn"]; + assert(earfcn.IsInt()); + + // Extract cell config + const Value& common_config = document["Request"]["Cell"]["AddOrReconfigure"]["Basic"]["StaticCellInfo"]["Common"]; + const Value& dl_config = document["Request"]["Cell"]["AddOrReconfigure"]["Basic"]["StaticCellInfo"]["Downlink"]; + const Value& phy_dl_config = document["Request"]["Cell"]["AddOrReconfigure"]["Basic"]["PhysicalLayerConfigDL"]; + + srslte_cell_t cell = {}; + cell.id = common_config["PhysicalCellId"].GetInt(); + cell.cp = (strcmp(dl_config["CyclicPrefix"].GetString(), "normal") == 0) ? SRSLTE_CP_NORM : SRSLTE_CP_EXT; + cell.nof_ports = + (strcmp(phy_dl_config["AntennaGroup"]["AntennaInfoCommon"]["R8"]["antennaPortsCount"].GetString(), "an1") == + 0) + ? 1 + : 2; + cell.nof_prb = (strcmp(dl_config["Bandwidth"].GetString(), "n25") == 0) ? 25 : 0; + cell.phich_length = + (strcmp(phy_dl_config["Phich"]["PhichConfig"]["R8"]["phich_Duration"].GetString(), "normal") == 0) + ? SRSLTE_PHICH_NORM + : SRSLTE_PHICH_EXT; + cell.phich_resources = + (strcmp(phy_dl_config["Phich"]["PhichConfig"]["R8"]["phich_Resource"].GetString(), "one") == 0) + ? SRSLTE_PHICH_R_1 + : SRSLTE_PHICH_R_1_6; + log->info("DL EARFCN is %d with n_prb=%d\n", earfcn.GetInt(), cell.nof_prb); + + const Value& ref_power = + document["Request"]["Cell"]["AddOrReconfigure"]["Basic"]["InitialCellPower"]["MaxReferencePower"]; + assert(ref_power.IsInt()); + + // That is the cellId or name that the testsuites uses to refer to a specific cell + const Value& cell_name = document["Common"]["CellId"]; + assert(cell_name.IsString()); + + // Now configure cell + syssim->set_cell_config(cell_name.GetString(), earfcn.GetInt(), cell, ref_power.GetInt()); + + // Pull out SIBs and send to syssim + uint16_t consumed_bytes = 0; + const uint8_t* payload_ptr = payload; + while (consumed_bytes < len) { + uint16_t tb_len = ((uint16_t)payload_ptr[0] << 8) | payload_ptr[1]; + payload_ptr += 2; + + unique_byte_buffer_t sib = pool_allocate_blocking; + memcpy(sib->msg, payload_ptr, tb_len); + payload_ptr += tb_len; + sib->N_bytes = tb_len; + + // Push to main component + log->info_hex(sib->msg, sib->N_bytes, "Received BCCH DL-SCH\n"); + syssim->add_bcch_pdu(std::move(sib)); + + consumed_bytes = payload_ptr - payload; + } + + // Create response for template car_CellConfig_CNF(CellId_Type p_CellId) + std::string resp = ttcn3_helpers::get_basic_sys_req_cnf(cell_name.GetString(), "Cell"); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } + } + + void handle_request_cell_active(Document& document, const uint8_t* payload, const uint16_t len) + { + // Create response for template car_CellConfig_CNF(CellId_Type p_CellId) + std::string cell_id = document["Common"]["CellId"].GetString(); + + std::string resp = ttcn3_helpers::get_basic_sys_req_cnf(cell_id, "Cell"); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } + + void handle_request_cell(Document& document, const uint8_t* payload, const uint16_t len) + { + // get Cmd and make sure it has MMI + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + // Handle cell creation + if (document["Request"]["Cell"].HasMember("AddOrReconfigure")) { + if (document["Request"]["Cell"]["AddOrReconfigure"].HasMember("Basic")) { + // basic information for a cell (e.g. broadcasting) + handle_request_cell_basic(document, payload, len); + } else if (document["Request"]["Cell"]["AddOrReconfigure"].HasMember("Active")) { + // additional configuration for active cell (i.e. cell being capable to receive RACH preamble) + handle_request_cell_active(document, payload, len); + } + } else if (document["Request"]["Cell"].HasMember("Release")) { + log->info("Received cell release command\n"); + // do nothing more + } + } + + void handle_request_l1_mac_ind_ctrl(Document& document) + { + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + const Value& cell_id = a["CellId"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + // check request + const Value& req = document["Request"]; + assert(req.HasMember("L1MacIndCtrl")); + + const Value& mac_ind_ctrl = req["L1MacIndCtrl"]; + + if (mac_ind_ctrl.HasMember("HarqError")) { + assert(mac_ind_ctrl["HarqError"].IsString()); + bool harq_error = (strcmp(mac_ind_ctrl["HarqError"].GetString(), "enable") == 0) ? true : false; + log->info("Setting HarqError to %s\n", harq_error ? "True" : "False"); + } + + if (ttcn3_helpers::requires_confirm(document)) { + std::string resp = ttcn3_helpers::get_basic_sys_req_cnf(cell_id.GetString(), "L1MacIndCtrl"); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } + } + + void handle_request_radio_bearer_list(Document& document) + { + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + const Value& cell_id = a["CellId"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + // check request + const Value& req = document["Request"]; + assert(req.HasMember("RadioBearerList")); + + const Value& bearers = req["RadioBearerList"]; + assert(bearers.IsArray()); + + // iterate over all bearers and configure them + for (Value::ConstValueIterator itr = bearers.Begin(); itr != bearers.End(); ++itr) { + assert(itr->HasMember("Id")); + const Value& id = (*itr)["Id"]; + if (id.HasMember("Srb")) { + const Value& config = (*itr)["Config"]; + if (config.HasMember("AddOrReconfigure")) { + uint32_t lcid = id["Srb"].GetInt(); + if (lcid > 0) { + log->info("Configure SRB%d\n", lcid); + pdcp_config_t pdcp_cfg = {.bearer_id = static_cast(lcid), + .rb_type = PDCP_RB_IS_SRB, + .tx_direction = SECURITY_DIRECTION_DOWNLINK, + .rx_direction = SECURITY_DIRECTION_UPLINK, + .sn_len = PDCP_SN_LEN_5}; + syssim->add_srb(lcid, pdcp_cfg); + } + } else if (config.HasMember("Release")) { + log->info("Releasing SRB%d\n", id["Srb"].GetInt()); + uint32_t lcid = id["Srb"].GetInt(); + syssim->del_srb(lcid); + } else { + log->error("Unknown config.\n"); + } + } else if (id.HasMember("Drb")) { + log->info("Configure DRB%d\n", id["Drb"].GetInt()); + } + + // FIXME: actually do configuration + } + + std::string resp = ttcn3_helpers::get_basic_sys_req_cnf(cell_id.GetString(), "RadioBearerList"); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } + + void handle_request_cell_attenuation_list(Document& document) + { + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + const Value& cell_id = a["CellId"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + // check request + const Value& req = document["Request"]; + assert(req.HasMember("CellAttenuationList")); + + const Value& cells = req["CellAttenuationList"]; + assert(cells.IsArray()); + + // iterate over all bearers and configure them + for (Value::ConstValueIterator itr = cells.Begin(); itr != cells.End(); ++itr) { + assert(itr->HasMember("CellId")); + const Value& id = (*itr)["CellId"]; + + assert(itr->HasMember("Attenuation")); + const Value& att = (*itr)["Attenuation"]; + + float att_value = 0; + if (att.HasMember("Value")) { + att_value = att["Value"].GetInt(); + } else if (att.HasMember("Off")) { + // is there other values than Off=True? + assert(att["Off"].GetBool() == true); + if (att["Off"].GetBool() == true) { + // use high attenuation value (-145dB RX power as per TS 36.508 Sec 6.2.2.1-1 is a non-suitable Off cell) + att_value = 90.0; + } + } + + log->info("Configuring attenuation of %s to %.2f dB\n", id.GetString(), att_value); + syssim->set_cell_attenuation(id.GetString(), att_value); + } + + std::string resp = ttcn3_helpers::get_basic_sys_req_cnf(cell_id.GetString(), "CellAttenuationList"); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } + + void handle_request_pdcp_count(Document& document) + { + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + const Value& cell_id = a["CellId"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + // check request + const Value& req = document["Request"]; + assert(req.HasMember("PdcpCount")); + + const Value& pdcp_count = req["PdcpCount"]; + assert(pdcp_count.HasMember("Get")); + + const Value& get = pdcp_count["Get"]; + assert(get.HasMember("AllRBs")); + + // prepare response to SS + std::vector bearers; + ttcn3_helpers::pdcp_count_t srb1; + srb1.rb_is_srb = true; + srb1.rb_id = 1; + srb1.dl_value = 0; + srb1.ul_value = 1; + bearers.push_back(srb1); + + std::string resp = ttcn3_helpers::get_pdcp_count_response(cell_id.GetString(), bearers); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } + + void handle_request_as_security(Document& document) + { + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + const Value& cell_id = a["CellId"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + const Value& config_flag = b["CnfFlag"]; + assert(config_flag.IsBool()); + + // check request + const Value& req = document["Request"]; + assert(req.HasMember("AS_Security")); + + // check AS security start + const Value& as_sec = req["AS_Security"]; + if (as_sec.HasMember("StartRestart")) { + // get integrity algo + srslte::INTEGRITY_ALGORITHM_ID_ENUM integ_algo = {}; + std::string int_algo_string = as_sec["StartRestart"]["Integrity"]["Algorithm"].GetString(); + if (int_algo_string == "eia0") { + integ_algo = srslte::INTEGRITY_ALGORITHM_ID_EIA0; + } else if (int_algo_string == "eia1") { + integ_algo = srslte::INTEGRITY_ALGORITHM_ID_128_EIA1; + } else if (int_algo_string == "eia2") { + integ_algo = srslte::INTEGRITY_ALGORITHM_ID_128_EIA2; + } else { + log->error("Unsupported integrity algorithm %s\n", int_algo_string.c_str()); + } + + // get integrity key + std::string integ_key_string = as_sec["StartRestart"]["Integrity"]["KRRCint"].GetString(); + std::array k_rrc_int = get_key_from_string(integ_key_string); + log->debug_hex(k_rrc_int.data(), k_rrc_int.size(), "K_rrc_int"); + + // get enc algo + srslte::CIPHERING_ALGORITHM_ID_ENUM cipher_algo = {}; + std::string cipher_algo_string = as_sec["StartRestart"]["Ciphering"]["Algorithm"].GetString(); + if (cipher_algo_string == "eea0") { + cipher_algo = srslte::CIPHERING_ALGORITHM_ID_EEA0; + } else if (int_algo_string == "eea1") { + cipher_algo = srslte::CIPHERING_ALGORITHM_ID_128_EEA1; + } else if (int_algo_string == "eea2") { + cipher_algo = srslte::CIPHERING_ALGORITHM_ID_128_EEA2; + } else { + log->error("Unsupported ciphering algorithm %s\n", cipher_algo_string.c_str()); + } + + // get cipher key + std::string cipher_key_string = as_sec["StartRestart"]["Ciphering"]["KRRCenc"].GetString(); + std::array k_rrc_enc = get_key_from_string(cipher_key_string); + log->debug_hex(k_rrc_enc.data(), k_rrc_enc.size(), "K_rrc_enc"); + + // get UP enc key + std::string up_enc_key_string = as_sec["StartRestart"]["Ciphering"]["KUPenc"].GetString(); + std::array k_up_enc = get_key_from_string(up_enc_key_string); + log->debug_hex(k_up_enc.data(), k_up_enc.size(), "K_UP_enc"); + + // get LCID + uint32_t lcid = 0; + if (as_sec["StartRestart"]["Ciphering"].HasMember("ActTimeList")) { + const Value& act_time_list = as_sec["StartRestart"]["Ciphering"]["ActTimeList"]; + if (act_time_list.IsArray()) { + for (Value::ConstValueIterator itr = act_time_list.Begin(); itr != act_time_list.End(); ++itr) { + if (itr->HasMember("RadioBearerId") && (*itr)["RadioBearerId"].HasMember("Srb")) { + lcid = (*itr)["RadioBearerId"]["Srb"].GetInt(); + } + } + } + } + + // configure SS to use AS security + syssim->set_as_security(lcid, k_rrc_enc, k_rrc_int, k_up_enc, cipher_algo, integ_algo); + } + + if (config_flag.GetBool() == true) { + std::string resp = ttcn3_helpers::get_basic_sys_req_cnf(cell_id.GetString(), "AS_Security"); + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } else { + log->info("Skipping response for AS_Security message.\n"); + } + } + + std::array get_key_from_string(const std::string& str) + { + std::array key = {}; + if (str.size() == 128) { + for (int i = 0; i < 16; i++) { + std::string byte_string(str, i * 8, 8); + key.at(i + 16) = std::stoul(byte_string, 0, 2); + } + } + return key; + } + + void handle_request_enquire_timing(Document& document) + { + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + const Value& cell_id = a["CellId"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + // check request + const Value& req = document["Request"]; + assert(req.HasMember("EnquireTiming")); + + std::string resp = + ttcn3_helpers::get_sys_req_cnf_with_time(cell_id.GetString(), "EnquireTiming", syssim->get_tti()); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } + + void handle_request_paging(Document& document, const uint8_t* payload, const uint16_t len) + { + const Value& a = document["Common"]; + + assert(a.HasMember("CellId")); + const Value& cell_id = a["CellId"]; + + // check cnf flag + assert(a.HasMember("ControlInfo")); + const Value& b = a["ControlInfo"]; + assert(b.HasMember("CnfFlag")); + + // check request + const Value& req = document["Request"]; + assert(req.HasMember("Paging")); + + // Pack payload into + // Inform SYSSIM about paging + + uint16_t consumed_bytes = 0; + const uint8_t* payload_ptr = payload; + + uint16_t tb_len = ((uint16_t)payload_ptr[0] << 8) | payload_ptr[1]; + payload_ptr += 2; + + unique_byte_buffer_t pch = pool_allocate_blocking; + memcpy(pch->msg, payload_ptr, tb_len); + payload_ptr += tb_len; + pch->N_bytes = tb_len; + + // Push to main component + log->info_hex(pch->msg, pch->N_bytes, "Received PCH DL-SCH\n"); + syssim->add_pch_pdu(std::move(pch)); + + if (ttcn3_helpers::requires_confirm(document)) { + std::string resp = ttcn3_helpers::get_sys_req_cnf_with_time(cell_id.GetString(), "Paging", syssim->get_tti()); + + log->info("Sending %s to tester (%zd B)\n", resp.c_str(), resp.length()); + srslte_netsource_write(&net_source, (char*)resp.c_str(), resp.length()); + } else { + log->info("Skipping response for Paging message.\n"); + } + } + + void run_thread() + { + if (!initialized) { + fprintf(stderr, "SYS interface not initialized. Exiting.\n"); + exit(-1); + } + + // open TCP socket + if (srslte_netsource_init(&net_source, net_ip.c_str(), net_port, SRSLTE_NETSOURCE_TCP)) { + fprintf(stderr, "Error creating input TCP socket at port %d\n", net_port); + exit(-1); + } + + log->info("Listening on %s:%d for incoming connections ..\n", net_ip.c_str(), net_port); + + running = true; + + int n; + while (run_enable) { + log->debug("Reading from SYS port ..\n"); + n = srslte_netsource_read(&net_source, rx_buf->begin(), RX_BUF_SIZE); + if (n > 0) { + rx_buf->at(n) = '\0'; + + log->debug("Received %d B from remote.\n", n); + + // Chop incoming msg, first two bytes are length of the JSON + // (see IPL4_EUTRA_SYSTEM_Definitions.ttcn + uint16_t json_len = ((uint16_t)rx_buf->at(0) << 8) | rx_buf->at(1); + + // Copy JSON from received buffer and null-terminate + char json[json_len + 1]; + memcpy(json, &rx_buf->at(2), json_len); + json[json_len] = '\0'; + + // The data part after the JSON starts right here but handling + // is done in the respective functions + uint16_t rx_buf_offset = json_len + 2; + + Document document; + if (document.Parse(json).HasParseError()) { + log->error_hex((uint8*)json, json_len, "Error parsing incoming data.\n"); + break; + } + assert(document.IsObject()); + + // Pretty-print + StringBuffer buffer; + PrettyWriter writer(buffer); + document.Accept(writer); + log->info("Received %d bytes\n%s\n", json_len, (char*)buffer.GetString()); + + // check for common + assert(document.HasMember("Common")); + assert(document["Common"].IsObject()); + + // Check for request type + assert(document.HasMember("Request")); + assert(document["Request"].IsObject()); + + // Get request type + const Value& request = document["Request"]; + if (request.HasMember("Cell")) { + log->info("Received Cell request.\n"); + handle_request_cell(document, &rx_buf->at(rx_buf_offset), n - rx_buf_offset); + } else if (request.HasMember("L1MacIndCtrl")) { + log->info("Received L1MacIndCtrl request.\n"); + handle_request_l1_mac_ind_ctrl(document); + } else if (request.HasMember("RadioBearerList")) { + log->info("Received RadioBearerList request.\n"); + handle_request_radio_bearer_list(document); + } else if (request.HasMember("CellAttenuationList")) { + log->info("Received CellAttenuationList request.\n"); + handle_request_cell_attenuation_list(document); + } else if (request.HasMember("PdcpCount")) { + log->info("Received PdcpCount request.\n"); + handle_request_pdcp_count(document); + } else if (request.HasMember("AS_Security")) { + log->info("Received AS_Security request.\n"); + handle_request_as_security(document); + } else if (request.HasMember("EnquireTiming")) { + log->info("Received EnquireTiming request.\n"); + handle_request_enquire_timing(document); + } else if (request.HasMember("Paging")) { + log->info("Received Paging request.\n"); + handle_request_paging(document, &rx_buf->at(rx_buf_offset), n - rx_buf_offset); + } else { + log->error("Received unknown request.\n"); + } + } else { + log->error("Error receiving from network\n"); + } + } + running = false; + + srslte_netsource_free(&net_source); + } + + phy_interface_syssim* phy = nullptr; + syssim_interface* syssim = nullptr; + byte_buffer_pool* pool = nullptr; +}; + +#endif // SRSUE_TTCN3_SYS_INTERFACE_H \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_syssim.h b/srsue/test/ttcn3/hdr/ttcn3_syssim.h new file mode 100644 index 000000000..d6b55d447 --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_syssim.h @@ -0,0 +1,885 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_SYSSIM_H +#define SRSUE_TTCN3_SYSSIM_H + +#include "dut_utils.h" +#include "srslte/common/netsource_handler.h" +#include "srslte/common/pdu_queue.h" +#include "srslte/upper/pdcp.h" +#include "srslte/upper/rlc.h" +#include "ttcn3_ip_ctrl_interface.h" +#include "ttcn3_ip_sock_interface.h" +#include "ttcn3_srb_interface.h" +#include "ttcn3_sys_interface.h" +#include "ttcn3_ue.h" +#include "ttcn3_ut_interface.h" +#include +#include + +#define TTCN3_CRNTI (0x1001) + +class ttcn3_syssim : public thread, + public syssim_interface_phy, + public syssim_interface, + public rrc_interface_rlc, + public rlc_interface_pdcp, + public rrc_interface_pdcp, + public srslte::pdu_queue::process_callback +{ +public: + ttcn3_syssim(srslte::logger_file* logger_file_) : + mac_msg_ul(20, &ss_mac_log), + mac_msg_dl(20, &ss_mac_log), + timers(8), + pdus(128), + logger(logger_file_), + logger_file(logger_file_), + pool(byte_buffer_pool::get_instance()), + thread("TTCN3_SYSSIM"), + rlc(&ss_rlc_log), + pdcp(&ss_pdcp_log){}; + + ~ttcn3_syssim(){}; + + void init(const all_args_t& args_) + { + args = args_; + + // Make sure to get SS logging as well + if (args.log.filename == "stdout") { + logger = &logger_stdout; + } + + // init and configure logging + log.init("SYS-SIM", logger); + ut_log.init("UT ", logger); + sys_log.init("SYS ", logger); + ip_sock_log.init("IP_SOCK", logger); + ip_ctrl_log.init("IP_CTRL", logger); + srb_log.init("SRB ", logger); + ss_mac_log.init("SS-MAC ", logger); + ss_rlc_log.init("SS-RLC ", logger); + ss_pdcp_log.init("SS-PDCP", logger); + + log.set_level(args.log.all_level); + ut_log.set_level(args.log.all_level); + sys_log.set_level(args.log.all_level); + ip_sock_log.set_level(args.log.all_level); + ip_ctrl_log.set_level(args.log.all_level); + srb_log.set_level(args.log.all_level); + ss_mac_log.set_level(args.log.all_level); + ss_rlc_log.set_level(args.log.all_level); + ss_pdcp_log.set_level(args.log.all_level); + + log.set_hex_limit(args.log.all_hex_limit); + ut_log.set_hex_limit(args.log.all_hex_limit); + sys_log.set_hex_limit(args.log.all_hex_limit); + ip_sock_log.set_hex_limit(args.log.all_hex_limit); + ip_ctrl_log.set_hex_limit(args.log.all_hex_limit); + srb_log.set_hex_limit(args.log.all_hex_limit); + ss_mac_log.set_hex_limit(args.log.all_hex_limit); + ss_rlc_log.set_hex_limit(args.log.all_hex_limit); + ss_pdcp_log.set_hex_limit(args.log.all_hex_limit); + + // init system interfaces to tester + ut.init(this, &ut_log, "0.0.0.0", 2222); + sys.init(this, &sys_log, "0.0.0.0", 2223); + ip_sock.init(&ip_sock_log, "0.0.0.0", 2224); + ip_ctrl.init(&ip_ctrl_log, "0.0.0.0", 2225); + srb.init(this, &srb_log, "0.0.0.0", 2226); + + ut.start(-2); + sys.start(-2); + ip_sock.start(-2); + ip_ctrl.start(-2); + srb.start(-2); + + pdus.init(this, &log); + rlc.init(&pdcp, this, &timers, 0 /* RB_ID_SRB0 */); + pdcp.init(&rlc, this, nullptr); + } + + void stop() + { + + running = false; + + if (ue != NULL) { + ue->stop(); + } + + // Stopping system interface + ut.stop(); + sys.stop(); + ip_sock.stop(); + ip_ctrl.stop(); + srb.stop(); + } + + void reset() + { + rlc.reset(); + pdcp.reset(); + cells.clear(); + pcell = {}; + } + + // Called from UT before starting testcase + void tc_start(const char* name) + { + if (ue == nullptr) { + // strip testsuite name + std::string tc_name = get_tc_name(name); + + // Make a copy of the UE args for this run + all_args_t local_args = args; + + // set up logging + if (args.log.filename == "stdout") { + logger = &logger_stdout; + } else { + logger_file->init(get_filename_with_tc_name(local_args.log.filename, run_id, tc_name).c_str(), -1); + logger = logger_file; + } + + log.info("Initializing UE ID=%d for TC=%s\n", run_id, tc_name.c_str()); + log.console("Initializing UE ID=%d for TC=%s\n", run_id, tc_name.c_str()); + + // Patch UE config + local_args.stack.pcap.filename = get_filename_with_tc_name(args.stack.pcap.filename, run_id, tc_name); + local_args.stack.pcap.nas_filename = get_filename_with_tc_name(args.stack.pcap.nas_filename, run_id, tc_name); + + // bring up UE + ue = std::unique_ptr(new ttcn3_ue()); + if (ue->init(local_args, logger, this, tc_name)) { + ue->stop(); + ue.reset(nullptr); + std::string err("Couldn't initialize UE.\n"); + log.error("%s\n", err.c_str()); + log.console("%s\n", err.c_str()); + return; + } + + // Start simulator thread + running = true; + start(); + } else { + log.error("UE hasn't been deallocated properly because TC didn't finish correctly.\n"); + log.console("UE hasn't been deallocated properly because TC didn't finish correctly.\n"); + } + } + + void tc_end() + { + if (ue != NULL) { + // ask periodic thread to stop + running = false; + + log.info("Deinitializing UE ID=%d\n", run_id); + log.console("Deinitializing UE ID=%d\n", run_id); + ue->stop(); + + // wait until SS main thread has terminated before resetting UE + wait_thread_finish(); + ue.reset(); + + // Reset SS' RLC and PDCP + reset(); + + logger_file->stop(); + + run_id++; + } else { + log.error("UE is not allocated. Nothing needs to be done.\n"); + log.console("UE is not allocated. Nothing needs to be done.\n"); + } + } + + void power_off_ue() + { + // only return after new UE instance is up and running + } + + void switch_on_ue() + { + if (ue != nullptr) { + log.info("Switching on UE ID=%d\n", run_id); + log.console("Switching on UE ID=%d\n", run_id); + + // Trigger attach procedure + ue->switch_on(); + } else { + log.error("UE not initialized. Can't switch UE on.\n"); + } + } + + void switch_off_ue() + { + if (ue != nullptr) { + log.info("Switching off UE ID=%d\n", run_id); + log.console("Switching off UE ID=%d\n", run_id); + ue->switch_off(); + } else { + log.error("UE not initialized. Can't switch UE off.\n"); + } + } + + // Interface for PHY + void prach_indication(uint32_t preamble_index_, const uint32_t& cell_id) + { + // store TTI for providing UL grant for Msg3 transmission + prach_tti = tti; + prach_preamble_index = preamble_index_; + + // update active pcell (chosen by UE) in syssim + for (auto& cell : cells) { + if (cell.cell.id == cell_id) { + pcell = cell; + break; + } + } + } + + void send_rar(uint32_t preamble_index) + { + log.info("Sending RAR for RAPID=%d\n", preamble_index); + + // Prepare RAR grant + uint8_t grant_buffer[64] = {}; + srslte_dci_rar_grant_t rar_grant = {}; + rar_grant.tpc_pusch = 3; + srslte_dci_rar_pack(&rar_grant, grant_buffer); + + // Create MAC PDU and add RAR subheader + srslte::rar_pdu rar_pdu; + rar_buffer.clear(); + + const int rar_pdu_len = 64; + rar_pdu.init_tx(&rar_buffer, rar_pdu_len); + rar_pdu.set_backoff(11); // Backoff of 480ms to prevent UE from PRACHing too fast + if (rar_pdu.new_subh()) { + rar_pdu.get()->set_rapid(preamble_index); + rar_pdu.get()->set_ta_cmd(0); + rar_pdu.get()->set_temp_crnti(crnti); + rar_pdu.get()->set_sched_grant(grant_buffer); + } + rar_pdu.write_packet(rar_buffer.msg); + rar_buffer.N_bytes = rar_pdu_len; + + // Prepare grant and pass all to MAC + mac_interface_phy_lte::mac_grant_dl_t dl_grant = {}; + dl_grant.pid = get_pid(tti); + dl_grant.rnti = 0x1; // must be a valid RAR-RNTI + dl_grant.tb[0].tbs = rar_buffer.N_bytes; + dl_grant.tb[0].ndi = get_ndi_for_new_dl_tx(tti); + + // send grant and pass payload to TB data (grant contains length) + ue->new_tb(dl_grant, rar_buffer.msg); + + // reset last PRACH transmission tti + prach_tti = -1; + } + + void send_msg3_grant() + { + log.info("Sending Msg3 grant for C-RNTI=%d\n", crnti); + mac_interface_phy_lte::mac_grant_ul_t ul_grant = {}; + + ul_grant.tb.tbs = 32; + ul_grant.tb.ndi_present = true; + ul_grant.tb.ndi = get_ndi_for_new_ul_tx(tti); + ul_grant.rnti = crnti; + ul_grant.pid = get_pid(tti); + + ue->new_grant_ul(ul_grant); + } + + void sr_req(uint32_t tti_tx) + { + log.info("Received SR from PHY\n"); + + // Provide new UL grant to UE + mac_interface_phy_lte::mac_grant_ul_t ul_grant = {}; + ul_grant.tb.tbs = 100; // FIXME: reasonable size? + ul_grant.tb.ndi_present = true; + ul_grant.tb.ndi = get_ndi_for_new_ul_tx(tti); + ul_grant.rnti = crnti; + ul_grant.pid = get_pid(tti); + + ue->new_grant_ul(ul_grant); + } + + void tx_pdu(const uint8_t* payload, const int len, const uint32_t tx_tti) + { + + if (payload == NULL) { + ss_mac_log.error("Received NULL as PDU payload. Dropping.\n"); + return; + } + + // Parse MAC + mac_msg_ul.init_rx(len, true); + mac_msg_ul.parse_packet((uint8_t*)payload); + + while (mac_msg_ul.next()) { + assert(mac_msg_ul.get()); + if (mac_msg_ul.get()->is_sdu()) { + // Route logical channel + ss_mac_log.info_hex(mac_msg_ul.get()->get_sdu_ptr(), + mac_msg_ul.get()->get_payload_size(), + "PDU: rnti=0x%x, lcid=%d, %d bytes\n", + 0xdead, + mac_msg_ul.get()->get_sdu_lcid(), + mac_msg_ul.get()->get_payload_size()); + + // Push PDU to our own RLC (needed to handle status reporting, etc. correctly + ss_mac_log.info_hex(mac_msg_ul.get()->get_sdu_ptr(), + mac_msg_ul.get()->get_payload_size(), + "Route PDU to LCID=%d (%d B)\n", + mac_msg_ul.get()->get_sdu_lcid(), + mac_msg_ul.get()->get_payload_size()); + rlc.write_pdu( + mac_msg_ul.get()->get_sdu_lcid(), mac_msg_ul.get()->get_sdu_ptr(), mac_msg_ul.get()->get_payload_size()); + + // Save contention resolution if lcid == 0 + if (mac_msg_ul.get()->get_sdu_lcid() == 0) { + int nbytes = srslte::sch_subh::MAC_CE_CONTRES_LEN; + if (mac_msg_ul.get()->get_payload_size() >= (uint32_t)nbytes) { + uint8_t* ue_cri_ptr = (uint8_t*)&conres_id; + uint8_t* pkt_ptr = mac_msg_ul.get()->get_sdu_ptr(); // Warning here: we want to include the + for (int i = 0; i < nbytes; i++) { + ue_cri_ptr[nbytes - i - 1] = pkt_ptr[i]; + } + ss_mac_log.info_hex(ue_cri_ptr, nbytes, "Contention resolution ID:\n"); + } else { + ss_mac_log.error("Received CCCH UL message of invalid size=%d bytes\n", + mac_msg_ul.get()->get_payload_size()); + } + } + } + } + mac_msg_ul.reset(); + + /* Process CE after all SDUs because we need to update BSR after */ + bool bsr_received = false; + while (mac_msg_ul.next()) { + assert(mac_msg_ul.get()); + if (!mac_msg_ul.get()->is_sdu()) { + // Process MAC Control Element + bsr_received |= process_ce(mac_msg_ul.get()); + } + } + } + + bool process_ce(srslte::sch_subh* subh) + { + + uint16_t rnti = dl_rnti; + + uint32_t buff_size[4] = {0, 0, 0, 0}; + float phr = 0; + int32_t idx = 0; + uint16_t old_rnti = 0; + bool is_bsr = false; + switch (subh->ce_type()) { + case srslte::sch_subh::PHR_REPORT: + phr = subh->get_phr(); + ss_mac_log.info("CE: Received PHR from rnti=0x%x, value=%.0f\n", rnti, phr); +#if 0 + //sched->ul_phr(rnti, (int) phr); + //metrics_phr(phr); +#endif + break; + case srslte::sch_subh::CRNTI: + old_rnti = subh->get_c_rnti(); + ss_mac_log.info("CE: Received C-RNTI from temp_rnti=0x%x, rnti=0x%x\n", rnti, old_rnti); +#if 0 + if (sched->ue_exists(old_rnti)) { + rrc->upd_user(rnti, old_rnti); + rnti = old_rnti; + } else { + Error("Updating user C-RNTI: rnti=0x%x already released\n", old_rnti); + } +#endif + break; + case srslte::sch_subh::TRUNC_BSR: + case srslte::sch_subh::SHORT_BSR: + idx = subh->get_bsr(buff_size); + if (idx == -1) { + ss_mac_log.error("Invalid Index Passed to lc groups\n"); + break; + } +#if 0 + for (uint32_t i=0;iul_bsr(rnti, lc_groups[idx][i], buff_size[idx]); + + } +#endif + ss_mac_log.info("CE: Received %s BSR rnti=0x%x, lcg=%d, value=%d\n", + subh->ce_type() == srslte::sch_subh::SHORT_BSR ? "Short" : "Trunc", + rnti, + idx, + buff_size[idx]); + is_bsr = true; + break; + case srslte::sch_subh::LONG_BSR: + subh->get_bsr(buff_size); +#if 0 + for (idx=0;idx<4;idx++) { + for (uint32_t i=0;iul_bsr(rnti, lc_groups[idx][i], buff_size[idx]); + } + } +#endif + is_bsr = true; + ss_mac_log.info("CE: Received Long BSR rnti=0x%x, value=%d,%d,%d,%d\n", + rnti, + buff_size[0], + buff_size[1], + buff_size[2], + buff_size[3]); + break; + case srslte::sch_subh::PADDING: + ss_mac_log.debug("CE: Received padding for rnti=0x%x\n", rnti); + break; + default: + ss_mac_log.error("CE: Invalid lcid=0x%x\n", subh->ce_type()); + break; + } + return is_bsr; + } + + uint32_t get_pid(const uint32_t tti) { return tti % (2 * FDD_HARQ_DELAY_MS); } + + bool get_ndi_for_new_ul_tx(const uint32_t tti_) + { + // toggle NDI to always create new Tx + const uint32_t pid = get_pid(tti_); + last_ul_ndi[pid] = !last_ul_ndi[pid]; + log.info("UL-PID=%d NDI=%s\n", pid, last_ul_ndi[pid] ? "1" : "0"); + return last_ul_ndi[pid]; + } + + bool get_ndi_for_new_dl_tx(const uint32_t tti_) + { + // toggle NDI to always create new Tx + const uint32_t pid = get_pid(tti_); + last_dl_ndi[pid] = !last_dl_ndi[pid]; + log.info("DL-PID=%d NDI=%s\n", pid, last_dl_ndi[pid] ? "1" : "0"); + return last_dl_ndi[pid]; + } + + void run_thread() + { + uint32_t sib_idx = 0; + + while (running) { + log.debug("SYSSIM-TTI=%d\n", tti); + ue->set_current_tti(tti); + dl_rnti = ue->get_dl_sched_rnti(tti); + + if (SRSLTE_RNTI_ISSI(dl_rnti)) { + // deliver SIBs one after another + mac_interface_phy_lte::mac_grant_dl_t dl_grant = {}; + dl_grant.pid = get_pid(tti); + dl_grant.rnti = dl_rnti; + dl_grant.tb[0].tbs = sibs[sib_idx]->N_bytes; + dl_grant.tb[0].ndi = get_ndi_for_new_dl_tx(tti); + ue->new_tb(dl_grant, sibs[sib_idx]->msg); + + sib_idx = (sib_idx + 1) % sibs.size(); + } else if (SRSLTE_RNTI_ISRAR(dl_rnti)) { + if (prach_tti != -1) { + rar_tti = (prach_tti + 3) % 10240; + if (tti == rar_tti) { + send_rar(prach_preamble_index); + } + } + } else if (SRSLTE_RNTI_ISPA(dl_rnti)) { + log.debug("Searching for paging RNTI\n"); + // PCH will be triggered from SYSSIM after receiving Paging + } else if (SRSLTE_RNTI_ISUSER(dl_rnti)) { + // check if this is for contention resolution after PRACH/RAR + if (dl_rnti == crnti) { + log.debug("Searching for C-RNTI=%d\n", crnti); + + if (rar_tti != -1) { + msg3_tti = (rar_tti + 3) % 10240; + if (tti == msg3_tti) { + send_msg3_grant(); + rar_tti = -1; + } + } + } + if (dl_rnti != 0) { + log.debug("Searching for RNTI=%d\n", dl_rnti); + + // look for DL data to be send in each bearer and provide grant accordingly + for (int lcid = 0; lcid < SRSLTE_N_RADIO_BEARERS; lcid++) { + uint32_t buf_state = rlc.get_buffer_state(lcid); + if (buf_state > 0) { + log.debug("LCID=%d, buffer_state=%d\n", lcid, buf_state); + const uint32_t mac_header_size = 10; // Add MAC header (10 B for all subheaders, etc) + if (tmp_rlc_buffer.get_tailroom() > (buf_state + mac_header_size)) { + uint32_t pdu_size = rlc.read_pdu(lcid, tmp_rlc_buffer.msg, buf_state); + tx_payload_buffer.clear(); + mac_msg_dl.init_tx(&tx_payload_buffer, pdu_size + mac_header_size, false); + + // check if this is Msg4 that needs to contain the contention resolution ID CE + if (msg3_tti != -1) { + if (lcid == 0) { + if (mac_msg_dl.new_subh()) { + if (mac_msg_dl.get()->set_con_res_id(conres_id)) { + log.info("CE: Added Contention Resolution ID=0x%lx\n", conres_id); + } else { + log.error("CE: Setting Contention Resolution ID CE\n"); + } + } else { + log.error("CE: Setting Contention Resolution ID CE. No space for a subheader\n"); + } + msg3_tti = -1; + } + } + + // Add payload + if (mac_msg_dl.new_subh()) { + int n = mac_msg_dl.get()->set_sdu(lcid, pdu_size, tmp_rlc_buffer.msg); + if (n == -1) { + log.error("Error while adding SDU (%d B) to MAC PDU\n", pdu_size); + mac_msg_dl.del_subh(); + } + } + + uint8_t* mac_pdu_ptr = mac_msg_dl.write_packet(&log); + if (mac_pdu_ptr != nullptr) { + log.info_hex(mac_pdu_ptr, mac_msg_dl.get_pdu_len(), "DL MAC PDU (%d B):\n", mac_msg_dl.get_pdu_len()); + + // Prepare MAC grant for CCCH + mac_interface_phy_lte::mac_grant_dl_t dl_grant = {}; + dl_grant.pid = get_pid(tti); + dl_grant.rnti = dl_rnti; + dl_grant.tb[0].tbs = mac_msg_dl.get_pdu_len(); + dl_grant.tb[0].ndi_present = true; + dl_grant.tb[0].ndi = get_ndi_for_new_dl_tx(tti); + + ue->new_tb(dl_grant, (const uint8_t*)mac_pdu_ptr); + } else { + log.error("Error writing DL MAC PDU\n"); + } + mac_msg_dl.reset(); + } else { + log.error("Can't fit RLC PDU into buffer (%d > %d)\n", buf_state, tmp_rlc_buffer.get_tailroom()); + } + } + } + // Check if we need to provide a UL grant as well + } + } else { + log.error("Not handling RNTI=%d\n", dl_rnti); + } + + usleep(1000); + tti = (tti + 1) % 10240; + } + + log.info("Leaving main thread.\n"); + log.console("Leaving main thread.\n"); + } + + uint32_t get_tti() { return tti; } + + void process_pdu(uint8_t* buff, uint32_t len, pdu_queue::channel_t channel) { log.info("%s\n", __PRETTY_FUNCTION__); } + + void set_cell_config(std::string name, uint32_t earfcn_, srslte_cell_t cell_, const float power) + { + // check if cell already exists + if (not syssim_has_cell(name)) { + // insert new cell + log.info("Adding cell %s with cellId=%d and power=%.2f dBm\n", name.c_str(), cell_.id, power); + syssim_cell_t cell = {}; + cell.name = name; + cell.cell = cell_; + cell.initial_power = power; + cell.earfcn = earfcn_; + cells.push_back(cell); + } else { + // cell is already there + log.info("Cell already there, reconfigure\n"); + } + + update_cell_map(); + } + + bool syssim_has_cell(std::string cell_name) + { + for (uint32_t i = 0; i < cells.size(); ++i) { + if (cells[i].name == cell_name) { + return true; + } + } + return false; + } + + void set_cell_attenuation(std::string cell_name, const float value) + { + if (not syssim_has_cell(cell_name)) { + log.error("Can't set cell power. Cell not found.\n"); + } + + // update cell's power + for (uint32_t i = 0; i < cells.size(); ++i) { + if (cells[i].name == cell_name) { + cells[i].attenuation = value; + break; + } + } + + update_cell_map(); + } + + void update_cell_map() + { + // Find cell with highest power and select as serving cell + if (ue != NULL) { + // convert syssim cell list to phy cell list + lte_ttcn3_phy::cell_list_t phy_cells; + for (uint32_t i = 0; i < cells.size(); ++i) { + lte_ttcn3_phy::cell_t phy_cell = {}; + phy_cell.info = cells[i].cell; + phy_cell.power = cells[i].initial_power - cells[i].attenuation; + phy_cell.earfcn = cells[i].earfcn; + log.debug("Configuring cell %d with PCI=%d with TxPower=%f\n", i, phy_cell.info.id, phy_cell.power); + phy_cells.push_back(phy_cell); + } + + // SYSSIM defines what cells the UE can connect to + ue->set_cell_map(phy_cells); + } else { + log.error("Can't configure cell. UE not initialized.\n"); + } + } + + void add_bcch_pdu(unique_byte_buffer_t pdu) { sibs.push_back(std::move(pdu)); } + + void add_ccch_pdu(unique_byte_buffer_t pdu) + { + // Add to SRB0 Tx queue + rlc.write_sdu(0, std::move(pdu)); + } + + void add_dcch_pdu(uint32_t lcid, unique_byte_buffer_t pdu) + { + // push to PDCP and create DL grant for it + log.info("Writing PDU (%d B) to LCID=%d\n", pdu->N_bytes, lcid); + pdcp.write_sdu(lcid, std::move(pdu)); + } + + void add_pch_pdu(unique_byte_buffer_t pdu) + { + log.info("Received PCH PDU (%d B)\n", pdu->N_bytes); + + // Prepare MAC grant for PCH + mac_interface_phy_lte::mac_grant_dl_t dl_grant = {}; + dl_grant.pid = get_pid(tti); + dl_grant.rnti = SRSLTE_PRNTI; + dl_grant.tb[0].tbs = pdu->N_bytes; + dl_grant.tb[0].ndi_present = true; + dl_grant.tb[0].ndi = get_ndi_for_new_dl_tx(tti); + ue->new_tb(dl_grant, (const uint8_t*)pdu->msg); + } + + srslte::timers::timer* timer_get(uint32_t timer_id) { return timers.get(timer_id); } + + uint32_t timer_get_unique_id() { return timers.get_unique_id(); } + + void timer_release_id(uint32_t timer_id) { timers.release_id(timer_id); } + + void step_timer() { timers.step_all(); } + + void add_srb(uint32_t lcid, pdcp_config_t pdcp_config) + { + pdcp.add_bearer(lcid, pdcp_config); + rlc.add_bearer(lcid, srslte::rlc_config_t::srb_config(lcid)); + } + + void del_srb(uint32_t lcid) + { + // Only delete SRB1/2 + if (lcid > 0) { + pdcp.del_bearer(lcid); + rlc.del_bearer(lcid); + } + + // Reset HARQ to generate new transmissions + if (lcid == 0) { + log.info("Resetting UL/DL NDI counters\n"); + memset(last_dl_ndi, 0, sizeof(last_dl_ndi)); + memset(last_ul_ndi, 0, sizeof(last_ul_ndi)); + } + } + + // RRC interface for PDCP, PDCP calls RRC to push RRC SDU + void write_pdu(uint32_t lcid, unique_byte_buffer_t pdu) + { + log.info_hex(pdu->msg, pdu->N_bytes, "RRC SDU received for LCID=%d (%d B)\n", lcid, pdu->N_bytes); + + // We don't handle RRC, prepend LCID + pdu->msg--; + *pdu->msg = lcid; + pdu->N_bytes++; + + // prepend pcell PCID + pdu->msg--; + *pdu->msg = static_cast(pcell.cell.id); + pdu->N_bytes++; + + // push content to Titan + srb.tx(std::move(pdu)); + } + + // Not supported right now + void write_pdu_bcch_bch(unique_byte_buffer_t pdu) { log.error("%s not implemented.\n", __FUNCTION__); } + void write_pdu_bcch_dlsch(unique_byte_buffer_t pdu) { log.error("%s not implemented.\n", __FUNCTION__); } + void write_pdu_pcch(unique_byte_buffer_t pdu) { log.error("%s not implemented.\n", __FUNCTION__); } + void write_pdu_mch(uint32_t lcid, unique_byte_buffer_t pdu) { log.error("%s not implemented.\n", __FUNCTION__); } + + void max_retx_attempted() { log.debug("max_retx_attempted\n"); } + + std::string get_rb_name(uint32_t lcid) + { + if (lcid < rb_id_vec.size()) { + return rb_id_vec.at(lcid); + } + return std::string("RB"); + }; + + void write_sdu(uint32_t lcid, unique_byte_buffer_t sdu, bool blocking = true) + { + log.info_hex(sdu->msg, sdu->N_bytes, "Received SDU on LCID=%d\n", lcid); + + uint8_t* mac_pdu_ptr; + mac_pdu_ptr = mac_msg_dl.write_packet(&log); + log.info_hex(mac_pdu_ptr, mac_msg_dl.get_pdu_len(), "DL MAC PDU:\n"); + + // Prepare MAC grant for CCCH + mac_interface_phy_lte::mac_grant_dl_t dl_grant = {}; + dl_grant.pid = get_pid(tti); + dl_grant.rnti = dl_rnti; + dl_grant.tb[0].tbs = mac_msg_dl.get_pdu_len(); + dl_grant.tb[0].ndi_present = true; + dl_grant.tb[0].ndi = get_ndi_for_new_dl_tx(tti); + + ue->new_tb(dl_grant, (const uint8_t*)mac_pdu_ptr); + } + + bool rb_is_um(uint32_t lcid) { return false; } + + int set_as_security(const uint32_t lcid, + std::array k_rrc_enc, + std::array k_rrc_int, + std::array k_up_enc, + const srslte::CIPHERING_ALGORITHM_ID_ENUM cipher_algo, + const srslte::INTEGRITY_ALGORITHM_ID_ENUM integ_algo) + { + pdcp.config_security(lcid, k_rrc_enc.data(), k_rrc_int.data(), k_up_enc.data(), cipher_algo, integ_algo); + pdcp.enable_integrity(lcid); + pdcp.enable_encryption(lcid); + return 0; + } + +private: + // SYS interface + ttcn3_ut_interface ut; + ttcn3_sys_interface sys; + ttcn3_ip_sock_interface ip_sock; + ttcn3_ip_ctrl_interface ip_ctrl; + ttcn3_srb_interface srb; + + // Logging stuff + srslte::logger_stdout logger_stdout; + srslte::logger_file* logger_file = nullptr; + srslte::logger* logger = nullptr; + srslte::log_filter log; + srslte::log_filter ut_log; + srslte::log_filter sys_log; + srslte::log_filter ip_sock_log; + srslte::log_filter ip_ctrl_log; + srslte::log_filter srb_log; + srslte::log_filter ss_mac_log; + srslte::log_filter ss_rlc_log; + srslte::log_filter ss_pdcp_log; + + all_args_t args = {}; + + bool running = false; + + srslte::byte_buffer_pool* pool = nullptr; + + // Simulator vars + unique_ptr ue = nullptr; + + uint32_t run_id = 0; + + std::vector sibs; + int32_t tti = 0; + int32_t prach_tti = -1; + int32_t rar_tti = -1; + int32_t msg3_tti = -1; + uint32_t prach_preamble_index = 0; + uint16_t dl_rnti = 0; + uint16_t crnti = TTCN3_CRNTI; + srslte::timers timers; + bool last_dl_ndi[2 * FDD_HARQ_DELAY_MS] = {}; + bool last_ul_ndi[2 * FDD_HARQ_DELAY_MS] = {}; + + // Map between the cellId (name) used by 3GPP test suite and srsLTE cell struct + typedef struct { + std::string name; + srslte_cell_t cell; + float initial_power; + float attenuation; + uint32_t earfcn; + } syssim_cell_t; + std::vector cells; + syssim_cell_t pcell = {}; + + srslte::pdu_queue pdus; + srslte::sch_pdu mac_msg_dl, mac_msg_ul; + + // buffer for DL transmissions + srslte::byte_buffer_t rar_buffer; + srslte::byte_buffer_t tmp_rlc_buffer; // Used to buffer RLC PDU + srslte::byte_buffer_t tx_payload_buffer; // Used to buffer final MAC PDU + + uint64_t conres_id = 0; + + // Simulator objects + srslte::rlc rlc; + srslte::pdcp pdcp; + + std::vector rb_id_vec = { + "SRB0", "SRB1", "SRB2", "DRB1", "DRB2", "DRB3", "DRB4", "DRB5", "DRB6", "DRB7", "DRB8"}; +}; + +#endif // SRSUE_TTCN3_SYSSIM_H \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_ue.h b/srsue/test/ttcn3/hdr/ttcn3_ue.h new file mode 100644 index 000000000..3dec9c5da --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_ue.h @@ -0,0 +1,150 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_UE_H +#define SRSUE_TTCN3_UE_H + +#include "lte_ttcn3_phy.h" +#include "srsue/hdr/stack/ue_stack_lte.h" +#include + +class ttcn3_ue : public phy_interface_syssim, public gw_interface_stack +{ +public: + ttcn3_ue() : run_id(0), logger(nullptr) {} + + virtual ~ttcn3_ue() {} + + int init(all_args_t args, srslte::logger* logger_, syssim_interface_phy* syssim_, const std::string tc_name_) + { + logger = logger_; + + // Init UE log + log.init("UE ", logger); + log.set_level(srslte::LOG_LEVEL_INFO); + + // Patch args + args.stack.nas.force_imsi_attach = true; + args.stack.nas.eia = "0,1,2"; + args.stack.nas.eea = "0,1,2"; + + // Configure default parameters + args.stack.usim.algo = "xor"; + args.stack.usim.imei = "356092040793011"; + args.stack.usim.imsi = "001010123456789"; // Anritsu test USIM + args.stack.usim.k = "000102030405060708090A0B0C0D0E0F"; // fixed as per TS 34.108 Sec. 8.2 + + args.stack.rrc.feature_group = 0xe6041000; + args.stack.rrc.ue_category_str = SRSLTE_UE_CATEGORY_DEFAULT; + args.stack.rrc.ue_category = atoi(args.stack.rrc.ue_category_str.c_str()); + args.stack.rrc.nof_supported_bands = 1; + args.stack.rrc.supported_bands[0] = 7; + args.stack.rrc.release = 8; + args.stack.rrc.mbms_service_id = -1; + + args.phy.dl_earfcn = "3400"; + args.rf.type = "none"; + args.stack.type = "lte"; + args.phy.type = "lte_ttcn3"; + args.phy.log.phy_level = "debug"; + + // Instantiate layers and stack together our UE + if (args.stack.type == "lte") { + stack = std::unique_ptr(new ue_stack_lte()); + if (!stack) { + log.console("Error creating LTE stack instance.\n"); + return SRSLTE_ERROR; + } + + phy = std::unique_ptr(new srsue::lte_ttcn3_phy(logger)); + if (!phy) { + log.console("Error creating LTE PHY instance.\n"); + return SRSLTE_ERROR; + } + } else { + log.console("Invalid stack type %s. Supported values are [lte].\n", args.stack.type.c_str()); + return SRSLTE_ERROR; + } + + // init layers + if (phy->init(args.phy, stack.get(), syssim_)) { + log.console("Error initializing PHY.\n"); + return SRSLTE_ERROR; + } + + if (stack->init(args.stack, logger, phy.get(), this)) { + log.console("Error initializing stack.\n"); + return SRSLTE_ERROR; + } + + return SRSLTE_SUCCESS; + } + + void stop() + { + // nothing to do here + } + + bool switch_on() { return stack->switch_on(); } + + bool switch_off() { return stack->switch_off(); } + + // The interface for SYSSIM + void set_cell_map(lte_ttcn3_phy::cell_list_t phy_cell_map) { phy->set_cell_map(phy_cell_map); } + + void new_grant_ul(const srsue::mac_interface_phy_lte::mac_grant_ul_t grant) { phy->new_grant_ul(grant); } + + void new_tb(const srsue::mac_interface_phy_lte::mac_grant_dl_t mac_grant, const uint8_t* data) + { + phy->new_tb(mac_grant, data); + } + + void set_current_tti(uint32_t tti) { phy->set_current_tti(tti); } + + uint16_t get_dl_sched_rnti(uint32_t tti) { return stack->get_dl_sched_rnti(tti); } + + // GW interface + void add_mch_port(uint32_t lcid, uint32_t port) {} + void write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu) {} + void write_pdu_mch(uint32_t lcid, srslte::unique_byte_buffer_t pdu) {} + int setup_if_addr(uint32_t lcid, uint8_t pdn_type, uint32_t ip_addr, uint8_t* ipv6_if_id, char* err_str) { return 0; } + int apply_traffic_flow_template(const uint8_t& eps_bearer_id, + const uint8_t& lcid, + const LIBLTE_MME_TRAFFIC_FLOW_TEMPLATE_STRUCT* tft) + { + return 0; + } + +private: + std::unique_ptr phy; + std::unique_ptr stack; + + // Generic logger members + srslte::logger* logger = nullptr; + srslte::log_filter log; // Own logger for UE + + all_args_t args; + srslte::byte_buffer_pool* pool = nullptr; + + uint32_t run_id; +}; + +#endif // SRSUE_TTCN3_UE_H \ No newline at end of file diff --git a/srsue/test/ttcn3/hdr/ttcn3_ut_interface.h b/srsue/test/ttcn3/hdr/ttcn3_ut_interface.h new file mode 100644 index 000000000..d30895393 --- /dev/null +++ b/srsue/test/ttcn3/hdr/ttcn3_ut_interface.h @@ -0,0 +1,170 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSUE_TTCN3_UT_INTERFACE_H +#define SRSUE_TTCN3_UT_INTERFACE_H + +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "srslte/common/log.h" +#include "srslte/common/netsource_handler.h" +#include "ttcn3_interfaces.h" + +using namespace rapidjson; + +// The UpperTester interface +class ttcn3_ut_interface : public netsource_handler +{ +public: + ttcn3_ut_interface() : netsource_handler("TTCN3_UT_IF") {} + ~ttcn3_ut_interface(){}; + + void init(syssim_interface* syssim_, srslte::log* log_, std::string net_ip_, uint32_t net_port_) + { + syssim = syssim_; + log = log_; + net_ip = net_ip_; + net_port = net_port_; + initialized = true; + log->debug("Initialized.\n"); + } + +private: + void run_thread() + { + if (!initialized) { + fprintf(stderr, "UT interface not initialized. Exiting.\n"); + exit(-1); + } + + // open TCP socket + if (srslte_netsource_init(&net_source, net_ip.c_str(), net_port, SRSLTE_NETSOURCE_TCP)) { + fprintf(stderr, "Error creating input TCP socket at port %d\n", net_port); + exit(-1); + } + + log->info("Listening on %s:%d for incoming connections ..\n", net_ip.c_str(), net_port); + + running = true; + + int n; + while (run_enable) { + log->debug("Reading from UT port ..\n"); + + n = srslte_netsource_read(&net_source, rx_buf->begin(), RX_BUF_SIZE); + if (n > 0) { + // Terminate + rx_buf->at(n) = '\0'; + + Document document; + if (document.Parse((char*)rx_buf->begin()).HasParseError()) { + log->error_hex(rx_buf->begin(), n, "Error parsing incoming data.\n"); + continue; + } + assert(document.IsObject()); + + // Pretty-print + StringBuffer buffer; + PrettyWriter writer(buffer); + document.Accept(writer); + log->info("Received %d bytes\n%s\n", n, (char*)buffer.GetString()); + + // check for command + assert(document.HasMember("Cmd")); + assert(document["Cmd"].IsObject()); + + // get Cmd + const Value& a = document["Cmd"]; + + if (a.HasMember("MMI")) { + assert(a.HasMember("MMI")); + + // get MMI and make sure it has another Cmd nested + const Value& mmi = a["MMI"]; + assert(mmi.HasMember("Cmd")); + + // get MMI cmd + const Value& mmi_cmd = mmi["Cmd"]; + assert(mmi_cmd.IsString()); + + // check for CnfRequired + assert(document.HasMember("CnfRequired")); + + if (strcmp(mmi_cmd.GetString(), "POWER_OFF") == 0) { + log->info("Received POWER_OFF command.\n"); + handle_power_off(document); + } else if (strcmp(mmi_cmd.GetString(), "SWITCH_ON") == 0) { + log->info("Received SWITCH_ON command.\n"); + syssim->switch_on_ue(); + } else if (strcmp(mmi_cmd.GetString(), "SWITCH_OFF") == 0) { + log->info("Received SWITCH_OFF command.\n"); + syssim->switch_off_ue(); + } else { + log->error("Received unknown command: %s\n", mmi_cmd.GetString()); + } + } else if (a.HasMember("AT")) { + log->error("AT commands not implemented.\n"); + } else if (a.HasMember("TC_START")) { + log->info("Received TC_START command.\n"); + const Value& cmd = a["TC_START"]; + assert(cmd.HasMember("Name")); + const Value& tc_name = cmd["Name"]; + syssim->tc_start(tc_name.GetString()); + } else if (a.HasMember("TC_END")) { + log->info("Received TC_END command.\n"); + syssim->tc_end(); + } else { + log->error("Unknown command type.\n"); + } + } else if (n == 0) { + log->error("Connection closed on UT interface.\n"); + } else { + log->error("Error receiving from network\n"); + exit(-1); + } + } + running = false; + + srslte_netsource_free(&net_source); + } + + void handle_power_off(Document& document) + { + syssim->power_off_ue(); + + // Create response + Document resp; + resp.SetObject(); + resp.AddMember("Result", true, resp.GetAllocator()); + + // Serialize and send to tester + StringBuffer buffer; + Writer writer(buffer); + resp.Accept(writer); + + log->info("Sending %s to tester (%zd B)\n", buffer.GetString(), buffer.GetSize()); + srslte_netsource_write(&net_source, (char*)buffer.GetString(), buffer.GetSize()); + } + + syssim_interface* syssim = nullptr; +}; + +#endif // SRSUE_TTCN3_UT_INTERFACE_H \ No newline at end of file diff --git a/srsue/test/ttcn3/src/CMakeLists.txt b/srsue/test/ttcn3/src/CMakeLists.txt new file mode 100644 index 000000000..4bc87d373 --- /dev/null +++ b/srsue/test/ttcn3/src/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright 2013-2019 Software Radio Systems Limited +# +# This file is part of srsLTE +# +# srsLTE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# srsLTE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# A copy of the GNU Affero General Public License can be found in +# the LICENSE file in the top-level directory of this distribution +# and at http://www.gnu.org/licenses/. +# + +add_executable(ttcn3_dut ttcn3_dut.cc lte_ttcn3_phy.cc) +target_link_libraries(ttcn3_dut srsue_stack + srsue_upper + srsue_rrc + srslte_upper + srslte_common + srslte_phy + srslte_radio + srslte_upper + srsue_phy + srsue_mac + rrc_asn1 + ${Boost_LIBRARIES}) +include_directories(${PROJECT_SOURCE_DIR}/srsue/test/ttcn3/hdr) \ No newline at end of file diff --git a/srsue/test/ttcn3/src/lte_ttcn3_phy.cc b/srsue/test/ttcn3/src/lte_ttcn3_phy.cc new file mode 100644 index 000000000..1321b9d70 --- /dev/null +++ b/srsue/test/ttcn3/src/lte_ttcn3_phy.cc @@ -0,0 +1,381 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "lte_ttcn3_phy.h" + +namespace srsue { + +#define MIN_IN_SYNC_POWER (-100) +#define DEFAULT_RSRQ (-3.0) + +lte_ttcn3_phy::lte_ttcn3_phy(srslte::logger* logger_) : logger(logger_) {} + +lte_ttcn3_phy::~lte_ttcn3_phy() {} + +int lte_ttcn3_phy::init(const phy_args_t& args_, stack_interface_phy_lte* stack_, syssim_interface_phy* syssim_) + +{ + stack = stack_; + syssim = syssim_; + + return init(args_); +} + +int lte_ttcn3_phy::init(const phy_args_t& args_, stack_interface_phy_lte* stack_, radio_interface_phy* radio_) +{ + + return init(args_); +} + +// ue_phy_base interface +int lte_ttcn3_phy::init(const phy_args_t& args_) +{ + log.init("PHY ", logger); + log.set_level(args_.log.phy_level); + + return SRSLTE_SUCCESS; +} + +void lte_ttcn3_phy::stop(){}; + +void lte_ttcn3_phy::set_earfcn(std::vector earfcns) {} + +void lte_ttcn3_phy::force_freq(float dl_freq, float ul_freq) {} + +void lte_ttcn3_phy::wait_initialize() {} + +void lte_ttcn3_phy::start_plot() {} + +void lte_ttcn3_phy::get_metrics(phy_metrics_t* m) {} + +// The interface for the SS +void lte_ttcn3_phy::set_cell_map(const cell_list_t& cells_) +{ + cells = cells_; +} + +// The interface for RRC +void lte_ttcn3_phy::get_current_cell(srslte_cell_t* cell_, uint32_t* earfcn_) +{ + if (cell_) { + memcpy(cell_, &pcell.info, sizeof(srslte_cell_t)); + } + if (earfcn_) { + *earfcn_ = pcell.earfcn; + } +} + +uint32_t lte_ttcn3_phy::get_current_earfcn() +{ + return pcell.earfcn; +} + +uint32_t lte_ttcn3_phy::get_current_pci() +{ + return pcell.info.id; +} + +void lte_ttcn3_phy::set_config_tdd(srslte_tdd_config_t& tdd_config) {} + +void lte_ttcn3_phy::set_config_scell(asn1::rrc::scell_to_add_mod_r10_s* scell_config) +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +void lte_ttcn3_phy::enable_pregen_signals(bool enable) +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +void lte_ttcn3_phy::set_activation_deactivation_scell(uint32_t cmd) +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +void lte_ttcn3_phy::set_config(srslte::phy_cfg_t& config, uint32_t cc_idx, uint32_t earfcn, srslte_cell_t* cell_info) +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +// Measurements interface +void lte_ttcn3_phy::meas_reset(){}; + +int lte_ttcn3_phy::meas_start(uint32_t earfcn, int pci) +{ + return 0; +} + +int lte_ttcn3_phy::meas_stop(uint32_t earfcn, int pci) +{ + return 0; +}; + +// eMBMS interface +void lte_ttcn3_phy::set_mch_period_stop(uint32_t stop){}; + +void lte_ttcn3_phy::set_config_mbsfn_sib2(asn1::rrc::sib_type2_s* sib2){}; + +void lte_ttcn3_phy::set_config_mbsfn_sib13(asn1::rrc::sib_type13_r9_s* sib13){}; + +void lte_ttcn3_phy::set_config_mbsfn_mcch(asn1::rrc::mcch_msg_s* mcch){}; + +/* Cell search and selection procedures */ +phy_interface_rrc_lte::cell_search_ret_t lte_ttcn3_phy::cell_search(phy_cell_t* found_cell) +{ + log.info("Running cell search in PHY\n"); + cell_search_ret_t ret = {}; + + // select strongest cell as PCell + float max_power = -145; + int max_index = 0; + for (uint32_t i = 0; i < cells.size(); ++i) { + if (cells[i].power > max_power) { + max_power = cells[i].power; + max_index = i; + } + } + + // Consider cell found if above -100dBm + if (max_power >= MIN_IN_SYNC_POWER) { + pcell = cells[max_index]; + log.info("Setting PCell to EARFCN=%d CellId=%d with RS power=%.2f\n", pcell.earfcn, pcell.info.id, max_power); + if (found_cell) { + found_cell->earfcn = pcell.earfcn; + found_cell->cell = pcell.info; + } + ret.found = cell_search_ret_t::CELL_FOUND; + ret.last_freq = cell_search_ret_t::NO_MORE_FREQS; + } else { + // no suitable cell found + ret.found = cell_search_ret_t::CELL_NOT_FOUND; + } + return ret; +}; + +bool lte_ttcn3_phy::cell_select(phy_cell_t* cell) +{ + log.debug("%s not implemented.\n", __FUNCTION__); + return true; +}; + +bool lte_ttcn3_phy::cell_is_camping() +{ + return true; +}; + +void lte_ttcn3_phy::reset() +{ + log.debug("%s not implemented.\n", __FUNCTION__); +}; + +// The interface for MAC +void lte_ttcn3_phy::configure_prach_params() +{ + log.debug("%s not implemented.\n", __FUNCTION__); +}; + +void lte_ttcn3_phy::prach_send(uint32_t preamble_idx, int allowed_subframe, float target_power_dbm) +{ + log.info("Sending PRACH with preamble %d on PCID=%d\n", preamble_idx, pcell.info.id); + prach_tti_tx = current_tti; + ra_trans_cnt++; + + syssim->prach_indication(preamble_idx, pcell.info.id); +}; + +std::string lte_ttcn3_phy::get_type() +{ + return "lte_ttcn3"; +} + +phy_interface_mac_lte::prach_info_t lte_ttcn3_phy::prach_get_info() +{ + prach_info_t info = {}; + if (prach_tti_tx != -1) { + info.is_transmitted = true; + info.tti_ra = prach_tti_tx; + } + + log.info("Return prach_tti_tx=%d\n", prach_tti_tx); + return info; +} + +/* Indicates the transmission of a SR signal in the next opportunity */ +void lte_ttcn3_phy::sr_send() +{ + sr_pending = true; + sr_tx_tti = -1; +} + +int lte_ttcn3_phy::sr_last_tx_tti() +{ + return sr_tx_tti; +} + +// The RAT-agnostic interface for MAC + +/* Sets a C-RNTI allowing the PHY to pregenerate signals if necessary */ +void lte_ttcn3_phy::set_crnti(uint16_t rnti) +{ + current_temp_rnti = rnti; + log.info("Set Temp-RNTI=%d\n", rnti); +} + +/* Time advance commands */ +void lte_ttcn3_phy::set_timeadv_rar(uint32_t ta_cmd) +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +void lte_ttcn3_phy::set_timeadv(uint32_t ta_cmd) +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +// Sets RAR grant payload +void lte_ttcn3_phy::set_rar_grant(uint8_t grant_payload[SRSLTE_RAR_GRANT_LEN], uint16_t rnti) +{ + // Empty, SYSSIM knows when to provide UL grant for Msg3 +} + +// Called from the SYSSIM to configure the current TTI +void lte_ttcn3_phy::set_current_tti(uint32_t tti) +{ + current_tti = tti; + run_tti(); +} + +// Called from MAC to retrieve the current TTI +uint32_t lte_ttcn3_phy::get_current_tti() +{ + return current_tti; +} + +float lte_ttcn3_phy::get_phr() +{ + log.debug("%s not implemented.\n", __FUNCTION__); + return 0.1; +} + +float lte_ttcn3_phy::get_pathloss_db() +{ + log.debug("%s not implemented.\n", __FUNCTION__); + return 85.0; +} + +// Only provides a new UL grant, Tx is then triggered +void lte_ttcn3_phy::new_grant_ul(mac_interface_phy_lte::mac_grant_ul_t ul_mac_grant) +{ + mac_interface_phy_lte::tb_action_ul_t ul_action = {}; + + // Deliver grant and retrieve payload + stack->new_grant_ul(cc_idx, ul_mac_grant, &ul_action); + + // Deliver MAC PDU to SYSSIM + if (ul_action.tb.enabled and ul_action.tb.payload != nullptr) { + syssim->tx_pdu(ul_action.tb.payload, ul_mac_grant.tb.tbs, ul_mac_grant.rnti); + } +} + +// Provides DL grant, copy data into DL action and pass up to MAC +void lte_ttcn3_phy::new_tb(const srsue::mac_interface_phy_lte::mac_grant_dl_t dl_grant, const uint8_t* data) +{ + if (data == nullptr) { + log.error("Invalid data buffer passed\n"); + return; + } + + // pass grant to MAC to retrieve DL action + mac_interface_phy_lte::tb_action_dl_t dl_action = {}; + + stack->new_grant_dl(cc_idx, dl_grant, &dl_action); + + bool dl_ack[SRSLTE_MAX_CODEWORDS] = {}; + + if (dl_action.tb[0].enabled && dl_action.tb[0].payload != nullptr) { + log.info_hex(data, + dl_grant.tb[0].tbs, + "TB received rnti=%d, tti=%d, n_bytes=%d\n", + dl_grant.rnti, + current_tti, + dl_grant.tb[0].tbs); + + if (dl_action.generate_ack) { + log.debug("Calling generate ACK callback\n"); + // action.generate_ack_callback(action.generate_ack_callback_arg); + } + memcpy(dl_action.tb->payload, data, dl_grant.tb[0].tbs); + + // ACK first TB and pass up + dl_ack[0] = true; + + log.info("TB processed correctly\n"); + } else { + log.error("Couldn't get buffer for TB\n"); + } + + stack->tb_decoded(cc_idx, dl_grant, dl_ack); +} + +void lte_ttcn3_phy::radio_overflow() +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +void lte_ttcn3_phy::radio_failure() +{ + log.debug("%s not implemented.\n", __FUNCTION__); +} + +void lte_ttcn3_phy::run_tti() +{ + // send report for each cell + for (auto& cell : cells) { + stack->new_phy_meas(cell.power, DEFAULT_RSRQ, current_tti, cell.earfcn, cell.info.id); + } + + // check if Pcell is in sync + for (auto& cell : cells) { + if (cell.info.id == pcell.info.id) { + if (cell.power >= MIN_IN_SYNC_POWER) { + log.info("PCell id=%d power=%.2f -> sync\n", pcell.info.id, cell.power); + stack->in_sync(); + } else { + log.info("PCell id=%d power=%.2f -> out of sync\n", pcell.info.id, cell.power); + stack->out_of_sync(); + } + break; // make sure to call stack only once + } + } + + log.step(current_tti); + + // Check for SR + if (sr_pending) { + syssim->sr_req(current_tti); + sr_pending = false; + sr_tx_tti = current_tti; + } + + stack->run_tti(current_tti); +} + +} // namespace srsue diff --git a/srsue/test/ttcn3/src/ttcn3_dut.cc b/srsue/test/ttcn3/src/ttcn3_dut.cc new file mode 100644 index 000000000..4f73a3664 --- /dev/null +++ b/srsue/test/ttcn3/src/ttcn3_dut.cc @@ -0,0 +1,152 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "rapidjson/document.h" // rapidjson's DOM-style API +#include "rapidjson/prettywriter.h" // for stringify JSON +#include "srslte/build_info.h" +#include "srslte/common/log_filter.h" +#include "srslte/common/logger_stdout.h" +#include "srsue/hdr/ue.h" +#include "ttcn3_helpers.h" +#include "ttcn3_syssim.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace srslte; +using namespace srsue; +; +using namespace std; +using namespace rapidjson; +namespace bpo = boost::program_options; + +typedef struct { + pcap_args_t pcap; + std::string log_filename; + std::string log_level; + int32_t log_hex_level; +} ttcn3_dut_args_t; + +all_args_t parse_args(ttcn3_dut_args_t* args, int argc, char* argv[]) +{ + // Command line only options + bpo::options_description general("General options"); + + general.add_options()("help,h", "Produce help message")("version,v", "Print version information and exit"); + + // Command line or config file options + bpo::options_description common("Configuration options"); + // clang-format off + common.add_options() + ("pcap.enable", bpo::value(&args->pcap.enable)->default_value(true), "Enable MAC packet captures for wireshark") + ("pcap.filename", bpo::value(&args->pcap.filename)->default_value("/tmp/ttcn3_ue.pcap"), "MAC layer capture filename") + ("pcap.nas_enable", bpo::value(&args->pcap.nas_enable)->default_value(false), "Enable NAS packet captures for wireshark") + ("pcap.nas_filename", bpo::value(&args->pcap.nas_filename)->default_value("/tmp/ttcn3_ue_nas.pcap"), "NAS layer capture filename (useful when NAS encryption is enabled)") + ("logfilename", bpo::value(&args->log_filename)->default_value("/tmp/ttcn3_ue.log"), "Filename of log file") + ("loglevel", bpo::value(&args->log_level)->default_value("warning"), "Log level (Error,Warning,Info,Debug)") + ("loghexlevel", bpo::value(&args->log_hex_level)->default_value(64), "Log hex level (-1 unbounded)"); + // clang-format on + + // these options are allowed on the command line + bpo::options_description cmdline_options; + cmdline_options.add(common).add(general); + + // parse the command line and store result in vm + bpo::variables_map vm; + bpo::store(bpo::command_line_parser(argc, argv).options(cmdline_options).run(), vm); + bpo::notify(vm); + + // help option was given - print usage and exit + if (vm.count("help")) { + cout << "Usage: " << argv[0] << " [OPTIONS] config_file" << endl << endl; + cout << common << endl << general << endl; + exit(0); + } + + all_args_t all_args = {}; + + all_args.stack.pcap.enable = args->pcap.enable; + all_args.stack.pcap.nas_enable = args->pcap.nas_enable; + + all_args.stack.pcap.filename = args->pcap.filename; + all_args.stack.pcap.nas_filename = args->pcap.nas_filename; + + all_args.log.filename = args->log_filename; + all_args.log.all_level = args->log_level; + all_args.log.all_hex_limit = args->log_hex_level; + + all_args.stack.log.mac_level = args->log_level; + all_args.stack.log.rlc_level = args->log_level; + all_args.stack.log.pdcp_level = args->log_level; + all_args.stack.log.rrc_level = args->log_level; + all_args.stack.log.nas_level = args->log_level; + all_args.stack.log.gw_level = args->log_level; + all_args.stack.log.usim_level = args->log_level; + all_args.stack.log.mac_hex_limit = args->log_hex_level; + all_args.stack.log.rlc_hex_limit = args->log_hex_level; + all_args.stack.log.pdcp_hex_limit = args->log_hex_level; + all_args.stack.log.rrc_hex_limit = args->log_hex_level; + all_args.stack.log.nas_hex_limit = args->log_hex_level; + all_args.stack.log.gw_hex_limit = args->log_hex_level; + all_args.stack.log.usim_hex_limit = args->log_hex_level; + + return all_args; +} + +bool go_exit = false; +void sig_int_handler(int signo) +{ + printf("SIGINT received. Exiting...\n"); + if (signo == SIGINT) { + go_exit = true; + } +} + +int main(int argc, char** argv) +{ + std::cout << "Built in " << srslte_get_build_mode() << " mode using " << srslte_get_build_info() << "." << std::endl; + + ttcn3_dut_args_t dut_args; + + all_args_t ue_args = parse_args(&dut_args, argc, argv); + + signal(SIGINT, sig_int_handler); + + // Instantiate file logger + srslte::logger_file logger_file; + + // create and init SYSSIM + ttcn3_syssim syssim(&logger_file); + syssim.init(ue_args); + + // Loop until finished .. + while (!go_exit) { + sleep(1); + } + + syssim.stop(); + + return SRSLTE_SUCCESS; +} diff --git a/srsue/test/ttcn3/test/CMakeLists.txt b/srsue/test/ttcn3/test/CMakeLists.txt new file mode 100644 index 000000000..eedc56779 --- /dev/null +++ b/srsue/test/ttcn3/test/CMakeLists.txt @@ -0,0 +1,28 @@ +# +# Copyright 2013-2019 Software Radio Systems Limited +# +# This file is part of srsLTE +# +# srsLTE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# srsLTE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# A copy of the GNU Affero General Public License can be found in +# the LICENSE file in the top-level directory of this distribution +# and at http://www.gnu.org/licenses/. +# + +include_directories(${PROJECT_SOURCE_DIR}/srsue/test/ttcn3/hdr) + +add_executable(rapidjson_test rapidjson_test.cc) +add_test(rapidjson_test rapidjson_test) + +add_executable(ttcn3_if_handler_test ttcn3_if_handler_test.cc) +target_link_libraries(ttcn3_if_handler_test srslte_phy srslte_common) +add_test(ttcn3_if_handler_test ttcn3_if_handler_test) \ No newline at end of file diff --git a/srsue/test/ttcn3/test/rapidjson_test.cc b/srsue/test/ttcn3/test/rapidjson_test.cc new file mode 100644 index 000000000..f31dea255 --- /dev/null +++ b/srsue/test/ttcn3/test/rapidjson_test.cc @@ -0,0 +1,153 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "ttcn3_helpers.h" +#include +#include +#include +#include +#include + +int SYSTEM_CTRL_CNF_test() +{ + char titan_result[] = "{\"Common\":{\"CellId\":\"eutra_Cell1\",\"RoutingInfo\":{\"None\":true},\"TimingInfo\":{" + "\"Now\":true},\"Result\":{\"Success\":true}},\"Confirm\":{\"Cell\":true}}"; + + printf("%s\n", titan_result); + + printf("SYSTEM_CTRL_CNF_test()\n"); + + const char cellid[] = "eutra_Cell1"; + + Document resp; + resp.SetObject(); + + // Create members of common object + + // RoutingInfo + Value routing_info(kObjectType); + routing_info.AddMember("None", true, resp.GetAllocator()); + + // TimingInfo + Value timing_info(kObjectType); + timing_info.AddMember("Now", true, resp.GetAllocator()); + + // Result + Value result(kObjectType); + result.AddMember("Success", true, resp.GetAllocator()); + + // Now, create the common object itself and add members + Value common(kObjectType); + common.AddMember("CellId", cellid, resp.GetAllocator()); + common.AddMember("RoutingInfo", routing_info, resp.GetAllocator()); + common.AddMember("TimingInfo", timing_info, resp.GetAllocator()); + common.AddMember("Result", result, resp.GetAllocator()); + + // The confirm object + Value confirm(kObjectType); + confirm.AddMember("Cell", true, resp.GetAllocator()); + + resp.AddMember("Common", common, resp.GetAllocator()); + resp.AddMember("Confirm", confirm, resp.GetAllocator()); + + // printf("SYSTEM_CTRL_CNF\n%s\n", (char*)buffer.GetString()); + + return 0; +} + +void pretty_print(std::string json) +{ + Document document; + if (document.Parse((char*)json.c_str()).HasParseError()) { + fprintf(stderr, "Error parsing incoming data. Exiting\n"); + exit(-1); + } + assert(document.IsObject()); + + // Pretty-print + StringBuffer buffer; + PrettyWriter writer(buffer); + document.Accept(writer); + printf("%s\n", (char*)buffer.GetString()); +} + +// UDP v4 test +void IP_SOCK_CTRL_udp_v4_test() +{ + printf("IP_SOCK_CTRL_udp_v4_test()\n"); + char titan_result[] = "{\\\"CTRL\\\":{\\\"ConnectionId\\\":{\\\"Protocol\\\":\\\"udp\\\",\\\"Local\\\":{" + "\\\"IpAddr\\\":{\\\"V4\\\":{\\\"Addr\\\":\\\"127.0.0.1\\\"}},\\\"Port\\\":11},\\\"Remote\\\":{" + "\\\"Port\\\":22}},\\\"Ind\\\":{\\\"UDP\\\":{\\\"SocketCnf\\\":true}}}}\")"; + printf("%s\n", titan_result); + + string resp = ttcn3_helpers::get_ctrl_cnf("udp", "V4", "127.0.0.1"); + + pretty_print(resp); +}; + +// UDP v4 test +void IP_SOCK_CTRL_icmp_v6_test() +{ + printf("IP_SOCK_CTRL_icmp_v6_test()\n"); + // char titan_result[] = + // "{\\\"CTRL\\\":{\\\"ConnectionId\\\":{\\\"Protocol\\\":\\\"udp\\\",\\\"Local\\\":{\\\"IpAddr\\\":{\\\"V4\\\":{\\\"Addr\\\":\\\"127.0.0.1\\\"}},\\\"Port\\\":11},\\\"Remote\\\":{\\\"Port\\\":22}},\\\"Ind\\\":{\\\"UDP\\\":{\\\"SocketCnf\\\":true}}}}\")"; + // printf("%s\n", titan_result); + + //{ V6 := { Addr := "", ScopeId := omit } } + + string resp = ttcn3_helpers::get_ctrl_cnf("icmp6", "V6", "127.0.0.1"); + + pretty_print(resp); +}; + +void PdcpCountGetReq_test() +{ + // Titan encoding test + //"{\"Common\":{\"CellId\":\"eutra_Cell1\",\"RoutingInfo\":{\"None\":true},\"TimingInfo\":{\"Now\":true},\"Result\":{\"Success\":true}},\"Confirm\":{\"PdcpCount\":{\"Get\":[{\"RadioBearerId\":{\"Srb\":1},\"UL\":{\"Format\":\"PdcpCount_Srb\",\"Value\":\"00000000000000000000000000000000\"},\"DL\":{\"Format\":\"PdcpCount_Srb\",\"Value\":\"00000000000000000000000000000000\"}}]}}}") + + std::vector bearers; + ttcn3_helpers::pdcp_count_t srb1; + srb1.rb_is_srb = true; + srb1.rb_id = 1; + srb1.dl_value = 0; + srb1.ul_value = 1; + bearers.push_back(srb1); + + string resp = ttcn3_helpers::get_pdcp_count_response("cell1", bearers); + pretty_print(resp); +} + +void EnquireTiming_test() +{ + string resp = ttcn3_helpers::get_sys_req_cnf_with_time("cell1", "EnquireTiming", 7289); + pretty_print(resp); +} + +int main(int argc, char** argv) +{ + // SYSTEM_CTRL_CNF_test(); + // IP_SOCK_CTRL_udp_v4_test(); + // IP_SOCK_CTRL_icmp_v6_test(); + // PdcpCountGetReq_test(); + EnquireTiming_test(); + + return 0; +} diff --git a/srsue/test/ttcn3/test/ttcn3_if_handler_test.cc b/srsue/test/ttcn3/test/ttcn3_if_handler_test.cc new file mode 100644 index 000000000..ebb330c89 --- /dev/null +++ b/srsue/test/ttcn3/test/ttcn3_if_handler_test.cc @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srslte/srslte.h" +#include "ttcn3_ip_ctrl_interface.h" +#include "ttcn3_ip_sock_interface.h" +#include "ttcn3_srb_interface.h" +#include "ttcn3_sys_interface.h" +#include "ttcn3_ut_interface.h" +#include +#include +#include + +int if_handler_test() +{ + ttcn3_ut_interface ut; + ttcn3_sys_interface sys; + ttcn3_ip_sock_interface ip_sock; + ttcn3_ip_ctrl_interface ip_ctrl; + ttcn3_srb_interface srb; + + return SRSLTE_SUCCESS; +} + +int main(int argc, char** argv) +{ + if_handler_test(); + + return SRSLTE_SUCCESS; +}