diff --git a/lib/include/srslte/common/basic_pnf.h b/lib/include/srslte/common/basic_pnf.h new file mode 100644 index 000000000..1838feb96 --- /dev/null +++ b/lib/include/srslte/common/basic_pnf.h @@ -0,0 +1,428 @@ +/* + * Copyright 2013-2020 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 SRSLTE_BASIC_PNF_H +#define SRSLTE_BASIC_PNF_H + +#include "basic_vnf_api.h" +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RAND_SEED (12384) +#define RX_TIMEOUT_MS (500) + +#define MIN_TB_LEN (100) // MAX_TB_LEN defined in api.h + +namespace srslte { + +struct pnf_metrics_t { + uint32_t avg_rtt_us; + uint32_t num_timing_errors; + uint32_t num_pdus; + uint32_t tb_size; +}; + +class srslte_basic_pnf +{ +public: + srslte_basic_pnf(const std::string& type_, + const std::string& vnf_p5_addr, + const uint16_t& vnf_p5_port, + const uint32_t& sf_interval, + const int32_t& num_sf_, + const uint32_t& tb_len_) : + running(false), + type(type_), + vnf_addr(vnf_p5_addr), + vnf_port(vnf_p5_port), + sf_interval_us(sf_interval), + num_sf(num_sf_), + tb_len(tb_len_), + rand_gen(RAND_SEED), + rand_dist(MIN_TB_LEN, MAX_TB_LEN){}; + + ~srslte_basic_pnf() { stop(); }; + + bool start() + { + // create socket + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("socket"); + return false; + } + + int enable = 1; +#if defined(SO_REUSEADDR) + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + perror("setsockopt(SO_REUSEADDR) failed"); + } +#endif +#if defined(SO_REUSEPORT) + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { + perror("setsockopt(SO_REUSEPORT) failed"); + } +#endif + + bzero(&servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = inet_addr(vnf_addr.c_str()); + servaddr.sin_port = htons(vnf_port); + + // start main thread + running = true; + + if (type == "gnb") { + rx_thread = std::unique_ptr(new std::thread(&srslte_basic_pnf::rx_thread_function, this)); + tx_thread = std::unique_ptr(new std::thread(&srslte_basic_pnf::tx_thread_function, this)); + } else { + tx_thread = std::unique_ptr(new std::thread(&srslte_basic_pnf::tx_thread_function_ue, this)); + } + + return true; + }; + + bool stop() + { + running = false; + + if (rx_thread) { + if (rx_thread->joinable()) { + rx_thread->join(); + } + } + + if (tx_thread) { + if (tx_thread->joinable()) { + tx_thread->join(); + } + } + + return true; + }; + + pnf_metrics_t get_metrics() + { + pnf_metrics_t tmp = metrics; + metrics = {}; + return tmp; + } + +private: + void rx_thread_function() + { + pthread_setname_np(pthread_self(), rx_thread_name.c_str()); + + // set_rt_prio(); + + struct pollfd fd; + fd.fd = sockfd; + fd.events = POLLIN; + + const uint32_t max_basic_api_pdu = sizeof(basic_vnf_api::dl_conf_msg_t) + 32; // larger than biggest message + std::unique_ptr > rx_buffer = + std::unique_ptr >(new std::array); + + while (running) { + // receive response + int ret = poll(&fd, 1, RX_TIMEOUT_MS); + switch (ret) { + case -1: + printf("Error occured.\n"); + running = false; + break; + case 0: + // Timeout + printf("Error: Didn't receive response after %dms\n", RX_TIMEOUT_MS); + running = false; + break; + default: + int recv_ret = recv(sockfd, rx_buffer->data(), rx_buffer->size(), 0); + handle_msg(rx_buffer->data(), recv_ret); + break; + } + + std::lock_guard lock(mutex); + auto rtt = + std::chrono::duration_cast(std::chrono::steady_clock::now() - tti_start_time) + .count(); + + // FIXME: add averaging + metrics.avg_rtt_us = rtt; + } + }; + + void tx_thread_function() + { + pthread_setname_np(pthread_self(), tx_thread_name.c_str()); + + // set_rt_prio(); + + struct pollfd fd; + fd.fd = sockfd; + fd.events = POLLIN; + + const uint32_t max_basic_api_pdu = sizeof(basic_vnf_api::dl_conf_msg_t) + 32; // larger than biggest message + std::unique_ptr > rx_buffer = + std::unique_ptr >(new std::array); + + int32_t sf_counter = 0; + while (running && (num_sf > 0 ? sf_counter < num_sf : true)) { + { + std::lock_guard lock(mutex); + + // Increase TTI + tti = (tti + 1) % 10240; + + // Take time before sending the SF indication + tti_start_time = std::chrono::steady_clock::now(); + + // Send request + send_sf_ind(tti); + + sf_counter++; + } + + std::this_thread::sleep_for(std::chrono::microseconds(sf_interval_us)); + } + + printf("Leaving Tx thread after %d subframes\n", sf_counter); + }; + + void tx_thread_function_ue() + { + pthread_setname_np(pthread_self(), tx_thread_name.c_str()); + + // set_rt_prio(); + + struct pollfd fd; + fd.fd = sockfd; + fd.events = POLLIN; + + const uint32_t max_basic_api_pdu = sizeof(basic_vnf_api::dl_conf_msg_t) + 32; // larger than biggest message + std::unique_ptr > rx_buffer = + std::unique_ptr >(new std::array); + + int32_t sf_counter = 0; + while (running && (num_sf > 0 ? sf_counter < num_sf : true)) { + { + std::lock_guard lock(mutex); + + // Increase TTI + tti = (tti + 1) % 10240; + + // Take time before sending the SF indication + tti_start_time = std::chrono::steady_clock::now(); + + // Send SF indication + send_sf_ind(tti); + + // provide DL grant every even TTI, and UL grant every odd + if (tti % 2 == 0) { + send_dl_ind(tti); + } else { + send_ul_ind(tti); + } + + sf_counter++; + } + + std::this_thread::sleep_for(std::chrono::microseconds(sf_interval_us)); + } + + printf("Leaving Tx thread after %d subframes\n", sf_counter); + }; + + void send_sf_ind(uint32_t tti_) + { + basic_vnf_api::sf_ind_msg_t sf_ind; + bzero(&sf_ind, sizeof(sf_ind)); + sf_ind.header.type = basic_vnf_api::SF_IND; + sf_ind.header.msg_len = sizeof(sf_ind) - sizeof(basic_vnf_api::msg_header_t); + sf_ind.tti = tti_; + sf_ind.t1 = 0; + sf_ind.tb_len = tb_len > 0 ? tb_len : rand_dist(rand_gen); + + int n = 0; + if ((n = sendto(sockfd, &sf_ind, sizeof(sf_ind), 0, (struct sockaddr*)&servaddr, sizeof(servaddr))) < 0) { + printf("sendto failed, ret=%d\n", n); + } + } + + int handle_msg(const uint8_t* buffer, const uint32_t len) + { + basic_vnf_api::msg_header_t* header = (basic_vnf_api::msg_header_t*)buffer; + + // printf("Received %s (%d B) in TTI\n", msg_type_text[header->type], len); + + switch (header->type) { + case basic_vnf_api::SF_IND: + printf("Error: %s not handled by VNF\n", basic_vnf_api::msg_type_text[header->type]); + break; + case basic_vnf_api::DL_CONFIG: + handle_dl_config((basic_vnf_api::dl_conf_msg_t*)header); + break; + case basic_vnf_api::TX_REQUEST: + handle_tx_request((basic_vnf_api::tx_request_msg_t*)header); + break; + default: + printf("Unknown msg type.\n"); + break; + } + return 0; + } + + int handle_dl_config(basic_vnf_api::dl_conf_msg_t* msg) + { + // printf("Received DL config for TTI=%d\n", msg->tti); + + if (msg->tti != tti) { + metrics.num_timing_errors++; + // printf("Received DL config for TTI=%d but current TTI is %d\n", msg->tti, tti.load()); + return -1; + } + + return 0; + } + + int handle_tx_request(basic_vnf_api::tx_request_msg_t* msg) + { + // printf("Received TX request config for TTI=%d\n", msg->tti); + + if (msg->tti != tti) { + metrics.num_timing_errors++; + // printf("Received TX request for TTI=%d but current TTI is %d\n", msg->tti, tti.load()); + return -1; + } + + for (uint32_t i = 0; i < msg->nof_pdus; ++i) { + metrics.tb_size += msg->pdus[i].length; + } + + metrics.num_pdus += msg->nof_pdus; + + return 0; + } + + void send_dl_ind(uint32_t tti_) + { + // MAC PDU with a single LCID with padding only + static uint8_t tv[] = { + 0x01, + 0x08, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + 0x66, + 0x77, + 0x88, + 0x3f, + }; + + basic_vnf_api::dl_ind_msg_t dl_ind = {}; + + dl_ind.header.type = basic_vnf_api::DL_IND; + dl_ind.header.msg_len = sizeof(dl_ind) - sizeof(basic_vnf_api::msg_header_t); + dl_ind.tti = tti_; + dl_ind.t1 = 0; + + dl_ind.nof_pdus = 1; + dl_ind.pdus[0].type = basic_vnf_api::PDSCH; + dl_ind.pdus[0].length = tb_len > 0 ? tb_len : rand_dist(rand_gen); + + if (dl_ind.pdus[0].length >= sizeof(tv)) { + // copy TV + memcpy(dl_ind.pdus[0].data, tv, sizeof(tv)); + // set remaining bytes to zero + memset(dl_ind.pdus[0].data + sizeof(tv), 0xaa, dl_ind.pdus[0].length - sizeof(tv)); + } else { + // just fill with dummy bytes + memset(dl_ind.pdus[0].data, 0xab, dl_ind.pdus[0].length); + } + + int n = 0; + if ((n = sendto(sockfd, &dl_ind, sizeof(dl_ind), 0, (struct sockaddr*)&servaddr, sizeof(servaddr))) < 0) { + printf("sendto failed, ret=%d\n", n); + } + } + + void send_ul_ind(uint32_t tti_) + { + basic_vnf_api::ul_ind_msg_t ul_ind = {}; + + ul_ind.header.type = basic_vnf_api::UL_IND; + ul_ind.header.msg_len = sizeof(ul_ind) - sizeof(basic_vnf_api::msg_header_t); + ul_ind.tti = tti_; + ul_ind.t1 = 0; + + ul_ind.pdus.type = basic_vnf_api::PUSCH; + ul_ind.pdus.length = tb_len > 0 ? tb_len : rand_dist(rand_gen); + + int n = 0; + if ((n = sendto(sockfd, &ul_ind, sizeof(ul_ind), 0, (struct sockaddr*)&servaddr, sizeof(servaddr))) < 0) { + printf("sendto failed, ret=%d\n", n); + } + } + + std::unique_ptr tx_thread, rx_thread; + std::string tx_thread_name = "TX_PNF", rx_thread_name = "RX_PNF"; + bool running = false; + + std::mutex mutex; + std::atomic tti; + std::chrono::steady_clock::time_point tti_start_time; + + std::string type; + + std::string vnf_addr; + uint16_t vnf_port = 3333; + + uint32_t sf_interval_us = 1000; + int32_t num_sf = -1; + uint32_t tb_len = 100; + + pnf_metrics_t metrics = {}; + + int sockfd = 0; + struct sockaddr_in servaddr = {}; + + // For random number generation + std::mt19937 rand_gen; + std::uniform_int_distribution rand_dist; +}; + +} // namespace srslte + +#endif // SRSLTE_BASIC_PNF_H diff --git a/lib/include/srslte/common/basic_vnf.h b/lib/include/srslte/common/basic_vnf.h new file mode 100644 index 000000000..3a2f3035b --- /dev/null +++ b/lib/include/srslte/common/basic_vnf.h @@ -0,0 +1,94 @@ +/* + * Copyright 2013-2020 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 SRSLTE_BASIC_VNF_H +#define SRSLTE_BASIC_VNF_H + +#include "basic_vnf_api.h" +#include "common.h" +#include "srslte/common/log_filter.h" +#include "srslte/common/threads.h" +#include "srslte/interfaces/gnb_interfaces.h" +#include "srslte/interfaces/ue_nr_interfaces.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace srslte { + +class srslte_basic_vnf : public thread +{ +public: + srslte_basic_vnf(const vnf_args_t& args_, srslte::logger* logger_, stack_interface_phy_nr* stack_); + ~srslte_basic_vnf(); + + bool stop(); + + int dl_config_request(const srsenb::phy_interface_stack_nr::dl_config_request_t& request); + int tx_request(const srsenb::phy_interface_stack_nr::tx_request_t& request); + int tx_request(const srsue::phy_interface_stack_nr::tx_request_t& request); + +private: + void run_thread(); + + // handlers + int handle_msg(const uint8_t* buffer, const uint32_t len); + int handle_sf_ind(basic_vnf_api::sf_ind_msg_t* msg); + int handle_dl_ind(basic_vnf_api::dl_ind_msg_t* msg); + int handle_ul_ind(basic_vnf_api::ul_ind_msg_t* msg); + + // senders + int send_dl_config_request(); + + // helpers + uint32_t calc_full_msg_len(const basic_vnf_api::tx_request_msg_t& msg); + + srslte::logger* m_logger = nullptr; + std::unique_ptr m_log = nullptr; + srsenb::stack_interface_phy_nr* m_gnb_stack = nullptr; + srsue::stack_interface_phy_nr* m_ue_stack = nullptr; + srslte::byte_buffer_pool* m_pool = nullptr; + + std::unique_ptr m_tx_req_msg; + + // std::unique_ptr m_dl_grant; ///< Used by UE as temp buffer for + // all DL indications + + bool running = false; + + vnf_args_t m_args = {}; + + int sockfd = 0; + struct sockaddr_in servaddr = {}, client_addr = {}; + + uint32_t last_sf_indication_time = 0; +}; + +} // namespace srslte + +#endif // SRSLTE_BASIC_VNF_H diff --git a/lib/include/srslte/common/basic_vnf_api.h b/lib/include/srslte/common/basic_vnf_api.h new file mode 100644 index 000000000..62bd7e37e --- /dev/null +++ b/lib/include/srslte/common/basic_vnf_api.h @@ -0,0 +1,147 @@ +/* + * Copyright 2013-2020 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 SRSLTE_BASIC_VNF_API_H +#define SRSLTE_BASIC_VNF_API_H + +#include + +namespace basic_vnf_api { + +// PNF (the PHY) VNF (the L2/L3) +// | | +// | | +// | - | +// | \ sf_ind_msg_t +// | \ | +// | \ | +// | \ | +// | \ | +// | \ | +// | \| +// | | +// | | DL_CONFIG.request +// | /| +// | / | +// | / | +// | / | +// | / | +// | / dl_conf_msg_t +// | / | +// |/ | TX.request +// | /| +// | / | +// | / | +// | / | +// | / | +// | / tx_request_msg_t +// | / | +// |/ | +// | | + +// Primitive API messages for basic testing basic VNF/PNF interaction +enum msg_type_t { SF_IND, DL_CONFIG, TX_REQUEST, DL_IND, UL_IND, MSG_TYPE_NITEMS }; +static const char* msg_type_text[MSG_TYPE_NITEMS] = {"SF Indication", + "DL_CONFIG.Request", + "TX.Request", + "DL_Indication", + "UL_Indication"}; +enum pdu_type_t { MAC_PBCH, PHY_PBCH, PDCCH, PDSCH, PUSCH }; + +struct msg_header_t { + msg_type_t type; + uint32_t msg_len; +}; + +struct sf_ind_msg_t { + msg_header_t header; + uint32_t t1; // Timestamp taken at PNF + uint32_t tti; // TTI of requested subframe + uint32_t tb_len; // Length of the TB +}; + +#define MAX_TB_LEN (16 * 1024) +#define MAX_PDU_SIZE (16 * 1024) +#define MAX_NUM_PDUS (1) + +struct phy_pbch_pdu_t { + uint16_t phy_cell_id; // 0 - 1007 + uint8_t ss_block_index; // 0-63 + uint8_t ssb_sc_offset; // 0-15 + uint8_t dmrs_pos; // 0-1 + uint8_t pdcch_config; // 0-255 +}; + +struct dl_conf_msg_t { + msg_header_t header; + uint32_t t1; // Replayed timestamp + uint32_t t2; // Timestamp taken at VNF + uint32_t tti; // TTI + uint16_t beam_id; // tx beam id for the slot +}; + +struct tx_request_pdu_t { + uint16_t length; + uint16_t index; // index indicated in dl_config + pdu_type_t type; // physical chan of pdu/tb + uint8_t data[MAX_PDU_SIZE]; +}; + +struct tx_request_msg_t { + msg_header_t header; + uint32_t tti; // TTI + uint32_t tb_len; // actual TB len + uint32_t nof_pdus; + tx_request_pdu_t pdus[MAX_NUM_PDUS]; +}; + +// UE specific messages +struct dl_ind_pdu_t { + pdu_type_t type; // physical chan of pdu/tb + uint16_t length; + uint8_t data[MAX_PDU_SIZE]; +}; + +struct dl_ind_msg_t { + msg_header_t header; + uint32_t t1; // Timestamp taken at PNF + uint32_t tti; // tti or slot? + uint32_t nof_pdus; + dl_ind_pdu_t pdus[MAX_NUM_PDUS]; +}; + +///< Messages for UL (only one PDU) +struct ul_ind_pdu_t { + pdu_type_t type; // physical chan of pdu/tb + uint16_t length; +}; + +struct ul_ind_msg_t { + msg_header_t header; + uint32_t t1; // Timestamp taken at PNF + uint32_t tti; // tti or slot? + uint32_t rnti; ///< RNTI of this grant + ul_ind_pdu_t pdus; +}; + +} // namespace basic_vnf_api + +#endif // SRSLTE_BASIC_VNF_API_H \ No newline at end of file diff --git a/lib/include/srslte/common/interfaces_common.h b/lib/include/srslte/common/interfaces_common.h index 8e481c38f..ba8eb2e51 100644 --- a/lib/include/srslte/common/interfaces_common.h +++ b/lib/include/srslte/common/interfaces_common.h @@ -64,6 +64,14 @@ typedef struct { } rf_args_t; +struct vnf_args_t { + std::string type; + std::string bind_addr; + uint16_t bind_port; + std::string log_level; + int log_hex_limit; +}; + class srslte_gw_config_t { public: diff --git a/lib/src/common/CMakeLists.txt b/lib/src/common/CMakeLists.txt index 3bef21aed..06d6b62b5 100644 --- a/lib/src/common/CMakeLists.txt +++ b/lib/src/common/CMakeLists.txt @@ -44,7 +44,7 @@ set(SOURCES arch_select.cc s3g.cc) if (ENABLE_5GNR) - set(SOURCES ${SOURCES} mac_nr_pcap.cc) + set(SOURCES ${SOURCES} mac_nr_pcap.cc basic_vnf.cc) endif(ENABLE_5GNR) # Avoid warnings caused by libmbedtls about deprecated functions @@ -59,4 +59,4 @@ add_executable(arch_select arch_select.cc) target_include_directories(srslte_common PUBLIC ${SEC_INCLUDE_DIRS}) target_link_libraries(srslte_common srslte_phy ${SEC_LIBRARIES}) -add_subdirectory(test) \ No newline at end of file +add_subdirectory(test) diff --git a/lib/src/common/basic_vnf.cc b/lib/src/common/basic_vnf.cc new file mode 100644 index 000000000..555018e3b --- /dev/null +++ b/lib/src/common/basic_vnf.cc @@ -0,0 +1,365 @@ +/* + * Copyright 2013-2020 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/common/basic_vnf.h" +#include "srslte/interfaces/ue_nr_interfaces.h" +#include +#include +#include + +#define RAND_SEED (12314) +#define RX_TIMEOUT_MS (1000) + +namespace srslte { + +struct srslte_pnf_info_t { + // TODO: fill when needed +}; + +struct srslte_vnf_info_t {}; + +srslte_basic_vnf::srslte_basic_vnf(const vnf_args_t& args_, srslte::logger* logger_, stack_interface_phy_nr* stack_) : + m_args(args_), + m_logger(logger_), + thread("BASIC_VNF_P7"), + m_tx_req_msg(new basic_vnf_api::tx_request_msg_t), + m_log(new srslte::log_filter), + m_pool(srslte::byte_buffer_pool::get_instance()) +{ + m_log->init("VNF ", m_logger); + m_log->set_level(m_args.log_level); + m_log->set_hex_limit(m_args.log_hex_limit); + + if (m_args.type == "gnb" || m_args.type == "ue") { + if (m_args.type == "gnb") { + m_gnb_stack = (srsenb::stack_interface_phy_nr*)stack_; + } else { + m_ue_stack = (srsue::stack_interface_phy_nr*)stack_; + } + + m_log->info("Initializing VNF for gNB\n"); + start(); + } else { + m_log->error("Unknown VNF type. Exiting\n."); + } +} + +srslte_basic_vnf::~srslte_basic_vnf() +{ + stop(); +} + +void srslte_basic_vnf::run_thread() +{ + // Bind to UDP socket + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("socket"); + return; + } + + // Make sockets reusable + int enable = 1; +#if defined(SO_REUSEADDR) + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + perror("setsockopt(SO_REUSEADDR) failed"); + } +#endif +#if defined(SO_REUSEPORT) + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { + perror("setsockopt(SO_REUSEPORT) failed"); + } +#endif + + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr.sin_port = htons(m_args.bind_port); + + if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr_in))) { + perror("bind"); + return; + } + + struct pollfd fd; + fd.fd = sockfd; + fd.events = POLLIN; + + const uint32_t max_basic_api_pdu = sizeof(basic_vnf_api::dl_ind_msg_t) + 32; // larger than biggest message + std::unique_ptr > rx_buffer = + std::unique_ptr >(new std::array); + + running = true; + + m_log->info("Started VNF handler listening on %s:%d\n", m_args.bind_addr.c_str(), m_args.bind_port); + + while (running) { + int ret = poll(&fd, 1, RX_TIMEOUT_MS); + switch (ret) { + case -1: + printf("Error occured.\n"); + break; + case 0: + // Timeout + break; + default: + + socklen_t len = sizeof(client_addr); + ret = recvfrom(sockfd, rx_buffer->data(), rx_buffer->size(), MSG_WAITALL, (struct sockaddr*)&client_addr, &len); + + handle_msg(rx_buffer->data(), ret); + break; + } + } + m_log->info("VNF thread stopped\n"); +} + +int srslte_basic_vnf::handle_msg(const uint8_t* buffer, const uint32_t len) +{ + basic_vnf_api::msg_header_t* header = (basic_vnf_api::msg_header_t*)buffer; + + m_log->info("Received %s (%d B)\n", basic_vnf_api::msg_type_text[header->type], len); + + switch (header->type) { + case basic_vnf_api::SF_IND: + handle_sf_ind((basic_vnf_api::sf_ind_msg_t*)header); + break; + case basic_vnf_api::DL_CONFIG: + printf("Error: %s not handled by VNF\n", basic_vnf_api::msg_type_text[header->type]); + break; + case basic_vnf_api::DL_IND: + handle_dl_ind((basic_vnf_api::dl_ind_msg_t*)header); + break; + case basic_vnf_api::UL_IND: + handle_ul_ind((basic_vnf_api::ul_ind_msg_t*)header); + break; + default: + printf("Unknown msg type.\n"); + break; + } + return 0; +} + +int srslte_basic_vnf::handle_sf_ind(basic_vnf_api::sf_ind_msg_t* msg) +{ + int ret = SRSLTE_SUCCESS; + m_log->info("Received %s for TTI=%d\n", basic_vnf_api::msg_type_text[msg->header.type], msg->tti); + + // store Rx timestamp + last_sf_indication_time = msg->t1; + + if (m_gnb_stack != nullptr) { + m_gnb_stack->sf_indication(msg->tti); + } else if (m_ue_stack != nullptr) { + m_ue_stack->sf_indication(msg->tti); + } else { + ret = SRSLTE_ERROR; + } + + return ret; +} + +int srslte_basic_vnf::handle_dl_ind(basic_vnf_api::dl_ind_msg_t* msg) +{ + int ret = SRSLTE_ERROR; + m_log->info("Received %s for TTI=%d\n", basic_vnf_api::msg_type_text[msg->header.type], msg->tti); + + uint32_t cc_idx = 0; + + // fill DL struct + srsue::stack_interface_phy_nr::mac_nr_grant_dl_t dl_grant = {}; + dl_grant.tti = msg->tti; + + if (msg->nof_pdus > SRSLTE_MAX_TB) { + m_log->error("Too many TBs (%d > %d)\n", msg->nof_pdus, SRSLTE_MAX_TB); + goto exit; + } + + for (uint32_t i = 0; i < msg->nof_pdus; ++i) { + dl_grant.tb[i] = srslte::allocate_unique_buffer(*m_pool); + if (dl_grant.tb[i]->get_tailroom() >= msg->pdus[i].length) { + memcpy(dl_grant.tb[i]->msg, msg->pdus[i].data, msg->pdus[i].length); + dl_grant.tb[i]->N_bytes = msg->pdus[i].length; + if (msg->pdus[i].type == basic_vnf_api::PDSCH) { + m_ue_stack->tb_decoded(cc_idx, dl_grant); + } + } else { + m_log->error("TB too big to fit into buffer (%d > %d)\n", msg->pdus[i].length, dl_grant.tb[i]->get_tailroom()); + } + } + + ret = SRSLTE_SUCCESS; + +exit: + + return ret; +} + +int srslte_basic_vnf::handle_ul_ind(basic_vnf_api::ul_ind_msg_t* msg) +{ + m_log->info("Received %s for TTI=%d\n", basic_vnf_api::msg_type_text[msg->header.type], msg->tti); + + if (msg->pdus.type != basic_vnf_api::PUSCH) { + m_log->error("Received UL indication for wrong PDU type\n"); + return SRSLTE_ERROR; + } + + uint32_t cc_idx = 0; + + // fill DL struct + srsue::stack_interface_phy_nr::mac_nr_grant_ul_t ul_grant = {}; + ul_grant.tti = msg->tti; + ul_grant.tbs = msg->pdus.length; + ul_grant.rnti = msg->rnti; + m_ue_stack->new_grant_ul(cc_idx, ul_grant); + + return SRSLTE_SUCCESS; +} + +int srslte_basic_vnf::dl_config_request(const srsenb::phy_interface_stack_nr::dl_config_request_t& request) +{ + // Generate DL Config + basic_vnf_api::dl_conf_msg_t dl_conf = {}; + dl_conf.header.type = basic_vnf_api::DL_CONFIG; + dl_conf.header.msg_len = sizeof(dl_conf) - sizeof(basic_vnf_api::msg_header_t); + + dl_conf.t1 = last_sf_indication_time; // play back the time + dl_conf.t2 = 0xaa; // FIXME: add timestamp + dl_conf.tti = request.tti; + dl_conf.beam_id = request.beam_id; + + // Send entire struct + uint32_t len = sizeof(dl_conf); + + // Send it to PNF + m_log->info("Sending %s (%d B)\n", basic_vnf_api::msg_type_text[dl_conf.header.type], len); + int n = 0; + if ((n = sendto(sockfd, &dl_conf, len, MSG_CONFIRM, (struct sockaddr*)&client_addr, sizeof(client_addr))) < 0) { + m_log->error("sendto failed, ret=%d\n", n); + } + + return 0; +} + +/// Tx request from UE, i.e. UL transmission +int srslte_basic_vnf::tx_request(const srsue::phy_interface_stack_nr::tx_request_t& request) +{ + // Generate Tx request + m_tx_req_msg->header.type = basic_vnf_api::TX_REQUEST; + m_tx_req_msg->header.msg_len = 0; // set further down + + m_tx_req_msg->tti = request.tti; + + m_tx_req_msg->nof_pdus = 1; + m_tx_req_msg->pdus[0].index = 0; + m_tx_req_msg->pdus[0].type = basic_vnf_api::PUSCH; + m_tx_req_msg->pdus[0].length = request.tb_len; + + if (request.tb_len <= MAX_PDU_SIZE) { + // copy data from TB0 + memcpy(m_tx_req_msg->pdus[0].data, request.data, request.tb_len); + } else { + m_log->error("Trying to send %d B PDU. Maximum size is %d B\n", request.tb_len, MAX_PDU_SIZE); + } + + // calculate actual length of + uint32_t len = calc_full_msg_len(*m_tx_req_msg.get()); + + // update msg header length field + m_tx_req_msg->header.msg_len = len - sizeof(basic_vnf_api::msg_header_t); + + // Send it to PNF + m_log->info("Sending %s (%d B)\n", basic_vnf_api::msg_type_text[m_tx_req_msg->header.type], len); + int n = 0; + if ((n = sendto(sockfd, m_tx_req_msg.get(), len, MSG_CONFIRM, (struct sockaddr*)&client_addr, sizeof(client_addr))) < + 0) { + m_log->error("sendto failed, ret=%d\n", n); + } + + return 0; +} + +int srslte_basic_vnf::tx_request(const srsenb::phy_interface_stack_nr::tx_request_t& request) +{ + if (request.nof_pdus > MAX_NUM_PDUS) { + m_log->error("Trying to send %d PDUs but only %d supported\n", request.nof_pdus, MAX_NUM_PDUS); + return SRSLTE_ERROR; + } + + // Generate Tx request + m_tx_req_msg->header.type = basic_vnf_api::TX_REQUEST; + m_tx_req_msg->header.msg_len = 0; // set further down + + m_tx_req_msg->nof_pdus = request.nof_pdus; + m_tx_req_msg->tti = request.tti; + + for (uint32_t i = 0; i < m_tx_req_msg->nof_pdus; ++i) { + if (request.pdus[i].length <= MAX_PDU_SIZE) { + m_tx_req_msg->pdus[i].index = i; + m_tx_req_msg->pdus[i].type = request.pdus[i].pbch.mib_present ? basic_vnf_api::MAC_PBCH : basic_vnf_api::PDSCH; + m_tx_req_msg->pdus[i].length = request.pdus[i].length; + // copy data from TB0 + memcpy(m_tx_req_msg->pdus[i].data, request.pdus[i].data[0], m_tx_req_msg->pdus[i].length); + } else { + m_log->error("Trying to send %d B PDU. Maximum size is %d B\n", request.pdus[i].length, MAX_PDU_SIZE); + } + } + + // calculate actual length of message + uint32_t len = calc_full_msg_len(*m_tx_req_msg.get()); + + // update msg header length field + m_tx_req_msg->header.msg_len = len - sizeof(basic_vnf_api::msg_header_t); + + // Send it to PNF + m_log->info("Sending %s (%d B)\n", basic_vnf_api::msg_type_text[m_tx_req_msg->header.type], len); + int n = 0; + if ((n = sendto(sockfd, m_tx_req_msg.get(), len, MSG_CONFIRM, (struct sockaddr*)&client_addr, sizeof(client_addr))) < + 0) { + m_log->error("sendto failed, ret=%d\n", n); + } + + return 0; +} + +uint32_t srslte_basic_vnf::calc_full_msg_len(const basic_vnf_api::tx_request_msg_t& msg) +{ + // start with mandatory part + uint32_t len = sizeof(basic_vnf_api::msg_header_t) + 3 * sizeof(uint32_t); + + // add all PDUs + for (uint32_t i = 0; i < msg.nof_pdus; ++i) { + len += 2 * sizeof(uint16_t) + sizeof(basic_vnf_api::pdu_type_t) + msg.pdus[i].length; + } + + return len; +} + +bool srslte_basic_vnf::stop() +{ + if (running) { + running = false; + wait_thread_finish(); + } + + return true; +} + +} // namespace srslte \ No newline at end of file diff --git a/lib/test/common/CMakeLists.txt b/lib/test/common/CMakeLists.txt index d64e62b54..0c15e7d48 100644 --- a/lib/test/common/CMakeLists.txt +++ b/lib/test/common/CMakeLists.txt @@ -97,3 +97,7 @@ add_test(choice_type_test choice_type_test) add_executable(expected_test expected_test.cc) target_link_libraries(expected_test srslte_common) add_test(expected_test expected_test) + +add_executable(pnf_dummy pnf_dummy.cc) +target_link_libraries(pnf_dummy ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) + diff --git a/lib/test/common/pnf_dummy.cc b/lib/test/common/pnf_dummy.cc new file mode 100644 index 000000000..fb610164c --- /dev/null +++ b/lib/test/common/pnf_dummy.cc @@ -0,0 +1,117 @@ +/* + * Copyright 2013-2020 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 +#include +#include +#include +#include +#include +#include + +#include "srslte/common/basic_pnf.h" + +using namespace std; +namespace bpo = boost::program_options; + +struct pnf_args_t { + std::string type; + std::string vnf_addr; + uint16_t vnf_port; + uint32_t sf_interval; + int32_t num_sf; + uint32_t tb_len; +}; + +void parse_args(pnf_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() + ("vnf.type", bpo::value(&args->type)->default_value("gnb"), "VNF instance type [gnb,ue]") + ("vnf.addr", bpo::value(&args->vnf_addr)->default_value("127.0.0.1"), "VNF address") + ("vnf.port", bpo::value(&args->vnf_port)->default_value(3333), "VNF port") + ("sf_interval", bpo::value(&args->sf_interval)->default_value(1000), "Interval between subframes in us") + ("num_sf", bpo::value(&args->num_sf)->default_value(-1), "Number of subframes to signal (-1 infinity)") + ("tb_len", bpo::value(&args->tb_len)->default_value(0), "TB lenth (0 for random size)"); + + // 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); + } +} + +bool running = true; +void sig_int_handler(int signo) +{ + printf("SIGINT received. Exiting...\n"); + if (signo == SIGINT) { + running = false; + } +} + +int main(int argc, char** argv) +{ + signal(SIGINT, sig_int_handler); + + pnf_args_t args; + parse_args(&args, argc, argv); + + srslte::srslte_basic_pnf pnf(args.type, args.vnf_addr, args.vnf_port, args.sf_interval, args.num_sf, args.tb_len); + + pnf.start(); + + while (running) { + srslte::pnf_metrics_t metrics = pnf.get_metrics(); + printf("RTT=%d, #Error=%d, #PDUs=%d, Total TB size=%d, Rate=%.2f Mbit/s\n", + metrics.avg_rtt_us, + metrics.num_timing_errors, + metrics.num_pdus, + metrics.tb_size, + metrics.tb_size * 8 / 1e6); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + pnf.stop(); + + return 0; +} \ No newline at end of file