diff --git a/lib/include/srslte/interfaces/rrc_interface_types.h b/lib/include/srslte/interfaces/rrc_interface_types.h index aa118e561..73bda37db 100644 --- a/lib/include/srslte/interfaces/rrc_interface_types.h +++ b/lib/include/srslte/interfaces/rrc_interface_types.h @@ -182,6 +182,18 @@ inline uint16_t to_number(const rlc_umd_sn_size_t& sn_size) return enum_to_number(options, (uint32_t)rlc_mode_t::nulltype, (uint32_t)sn_size); } +enum class rlc_um_nr_sn_size_t { size6bits, size12bits, nulltype }; +inline std::string to_string(const rlc_um_nr_sn_size_t& sn_size) +{ + constexpr static const char* options[] = {"6 bits", "12 bits"}; + return enum_to_text(options, (uint32_t)rlc_mode_t::nulltype, (uint32_t)sn_size); +} +inline uint16_t to_number(const rlc_um_nr_sn_size_t& sn_size) +{ + constexpr static uint16_t options[] = {6, 12}; + return enum_to_number(options, (uint32_t)rlc_mode_t::nulltype, (uint32_t)sn_size); +} + struct rlc_am_config_t { /**************************************************************************** * Configurable parameters @@ -215,22 +227,49 @@ struct rlc_um_config_t { bool is_mrb; // Whether this is a multicast bearer }; +struct rlc_um_nr_config_t { + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 38.322 v15.3.0 Section 7 + ***************************************************************************/ + + rlc_um_nr_sn_size_t sn_field_length; // Number of bits used for sequence number + uint32_t UM_Window_Size; + uint32_t mod; // Rx/Tx counter modulus +}; + #define RLC_TX_QUEUE_LEN (128) +enum class rlc_type_t { lte, nr, nulltype }; +inline std::string to_string(const rlc_type_t& type) +{ + constexpr static const char* options[] = {"LTE", "NR"}; + return enum_to_text(options, (uint32_t)rlc_type_t::nulltype, (uint32_t)type); +} + class rlc_config_t { public: + rlc_type_t type; rlc_mode_t rlc_mode; rlc_am_config_t am; rlc_um_config_t um; + rlc_um_nr_config_t um_nr; uint32_t tx_queue_length; - rlc_config_t() : rlc_mode(rlc_mode_t::tm), am(), um(), tx_queue_length(RLC_TX_QUEUE_LEN){}; + rlc_config_t() : + type(rlc_type_t::lte), + rlc_mode(rlc_mode_t::tm), + am(), + um(), + um_nr(), + tx_queue_length(RLC_TX_QUEUE_LEN){}; // Factory for MCH static rlc_config_t mch_config() { - rlc_config_t cfg; + rlc_config_t cfg = {}; + cfg.type = rlc_type_t::lte; cfg.rlc_mode = rlc_mode_t::um; cfg.um.t_reordering = 45; cfg.um.rx_sn_field_length = rlc_umd_sn_size_t::size5bits; @@ -248,7 +287,8 @@ public: return {}; } // SRB1 and SRB2 are AM - rlc_config_t rlc_cfg; + rlc_config_t rlc_cfg = {}; + rlc_cfg.type = rlc_type_t::lte; rlc_cfg.rlc_mode = rlc_mode_t::am; rlc_cfg.am.t_poll_retx = 45; rlc_cfg.am.poll_pdu = -1; @@ -260,7 +300,8 @@ public: } static rlc_config_t default_rlc_um_config(uint32_t sn_size = 10) { - rlc_config_t cnfg; + rlc_config_t cnfg = {}; + cnfg.type = rlc_type_t::lte; cnfg.rlc_mode = rlc_mode_t::um; cnfg.um.t_reordering = 5; if (sn_size == 10) { @@ -282,7 +323,8 @@ public: } static rlc_config_t default_rlc_am_config() { - rlc_config_t rlc_cnfg; + rlc_config_t rlc_cnfg = {}; + rlc_cnfg.type = rlc_type_t::lte; rlc_cnfg.rlc_mode = rlc_mode_t::am; rlc_cnfg.am.t_reordering = 5; rlc_cnfg.am.t_status_prohibit = 5; @@ -292,6 +334,24 @@ public: rlc_cnfg.am.t_poll_retx = 5; return rlc_cnfg; } + static rlc_config_t default_rlc_um_nr_config(uint32_t sn_size = 6) + { + rlc_config_t cnfg = {}; + cnfg.type = rlc_type_t::nr; + cnfg.rlc_mode = rlc_mode_t::um; + if (sn_size == 6) { + cnfg.um_nr.sn_field_length = rlc_um_nr_sn_size_t::size6bits; + cnfg.um_nr.UM_Window_Size = 32; + cnfg.um_nr.mod = 64; + } else if (sn_size == 12) { + cnfg.um_nr.sn_field_length = rlc_um_nr_sn_size_t::size12bits; + cnfg.um_nr.UM_Window_Size = 2048; + cnfg.um_nr.mod = 64; + } else { + return {}; + } + return cnfg; + } }; /*************************** diff --git a/lib/include/srslte/upper/rlc_common.h b/lib/include/srslte/upper/rlc_common.h index 4ab308fe5..846e8922d 100644 --- a/lib/include/srslte/upper/rlc_common.h +++ b/lib/include/srslte/upper/rlc_common.h @@ -48,6 +48,28 @@ static const char rlc_fi_field_text[RLC_FI_FIELD_N_ITEMS][32] = {"Start and end "Not start aligned", "Not start or end aligned"}; +enum class rlc_nr_si_field_t : unsigned { + full_sdu = 0b00, + first_segment = 0b01, + last_segment = 0b10, + neither_first_nor_last_segment = 0b11, + nulltype +}; +inline std::string to_string(const rlc_nr_si_field_t& si) +{ + constexpr static const char* options[] = {"Data field contains full SDU", + "Data field contains first segment of SDU", + "Data field contains last segment of SDU", + "Data field contains neither first nor last segment of SDU"}; + return enum_to_text(options, (uint32_t)rlc_nr_si_field_t::nulltype, (uint32_t)si); +} + +static inline uint8_t operator&(rlc_nr_si_field_t lhs, int rhs) +{ + return static_cast(static_cast::type>(lhs) & + static_cast::type>(rhs)); +} + typedef enum{ RLC_DC_FIELD_CONTROL_PDU = 0, RLC_DC_FIELD_DATA_PDU, @@ -65,6 +87,13 @@ typedef struct{ uint16_t li[RLC_AM_WINDOW_SIZE]; // Array of length indicators }rlc_umd_pdu_header_t; +typedef struct { + rlc_nr_si_field_t si; // Segmentation info + rlc_um_nr_sn_size_t sn_size; // Sequence number size (6 or 12 bits) + uint16_t sn; // Sequence number + uint16_t so; // Sequence offset +} rlc_um_nr_pdu_header_t; + // AMD PDU Header struct rlc_amd_pdu_header_t{ rlc_dc_field_t dc; // Data or control diff --git a/lib/include/srslte/upper/rlc_um.h b/lib/include/srslte/upper/rlc_um.h index 3be20a831..71696c9ae 100644 --- a/lib/include/srslte/upper/rlc_um.h +++ b/lib/include/srslte/upper/rlc_um.h @@ -23,13 +23,14 @@ #define SRSLTE_RLC_UM_H #include "srslte/common/buffer_pool.h" -#include "srslte/common/log.h" #include "srslte/common/common.h" +#include "srslte/common/log.h" #include "srslte/interfaces/ue_interfaces.h" -#include "srslte/upper/rlc_tx_queue.h" #include "srslte/upper/rlc_common.h" -#include +#include "srslte/upper/rlc_tx_queue.h" #include +#include +#include #include namespace srslte { @@ -39,6 +40,11 @@ struct rlc_umd_pdu_t{ unique_byte_buffer_t buf; }; +typedef struct { + rlc_um_nr_pdu_header_t header; + unique_byte_buffer_t buf; +} rlc_umd_pdu_nr_t; + class rlc_um :public rlc_common { @@ -73,14 +79,13 @@ public: void reset_metrics(); private: - - // Transmitter sub-class - class rlc_um_tx + // Transmitter sub-class base + class rlc_um_tx_base { public: - rlc_um_tx(srslte::log* log_); - ~rlc_um_tx(); - bool configure(rlc_config_t cfg, std::string rb_name); + rlc_um_tx_base(srslte::log* log_); + virtual ~rlc_um_tx_base(); + virtual bool configure(rlc_config_t cfg, std::string rb_name) = 0; int build_data_pdu(uint8_t *payload, uint32_t nof_bytes); void stop(); void reestablish(); @@ -92,7 +97,7 @@ private: bool has_data(); uint32_t get_buffer_state(); - private: + protected: byte_buffer_pool* pool = nullptr; srslte::log* log = nullptr; std::string rb_name; @@ -101,28 +106,58 @@ private: * Configurable parameters * Ref: 3GPP TS 36.322 v10.0.0 Section 7 ***************************************************************************/ - rlc_um_config_t cfg = {}; + rlc_config_t cfg = {}; // TX SDU buffers rlc_tx_queue tx_sdu_queue; unique_byte_buffer_t tx_sdu; + // Mutexes + std::mutex mutex; + + bool tx_enabled = false; + + uint32_t num_tx_bytes = 0; + + virtual int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) = 0; + + // helper functions + virtual void debug_state() = 0; + const char* get_rb_name(); + }; + + // Transmitter sub-class for LTE + class rlc_um_tx : public rlc_um_tx_base + { + public: + rlc_um_tx(srslte::log* log_); + + bool configure(rlc_config_t cfg, std::string rb_name); + int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); + + private: /**************************************************************************** * State variables and counters * Ref: 3GPP TS 36.322 v10.0.0 Section 7 ***************************************************************************/ uint32_t vt_us = 0; // Send state. SN to be assigned for next PDU. - // Mutexes - pthread_mutex_t mutex; + void debug_state(); + }; - bool tx_enabled = false; + // Transmitter sub-class for NR + class rlc_um_tx_nr : public rlc_um_tx_base + { + public: + rlc_um_tx_nr(srslte::log* log_); - uint32_t num_tx_bytes = 0; + bool configure(rlc_config_t cfg, std::string rb_name); + int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); + + private: + uint32_t TX_Next = 0; // send state as defined in TS 38.322 v15.3 Section 7 - // helper functions void debug_state(); - const char* get_rb_name(); }; // Receiver sub-class @@ -159,7 +194,7 @@ private: * Configurable parameters * Ref: 3GPP TS 36.322 v10.0.0 Section 7 ***************************************************************************/ - rlc_um_config_t cfg = {}; + rlc_config_t cfg = {}; // Rx window std::map rx_window; @@ -182,7 +217,7 @@ private: uint32_t lcid = 0; // Mutexes - pthread_mutex_t mutex; + std::mutex mutex; bool rx_enabled = false; @@ -209,7 +244,7 @@ private: std::string get_rb_name(srsue::rrc_interface_rlc *rrc, uint32_t lcid, bool is_mrb); // Rx and Tx objects - rlc_um_tx tx; + std::unique_ptr tx; rlc_um_rx rx; }; @@ -225,6 +260,23 @@ uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t *header); bool rlc_um_start_aligned(uint8_t fi); bool rlc_um_end_aligned(uint8_t fi); +/**************************************************************************** + * Header pack/unpack helper functions for NR + * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3 + ***************************************************************************/ +uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header); + +uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload, + const uint32_t nof_bytes, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header); + +uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu); + +uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header); + } // namespace srslte #endif // SRSLTE_RLC_UM_H diff --git a/lib/src/upper/rlc_um.cc b/lib/src/upper/rlc_um.cc index 2c33410ef..47d01d330 100644 --- a/lib/src/upper/rlc_um.cc +++ b/lib/src/upper/rlc_um.cc @@ -22,7 +22,7 @@ #include "srslte/upper/rlc_um.h" #include -#define RX_MOD_BASE(x) (((x)-vr_uh-cfg.rx_window_size)%cfg.rx_mod) +#define RX_MOD_BASE(x) (((x)-vr_uh - cfg.um.rx_window_size) % cfg.um.rx_mod) using namespace asn1::rrc; @@ -37,7 +37,6 @@ rlc_um::rlc_um(srslte::log* log_, pool(byte_buffer_pool::get_instance()), rrc(rrc_), log(log_), - tx(log_), rx(log_, lcid_, pdcp_, rrc_, timers_) { } @@ -60,31 +59,51 @@ bool rlc_um::configure(rlc_config_t cnfg_) return false; } - if (not tx.configure(cfg, rb_name)) { - return false; - } + if (cfg.type == rlc_type_t::lte) { + tx.reset(new rlc_um_tx(log)); + if (not tx->configure(cfg, rb_name)) { + return false; + } - log->info("%s configured in %s: t_reordering=%d ms, rx_sn_field_length=%u bits, tx_sn_field_length=%u bits\n", - rb_name.c_str(), - srslte::to_string(cnfg_.rlc_mode).c_str(), - cfg.um.t_reordering, - srslte::to_number(cfg.um.rx_sn_field_length), - srslte::to_number(cfg.um.tx_sn_field_length)); + log->info("%s configured in %s: t_reordering=%d ms, rx_sn_field_length=%u bits, tx_sn_field_length=%u bits\n", + rb_name.c_str(), + srslte::to_string(cnfg_.rlc_mode).c_str(), + cfg.um.t_reordering, + srslte::to_number(cfg.um.rx_sn_field_length), + srslte::to_number(cfg.um.tx_sn_field_length)); + } else { + tx.reset(new rlc_um_tx_nr(log)); + if (not tx->configure(cfg, rb_name)) { + return false; + } + + log->info("%s configured in %s: sn_field_length=%u bits\n", + rb_name.c_str(), + srslte::to_string(cnfg_.rlc_mode).c_str(), + srslte::to_number(cfg.um_nr.sn_field_length)); + } return true; } bool rlc_um::rlc_um_rx::configure(rlc_config_t cnfg_, std::string rb_name_) { - cfg = cnfg_.um; + cfg = cnfg_; - if (cfg.rx_mod == 0) { - log->error("Error configuring %s RLC UM: rx_mod==0\n", get_rb_name()); - return false; - } + if (cfg.type == rlc_type_t::lte) { + if (cfg.um.rx_mod == 0) { + log->error("Error configuring %s RLC UM: rx_mod==0\n", get_rb_name()); + return false; + } - // set reordering timer - if (reordering_timer != NULL) { - reordering_timer->set(this, cfg.t_reordering); + // set reordering timer + if (reordering_timer != NULL) { + reordering_timer->set(this, cfg.um.t_reordering); + } + } else { + if (cfg.um_nr.mod == 0) { + log->error("Error configuring %s RLC UM: rx_mod==0\n", get_rb_name()); + return false; + } } rb_name = rb_name_; @@ -94,13 +113,12 @@ bool rlc_um::rlc_um_rx::configure(rlc_config_t cnfg_, std::string rb_name_) return true; } - -void rlc_um::empty_queue() { +void rlc_um::empty_queue() +{ // Drop all messages in TX SDU queue - tx.empty_queue(); + tx->empty_queue(); } - bool rlc_um::is_mrb() { return cfg.um.is_mrb; @@ -109,14 +127,16 @@ bool rlc_um::is_mrb() void rlc_um::reestablish() { - tx.reestablish(); // calls stop and enables tx again + tx->reestablish(); // calls stop and enables tx again rx.reestablish(); // nothing else needed } void rlc_um::stop() { - tx.stop(); + if (tx) { + tx->stop(); + } rx.stop(); } @@ -137,9 +157,9 @@ uint32_t rlc_um::get_bearer() void rlc_um::write_sdu(unique_byte_buffer_t sdu, bool blocking) { if (blocking) { - tx.write_sdu(std::move(sdu)); + tx->write_sdu(std::move(sdu)); } else { - tx.try_write_sdu(std::move(sdu)); + tx->try_write_sdu(std::move(sdu)); } } @@ -149,17 +169,17 @@ void rlc_um::write_sdu(unique_byte_buffer_t sdu, bool blocking) bool rlc_um::has_data() { - return tx.has_data(); + return tx->has_data(); } uint32_t rlc_um::get_buffer_state() { - return tx.get_buffer_state(); + return tx->get_buffer_state(); } int rlc_um::read_pdu(uint8_t *payload, uint32_t nof_bytes) { - return tx.build_data_pdu(payload, nof_bytes); + return tx->build_data_pdu(payload, nof_bytes); } void rlc_um::write_pdu(uint8_t *payload, uint32_t nof_bytes) @@ -169,7 +189,7 @@ void rlc_um::write_pdu(uint8_t *payload, uint32_t nof_bytes) uint32_t rlc_um::get_num_tx_bytes() { - return tx.get_num_tx_bytes(); + return tx->get_num_tx_bytes(); } uint32_t rlc_um::get_num_rx_bytes() @@ -179,7 +199,7 @@ uint32_t rlc_um::get_num_rx_bytes() void rlc_um::reset_metrics() { - tx.reset_metrics(); + tx->reset_metrics(); rx.reset_metrics(); } @@ -199,57 +219,33 @@ std::string rlc_um::get_rb_name(srsue::rrc_interface_rlc *rrc, uint32_t lcid, bo } } - /**************************************************************************** - * Tx subclass implementation + * Tx subclass implementation (base) ***************************************************************************/ -rlc_um::rlc_um_tx::rlc_um_tx(srslte::log* log_) : pool(byte_buffer_pool::get_instance()), log(log_) +rlc_um::rlc_um_tx_base::rlc_um_tx_base(srslte::log* log_) : pool(byte_buffer_pool::get_instance()), log(log_) { - pthread_mutex_init(&mutex, NULL); } - -rlc_um::rlc_um_tx::~rlc_um_tx() +rlc_um::rlc_um_tx_base::~rlc_um_tx_base() { - pthread_mutex_destroy(&mutex); } -bool rlc_um::rlc_um_tx::configure(rlc_config_t cnfg_, std::string rb_name_) -{ - cfg = cnfg_.um; - - if (cfg.tx_mod == 0) { - log->error("Error configuring %s RLC UM: tx_mod==0\n", get_rb_name()); - return false; - } - - tx_sdu_queue.resize(cnfg_.tx_queue_length); - - rb_name = rb_name_; - tx_enabled = true; - - return true; -} - - -void rlc_um::rlc_um_tx::stop() +void rlc_um::rlc_um_tx_base::stop() { tx_enabled = false; empty_queue(); } - -void rlc_um::rlc_um_tx::reestablish() +void rlc_um::rlc_um_tx_base::reestablish() { stop(); tx_enabled = true; } - -void rlc_um::rlc_um_tx::empty_queue() +void rlc_um::rlc_um_tx_base::empty_queue() { - pthread_mutex_lock(&mutex); + std::lock_guard lock(mutex); // deallocate all SDUs in transmit queue while(tx_sdu_queue.size() > 0) { @@ -258,32 +254,25 @@ void rlc_um::rlc_um_tx::empty_queue() // deallocate SDU that is currently processed tx_sdu.reset(); - - pthread_mutex_unlock(&mutex); } - -uint32_t rlc_um::rlc_um_tx::get_num_tx_bytes() +uint32_t rlc_um::rlc_um_tx_base::get_num_tx_bytes() { return num_tx_bytes; } - -void rlc_um::rlc_um_tx::reset_metrics() +void rlc_um::rlc_um_tx_base::reset_metrics() { - pthread_mutex_lock(&mutex); + std::lock_guard lock(mutex); num_tx_bytes = 0; - pthread_mutex_unlock(&mutex); } - -bool rlc_um::rlc_um_tx::has_data() +bool rlc_um::rlc_um_tx_base::has_data() { return (tx_sdu != NULL || !tx_sdu_queue.is_empty()); } - -uint32_t rlc_um::rlc_um_tx::get_buffer_state() +uint32_t rlc_um::rlc_um_tx_base::get_buffer_state() { // Bytes needed for tx SDUs uint32_t n_sdus = tx_sdu_queue.size(); @@ -300,12 +289,12 @@ uint32_t rlc_um::rlc_um_tx::get_buffer_state() // Room needed for fixed header? if(n_bytes > 0) - n_bytes += (cfg.is_mrb)?2:3; + n_bytes += (cfg.um.is_mrb) ? 2 : 3; return n_bytes; } -void rlc_um::rlc_um_tx::write_sdu(unique_byte_buffer_t sdu) +void rlc_um::rlc_um_tx_base::write_sdu(unique_byte_buffer_t sdu) { if (!tx_enabled) { return; @@ -319,7 +308,7 @@ void rlc_um::rlc_um_tx::write_sdu(unique_byte_buffer_t sdu) } } -void rlc_um::rlc_um_tx::try_write_sdu(unique_byte_buffer_t sdu) +void rlc_um::rlc_um_tx_base::try_write_sdu(unique_byte_buffer_t sdu) { if (!tx_enabled) { sdu.reset(); @@ -346,35 +335,67 @@ void rlc_um::rlc_um_tx::try_write_sdu(unique_byte_buffer_t sdu) } } - -int rlc_um::rlc_um_tx::build_data_pdu(uint8_t *payload, uint32_t nof_bytes) +int rlc_um::rlc_um_tx_base::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) { - pthread_mutex_lock(&mutex); - log->debug("MAC opportunity - %d bytes\n", nof_bytes); + unique_byte_buffer_t pdu; + { + std::lock_guard lock(mutex); + log->debug("MAC opportunity - %d bytes\n", nof_bytes); - if (not tx_enabled) { - pthread_mutex_unlock(&mutex); - return 0; - } + if (not tx_enabled) { + return 0; + } - if(!tx_sdu && tx_sdu_queue.size() == 0) { - log->info("No data available to be sent\n"); - pthread_mutex_unlock(&mutex); - return 0; + if (!tx_sdu && tx_sdu_queue.size() == 0) { + log->info("No data available to be sent\n"); + return 0; + } + + pdu = allocate_unique_buffer(*pool); + if (!pdu || pdu->N_bytes != 0) { + log->error("Failed to allocate PDU buffer\n"); + return 0; + } } + return build_data_pdu(std::move(pdu), payload, nof_bytes); +} - unique_byte_buffer_t pdu = allocate_unique_buffer(*pool); - if(!pdu || pdu->N_bytes != 0) { - log->error("Failed to allocate PDU buffer\n"); - pthread_mutex_unlock(&mutex); - return 0; +const char* rlc_um::rlc_um_tx_base::get_rb_name() +{ + return rb_name.c_str(); +} + +/**************************************************************************** + * Tx Subclass implementation for LTE + ***************************************************************************/ + +rlc_um::rlc_um_tx::rlc_um_tx(srslte::log* log_) : rlc_um_tx_base(log_) {} + +bool rlc_um::rlc_um_tx::configure(rlc_config_t cnfg_, std::string rb_name_) +{ + cfg = cnfg_; + + if (cfg.um.tx_mod == 0) { + log->error("Error configuring %s RLC UM: tx_mod==0\n", get_rb_name()); + return false; } + tx_sdu_queue.resize(cnfg_.tx_queue_length); + + rb_name = rb_name_; + tx_enabled = true; + + return true; +} + +int rlc_um::rlc_um_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) +{ + std::lock_guard lock(mutex); rlc_umd_pdu_header_t header; header.fi = RLC_FI_FIELD_START_AND_END_ALIGNED; header.sn = vt_us; header.N_li = 0; - header.sn_size = cfg.tx_sn_field_length; + header.sn_size = cfg.um.tx_sn_field_length; uint32_t to_move = 0; uint32_t last_li = 0; @@ -387,7 +408,6 @@ int rlc_um::rlc_um_tx::build_data_pdu(uint8_t *payload, uint32_t nof_bytes) { log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n", get_rb_name(), nof_bytes, head_len); - pthread_mutex_unlock(&mutex); return 0; } @@ -446,7 +466,7 @@ int rlc_um::rlc_um_tx::build_data_pdu(uint8_t *payload, uint32_t nof_bytes) // Set SN header.sn = vt_us; - vt_us = (vt_us + 1)%cfg.tx_mod; + vt_us = (vt_us + 1) % cfg.um.tx_mod; // Add header and TX rlc_um_write_data_pdu_header(&header, pdu.get()); @@ -459,20 +479,124 @@ int rlc_um::rlc_um_tx::build_data_pdu(uint8_t *payload, uint32_t nof_bytes) num_tx_bytes += ret; - pthread_mutex_unlock(&mutex); return ret; } +void rlc_um::rlc_um_tx::debug_state() +{ + log->debug("%s vt_us = %d\n", get_rb_name(), vt_us); +} + +/**************************************************************************** + * Tx Subclass implementation for NR + ***************************************************************************/ -const char* rlc_um::rlc_um_tx::get_rb_name() +rlc_um::rlc_um_tx_nr::rlc_um_tx_nr(srslte::log* log_) : rlc_um_tx_base(log_) {} + +bool rlc_um::rlc_um_tx_nr::configure(rlc_config_t cnfg_, std::string rb_name_) { - return rb_name.c_str(); + cfg = cnfg_; + + if (cfg.um_nr.mod == 0) { + log->error("Error configuring %s RLC UM: tx_mod==0\n", get_rb_name()); + return false; + } + + tx_sdu_queue.resize(cnfg_.tx_queue_length); + + rb_name = rb_name_; + tx_enabled = true; + + return true; } +int rlc_um::rlc_um_tx_nr::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) +{ + std::lock_guard lock(mutex); + rlc_um_nr_pdu_header_t header = {}; + header.si = rlc_nr_si_field_t::full_sdu; + header.sn = TX_Next; + header.sn_size = cfg.um_nr.sn_field_length; -void rlc_um::rlc_um_tx::debug_state() + uint32_t to_move = 0; + uint8_t* pdu_ptr = pdu->msg; + + int head_len = rlc_um_nr_packed_length(header); + int pdu_space = SRSLTE_MIN(nof_bytes, pdu->get_tailroom()); + + if (pdu_space <= head_len + 1) { + log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n", + get_rb_name(), + nof_bytes, + head_len); + return 0; + } + + // Check for SDU segment + if (tx_sdu) { + uint32_t space = pdu_space - head_len; + to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; + log->debug( + "%s adding remainder of SDU segment - %d bytes of %d remaining\n", get_rb_name(), to_move, tx_sdu->N_bytes); + memcpy(pdu_ptr, tx_sdu->msg, to_move); + pdu_ptr += to_move; + pdu->N_bytes += to_move; + tx_sdu->N_bytes -= to_move; + tx_sdu->msg += to_move; + if (tx_sdu->N_bytes == 0) { + log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", get_rb_name(), tx_sdu->get_latency_us()); + tx_sdu.reset(); + header.si = rlc_nr_si_field_t::last_segment; + } else { + header.si = rlc_nr_si_field_t::neither_first_nor_last_segment; + } + pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom()); + } else { + // Pull SDU from queue + log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len); + + head_len = rlc_um_nr_packed_length(header); + tx_sdu = tx_sdu_queue.read(); + uint32_t space = pdu_space - head_len; + to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; + log->debug("%s adding new SDU - %d bytes of %d remaining\n", get_rb_name(), to_move, tx_sdu->N_bytes); + memcpy(pdu_ptr, tx_sdu->msg, to_move); + pdu_ptr += to_move; + pdu->N_bytes += to_move; + tx_sdu->N_bytes -= to_move; + tx_sdu->msg += to_move; + if (tx_sdu->N_bytes == 0) { + log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", get_rb_name(), tx_sdu->get_latency_us()); + tx_sdu.reset(); + header.si = rlc_nr_si_field_t::full_sdu; + } else { + header.si = rlc_nr_si_field_t::first_segment; + } + pdu_space -= to_move; + } + + // Update SN if needed + if (header.si == rlc_nr_si_field_t::last_segment) { + TX_Next = (TX_Next + 1) % cfg.um_nr.mod; + } + + // Add header and TX + rlc_um_nr_write_data_pdu_header(header, pdu.get()); + memcpy(payload, pdu->msg, pdu->N_bytes); + uint32_t ret = pdu->N_bytes; + + log->info_hex(payload, ret, "%s Tx PDU SN=%d (%d B)\n", get_rb_name(), header.sn, pdu->N_bytes); + + debug_state(); + + num_tx_bytes += ret; + + return ret; +} + +void rlc_um::rlc_um_tx_nr::debug_state() { - log->debug("%s vt_us = %d\n", get_rb_name(), vt_us); + log->debug("%s TX_Next = %d\n", get_rb_name(), TX_Next); } /**************************************************************************** @@ -493,16 +617,12 @@ rlc_um::rlc_um_rx::rlc_um_rx(srslte::log* log_, { reordering_timer_id = timers->get_unique_id(); reordering_timer = timers->get(reordering_timer_id); - - pthread_mutex_init(&mutex, NULL); } rlc_um::rlc_um_rx::~rlc_um_rx() { reordering_timer->stop(); timers->release_id(reordering_timer_id); - - pthread_mutex_destroy(&mutex); } void rlc_um::rlc_um_rx::reestablish() @@ -515,22 +635,17 @@ void rlc_um::rlc_um_rx::reestablish() } } - pthread_mutex_lock(&mutex); + std::lock_guard lock(mutex); reset(); rx_enabled = true; - pthread_mutex_unlock(&mutex); } void rlc_um::rlc_um_rx::stop() { - pthread_mutex_lock(&mutex); - + std::lock_guard lock(mutex); reset(); - reordering_timer->stop(); - - pthread_mutex_unlock(&mutex); } void rlc_um::rlc_um_rx::reset() @@ -550,7 +665,7 @@ void rlc_um::rlc_um_rx::reset() void rlc_um::rlc_um_rx::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes) { - pthread_mutex_lock(&mutex); + std::lock_guard lock(mutex); rlc_umd_pdu_t pdu; int header_len = 0; @@ -558,34 +673,33 @@ void rlc_um::rlc_um_rx::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes) rlc_umd_pdu_header_t header; if (!rx_enabled) { - goto unlock_and_exit; + return; } num_rx_bytes += nof_bytes; - rlc_um_read_data_pdu_header(payload, nof_bytes, cfg.rx_sn_field_length, &header); + rlc_um_read_data_pdu_header(payload, nof_bytes, cfg.um.rx_sn_field_length, &header); log->info_hex(payload, nof_bytes, "RX %s Rx data PDU SN: %d (%d B)", get_rb_name(), header.sn, nof_bytes); - if(RX_MOD_BASE(header.sn) >= RX_MOD_BASE(vr_uh-cfg.rx_window_size) && - RX_MOD_BASE(header.sn) < RX_MOD_BASE(vr_ur)) - { + if (RX_MOD_BASE(header.sn) >= RX_MOD_BASE(vr_uh - cfg.um.rx_window_size) && + RX_MOD_BASE(header.sn) < RX_MOD_BASE(vr_ur)) { log->info("%s SN: %d outside rx window [%d:%d] - discarding\n", get_rb_name(), header.sn, vr_ur, vr_uh); - goto unlock_and_exit; + return; } it = rx_window.find(header.sn); if(rx_window.end() != it) { log->info("%s Discarding duplicate SN: %d\n", get_rb_name(), header.sn); - goto unlock_and_exit; + return; } // Write to rx window pdu.buf = allocate_unique_buffer(*pool); if (!pdu.buf) { log->error("Discarting packet: no space in buffer pool\n"); - goto unlock_and_exit; + return; } memcpy(pdu.buf->msg, payload, nof_bytes); pdu.buf->N_bytes = nof_bytes; @@ -598,7 +712,7 @@ void rlc_um::rlc_um_rx::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes) // Update vr_uh if(!inside_reordering_window(header.sn)) { - vr_uh = (header.sn + 1)%cfg.rx_mod; + vr_uh = (header.sn + 1) % cfg.um.rx_mod; } // Reassemble and deliver SDUs, while updating vr_ur @@ -623,9 +737,6 @@ void rlc_um::rlc_um_rx::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes) } debug_state(); - -unlock_and_exit: - pthread_mutex_unlock(&mutex); } @@ -669,13 +780,14 @@ void rlc_um::rlc_um_rx::reassemble_rx_sdus() rx_sdu->N_bytes += len; rx_window[vr_ur].buf->msg += len; rx_window[vr_ur].buf->N_bytes -= len; - if((pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) || (vr_ur != ((vr_ur_in_rx_sdu+1)%cfg.rx_mod))) { + if ((pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) || + (vr_ur != ((vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod))) { log->warning("Dropping remainder of lost PDU (lower edge middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n", vr_ur, vr_ur_in_rx_sdu); rx_sdu->clear(); } else { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d, i=%d (lower edge middle segments)", get_rb_name(), vr_ur, i); rx_sdu->set_timestamp(); - if(cfg.is_mrb){ + if (cfg.um.is_mrb) { pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); } else { pdcp->write_pdu(lcid, std::move(rx_sdu)); @@ -705,7 +817,7 @@ void rlc_um::rlc_um_rx::reassemble_rx_sdus() } else { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", get_rb_name(), vr_ur); rx_sdu->set_timestamp(); - if(cfg.is_mrb){ + if (cfg.um.is_mrb) { pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); } else { pdcp->write_pdu(lcid, std::move(rx_sdu)); @@ -724,7 +836,7 @@ void rlc_um::rlc_um_rx::reassemble_rx_sdus() rx_window.erase(vr_ur); } - vr_ur = (vr_ur + 1)%cfg.rx_mod; + vr_ur = (vr_ur + 1) % cfg.um.rx_mod; } // Now update vr_ur until we reach an SN we haven't yet received @@ -772,8 +884,17 @@ void rlc_um::rlc_um_rx::reassemble_rx_sdus() log->info("Updating vr_ur_in_rx_sdu. old=%d, new=%d\n", vr_ur_in_rx_sdu, vr_ur); vr_ur_in_rx_sdu = vr_ur; } else { - log->info_hex(rx_window[vr_ur].buf->msg, len, "Concatenating %d bytes in to current length %d. rx_window remaining bytes=%d, vr_ur_in_rx_sdu=%d, vr_ur=%d, rx_mod=%d, last_mod=%d\n", - len, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes, vr_ur_in_rx_sdu, vr_ur, cfg.rx_mod, (vr_ur_in_rx_sdu+1)%cfg.rx_mod); + log->info_hex(rx_window[vr_ur].buf->msg, + len, + "Concatenating %d bytes in to current length %d. rx_window remaining bytes=%d, " + "vr_ur_in_rx_sdu=%d, vr_ur=%d, rx_mod=%d, last_mod=%d\n", + len, + rx_sdu->N_bytes, + rx_window[vr_ur].buf->N_bytes, + vr_ur_in_rx_sdu, + vr_ur, + cfg.um.rx_mod, + (vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod); } memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); @@ -785,7 +906,7 @@ void rlc_um::rlc_um_rx::reassemble_rx_sdus() if (pdu_belongs_to_rx_sdu()) { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d, i=%d, (update vr_ur middle segments)", get_rb_name(), vr_ur, i); rx_sdu->set_timestamp(); - if(cfg.is_mrb){ + if (cfg.um.is_mrb) { pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); } else { pdcp->write_pdu(lcid, std::move(rx_sdu)); @@ -831,7 +952,7 @@ void rlc_um::rlc_um_rx::reassemble_rx_sdus() } else { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (update vr_ur last segments)", get_rb_name(), vr_ur); rx_sdu->set_timestamp(); - if(cfg.is_mrb){ + if (cfg.um.is_mrb) { pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); } else { pdcp->write_pdu(lcid, std::move(rx_sdu)); @@ -849,7 +970,7 @@ clean_up_rx_window: // Clean up rx_window rx_window.erase(vr_ur); - vr_ur = (vr_ur + 1)%cfg.rx_mod; + vr_ur = (vr_ur + 1) % cfg.um.rx_mod; } } @@ -858,7 +979,7 @@ clean_up_rx_window: bool rlc_um::rlc_um_rx::pdu_belongs_to_rx_sdu() { // return true if the currently received SDU - if (((vr_ur_in_rx_sdu + 1)%cfg.rx_mod == vr_ur) || (vr_ur == vr_ur_in_rx_sdu)) { + if (((vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod == vr_ur) || (vr_ur == vr_ur_in_rx_sdu)) { return true; } return false; @@ -869,10 +990,10 @@ bool rlc_um::rlc_um_rx::pdu_belongs_to_rx_sdu() // 36.322 Section 5.1.2.2.1 bool rlc_um::rlc_um_rx::inside_reordering_window(uint16_t sn) { - if (cfg.rx_window_size == 0 || rx_window.empty()) { + if (cfg.um.rx_window_size == 0 || rx_window.empty()) { return true; } - if (RX_MOD_BASE(vr_uh-cfg.rx_window_size) <= RX_MOD_BASE(sn) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_uh)) { + if (RX_MOD_BASE(vr_uh - cfg.um.rx_window_size) <= RX_MOD_BASE(sn) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_uh)) { return true; } else { return false; @@ -888,9 +1009,8 @@ uint32_t rlc_um::rlc_um_rx::get_num_rx_bytes() void rlc_um::rlc_um_rx::reset_metrics() { - pthread_mutex_lock(&mutex); + std::lock_guard lock(mutex); num_rx_bytes = 0; - pthread_mutex_unlock(&mutex); } @@ -900,7 +1020,7 @@ void rlc_um::rlc_um_rx::reset_metrics() void rlc_um::rlc_um_rx::timer_expired(uint32_t timeout_id) { - pthread_mutex_lock(&mutex); + std::lock_guard lock(mutex); if (reordering_timer != NULL && reordering_timer_id == timeout_id) { // 36.322 v10 Section 5.1.2.2.4 log->info("%s reordering timeout expiry - updating vr_ur and reassembling\n", @@ -914,7 +1034,7 @@ void rlc_um::rlc_um_rx::timer_expired(uint32_t timeout_id) } while(RX_MOD_BASE(vr_ur) < RX_MOD_BASE(vr_ux)) { - vr_ur = (vr_ur + 1)%cfg.rx_mod; + vr_ur = (vr_ur + 1) % cfg.um.rx_mod; log->debug("Entering Reassemble from timeout id=%d\n", timeout_id); reassemble_rx_sdus(); log->debug("Finished reassemble from timeout id=%d\n", timeout_id); @@ -928,7 +1048,6 @@ void rlc_um::rlc_um_rx::timer_expired(uint32_t timeout_id) debug_state(); } - pthread_mutex_unlock(&mutex); } /**************************************************************************** @@ -1075,4 +1194,128 @@ bool rlc_um_end_aligned(uint8_t fi) return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED); } +/**************************************************************************** + * Header pack/unpack helper functions + * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3 + ***************************************************************************/ + +uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header) +{ + return rlc_um_nr_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header); +} + +uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload, + const uint32_t nof_bytes, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header) +{ + uint8_t* ptr = const_cast(payload); + + header->sn_size = sn_size; + + // Fixed part + if (sn_size == rlc_um_nr_sn_size_t::size6bits) { + header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI + header->sn = *ptr & 0x3F; // 6 bits SN + // sanity check + if (header->si == rlc_nr_si_field_t::full_sdu and not header->sn == 0) { + fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); + return 0; + } + ptr++; + } else if (sn_size == rlc_um_nr_sn_size_t::size12bits) { + header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI + header->sn = (*ptr & 0x0F) << 4; // 4 bits SN + + // sanity check + if (header->si == rlc_nr_si_field_t::first_segment) { + // make sure two reserved bits are not set + if (((*ptr >> 4) & 0x03) != 0) { + fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); + return 0; + } + } + + // continue unpacking remaining SN + ptr++; + header->sn |= (*ptr & 0xFF); // 8 bits SN + ptr++; + } else { + fprintf(stderr, "Unsupported SN length\n"); + return 0; + } + + // Read optional part + if (header->si == rlc_nr_si_field_t::last_segment || + header->si == rlc_nr_si_field_t::neither_first_nor_last_segment) { + // read SO + header->so = (*ptr & 0xFF) << 8; + ptr++; + header->so |= (*ptr & 0xFF); + ptr++; + } + + // return consumed bytes + return (ptr - payload); +} + +uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header) +{ + uint32_t len = 0; + if (header.si == rlc_nr_si_field_t::full_sdu || header.si == rlc_nr_si_field_t::first_segment) { + len = 1; + if (header.sn_size == rlc_um_nr_sn_size_t::size12bits) { + len++; + } + } else { + if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) { + len = 3; + } else { + len = 4; + } + } + return len; +} + +uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu) +{ + // Make room for the header + uint32_t len = rlc_um_nr_packed_length(header); + pdu->msg -= len; + uint8_t* ptr = pdu->msg; + + // write SI field + *ptr = (header.si & 0x03) << 6; // 2 bits SI + + if (header.si == rlc_nr_si_field_t::full_sdu) { + // that's all .. + ptr++; + } else { + if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) { + // write SN + *ptr |= (header.sn & 0x3f); // 6 bit SN + ptr++; + } else { + // 12bit SN + *ptr |= (header.sn & 0xf); // 4 bit SN + ptr++; + *ptr = (header.sn & 0xFF); // remaining 8 bit SN + ptr++; + } + if (header.so) { + // write SO + *ptr = (header.so) >> 8; // first part of SO + ptr++; + *ptr = (header.so & 0xFF); // second part of SO + ptr++; + } + } + + pdu->N_bytes += ptr - pdu->msg; + + return len; +} + } // namespace srslte diff --git a/lib/test/upper/CMakeLists.txt b/lib/test/upper/CMakeLists.txt index a89512b3e..797b963a5 100644 --- a/lib/test/upper/CMakeLists.txt +++ b/lib/test/upper/CMakeLists.txt @@ -51,6 +51,14 @@ add_executable(rlc_common_test rlc_common_test.cc) target_link_libraries(rlc_common_test srslte_upper srslte_phy) add_test(rlc_common_test rlc_common_test) +add_executable(rlc_um_nr_pdu_test rlc_um_nr_pdu_test.cc) +target_link_libraries(rlc_um_nr_pdu_test srslte_upper srslte_phy) +add_test(rlc_um_nr_pdu_test rlc_um_nr_pdu_test) + +add_executable(rlc_um_nr_test rlc_um_nr_test.cc) +target_link_libraries(rlc_um_nr_test srslte_upper srslte_phy) +add_test(rlc_um_nr_test rlc_um_nr_test) + add_executable(pdcp_nr_test pdcp_nr_test.cc) target_link_libraries(pdcp_nr_test srslte_upper srslte_common) add_test(pdcp_nr_test pdcp_nr_test) diff --git a/lib/test/upper/rlc_test_common.h b/lib/test/upper/rlc_test_common.h new file mode 100644 index 000000000..92ae0e9b6 --- /dev/null +++ b/lib/test/upper/rlc_test_common.h @@ -0,0 +1,63 @@ +/* + * 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 SRSLTE_RLC_TEST_COMMON_H +#define SRSLTE_RLC_TEST_COMMON_H + +#include "srslte/interfaces/ue_interfaces.h" +#include + +namespace srslte { + +class rlc_um_tester : public srsue::pdcp_interface_rlc, public srsue::rrc_interface_rlc +{ +public: + rlc_um_tester() {} + + // PDCP interface + void write_pdu(uint32_t lcid, unique_byte_buffer_t sdu) + { + if (lcid != 3 && sdu->N_bytes != expected_sdu_len) { + printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, expected_sdu_len); + exit(-1); + } + sdus.push_back(std::move(sdu)); + } + void write_pdu_bcch_bch(unique_byte_buffer_t sdu) {} + void write_pdu_bcch_dlsch(unique_byte_buffer_t sdu) {} + void write_pdu_pcch(unique_byte_buffer_t sdu) {} + void write_pdu_mch(uint32_t lcid, srslte::unique_byte_buffer_t sdu) { sdus.push_back(std::move(sdu)); } + + // RRC interface + void max_retx_attempted() {} + std::string get_rb_name(uint32_t lcid) { return std::string(""); } + void set_expected_sdu_len(uint32_t len) { expected_sdu_len = len; } + + uint32_t get_num_sdus() { return sdus.size(); } + + // TODO: this should be private + std::vector sdus; + uint32_t expected_sdu_len = 0; +}; + +} // namespace srslte + +#endif // SRSLTE_RLC_TEST_COMMON_H diff --git a/lib/test/upper/rlc_um_nr_pdu_test.cc b/lib/test/upper/rlc_um_nr_pdu_test.cc new file mode 100644 index 000000000..730b1bb4c --- /dev/null +++ b/lib/test/upper/rlc_um_nr_pdu_test.cc @@ -0,0 +1,240 @@ +/* + * 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/config.h" +#include "srslte/upper/rlc.h" +#include "srslte/upper/rlc_um.h" + +#include +#include +#include +#include + +#define TESTASSERT(cond) \ + { \ + if (!(cond)) { \ + std::cout << "[" << __FUNCTION__ << "][Line " << __LINE__ << "]: FAIL at " << (#cond) << std::endl; \ + return -1; \ + } \ + } + +#define PCAP 0 +#define PCAP_CRNTI (0x1001) +#define PCAP_TTI (666) + +using namespace srslte; + +#if PCAP +#include "srslte/common/mac_nr_pcap.h" +#include "srslte/common/mac_nr_pdu.h" +static std::unique_ptr pcap_handle = nullptr; +#endif + +int write_pdu_to_pcap(const uint32_t lcid, const uint8_t* payload, const uint32_t len) +{ +#if PCAP + if (pcap_handle) { + byte_buffer_t tx_buffer; + srslte::mac_nr_pdu tx_pdu; + tx_pdu.init_tx(&tx_buffer, len + 10); + tx_pdu.add_sdu(lcid, payload, len); + tx_pdu.pack(); + pcap_handle->write_dl_crnti(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); + return SRSLTE_SUCCESS; + } +#endif + return SRSLTE_ERROR; +} + +template +srslte::byte_buffer_t make_pdu_and_log(const std::array& tv) +{ + srslte::byte_buffer_t pdu; + memcpy(pdu.msg, tv.data(), tv.size()); + pdu.N_bytes = tv.size(); + write_pdu_to_pcap(4, tv.data(), tv.size()); + return pdu; +} + +void corrupt_pdu_header(srslte::byte_buffer_t& pdu, const uint32_t header_len, const uint32_t payload_len) +{ + // clear header only + for (uint32_t i = 0; i < header_len; i++) { + pdu.msg[i] = 0xaa; + } + pdu.msg += header_len; + pdu.N_bytes = payload_len; +} + +// RLC UM PDU with complete SDU +int rlc_um_nr_pdu_test1() +{ + const int header_len = 1, payload_len = 4; + std::array tv = {0x00, 0x11, 0x22, 0x33, 0x44}; + srslte::byte_buffer_t pdu = make_pdu_and_log(tv); + + // unpack PDU + rlc_um_nr_pdu_header_t header = {}; + TESTASSERT(rlc_um_nr_read_data_pdu_header(&pdu, srslte::rlc_um_nr_sn_size_t::size6bits, &header) != 0); + TESTASSERT(header.si == rlc_nr_si_field_t::full_sdu); + + // clear header + corrupt_pdu_header(pdu, header_len, payload_len); + + // pack again + TESTASSERT(rlc_um_nr_write_data_pdu_header(header, &pdu) == header_len); + TESTASSERT(pdu.N_bytes == tv.size()); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSLTE_SUCCESS; +} + +// RLC UM PDU with 6 Bit SN carrying the last segment of an SDU +int rlc_um_nr_pdu_test2() +{ + // SN = 1 + const int header_len = 3, payload_len = 4; + const std::array tv = {0x81, 0x01, 0x02, 0x11, 0x22, 0x33, 0x44}; + srslte::byte_buffer_t pdu = make_pdu_and_log(tv); + + // unpack PDU + rlc_um_nr_pdu_header_t header = {}; + TESTASSERT(rlc_um_nr_read_data_pdu_header(&pdu, srslte::rlc_um_nr_sn_size_t::size6bits, &header) != 0); + + TESTASSERT(header.si == rlc_nr_si_field_t::last_segment); + TESTASSERT(header.sn == 1); + TESTASSERT(header.so == 258); + TESTASSERT(header.sn_size == srslte::rlc_um_nr_sn_size_t::size6bits); + + // clear header + corrupt_pdu_header(pdu, header_len, payload_len); + + // pack again + TESTASSERT(rlc_um_nr_write_data_pdu_header(header, &pdu) == header_len); + TESTASSERT(pdu.N_bytes == tv.size()); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSLTE_SUCCESS; +} + +// RLC UM PDU with 6 Bit SN carrying a middle segment of an SDU +int rlc_um_nr_pdu_test3() +{ + // SN = 3 + const int header_len = 3, payload_len = 4; + const std::array tv = {0xc3, 0x01, 0x02, 0x11, 0x22, 0x33, 0x44}; + srslte::byte_buffer_t pdu = make_pdu_and_log(tv); + + // unpack PDU + rlc_um_nr_pdu_header_t header = {}; + TESTASSERT(rlc_um_nr_read_data_pdu_header(&pdu, srslte::rlc_um_nr_sn_size_t::size6bits, &header) != 0); + + TESTASSERT(header.si == rlc_nr_si_field_t::neither_first_nor_last_segment); + TESTASSERT(header.so == 258); + TESTASSERT(header.sn == 3); + TESTASSERT(header.sn_size == srslte::rlc_um_nr_sn_size_t::size6bits); + + // clear header + corrupt_pdu_header(pdu, header_len, payload_len); + + // pack again + TESTASSERT(rlc_um_nr_write_data_pdu_header(header, &pdu) == header_len); + TESTASSERT(pdu.N_bytes == tv.size()); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSLTE_SUCCESS; +} + +// RLC UM PDU with 6 Bit SN carrying a first segment of an SDU +int rlc_um_nr_pdu_test4() +{ + // SN = 31 + const int header_len = 1, payload_len = 4; + std::array tv = {0x5f, 0x11, 0x22, 0x33, 0x44}; + srslte::byte_buffer_t pdu = make_pdu_and_log(tv); + + // unpack PDU + rlc_um_nr_pdu_header_t header = {}; + TESTASSERT(rlc_um_nr_read_data_pdu_header(&pdu, srslte::rlc_um_nr_sn_size_t::size6bits, &header) != 0); + + TESTASSERT(header.si == rlc_nr_si_field_t::first_segment); + TESTASSERT(header.so == 0); + TESTASSERT(header.sn == 31); + TESTASSERT(header.sn_size == srslte::rlc_um_nr_sn_size_t::size6bits); + + // clear header + corrupt_pdu_header(pdu, header_len, payload_len); + + // pack again + TESTASSERT(rlc_um_nr_write_data_pdu_header(header, &pdu) == header_len); + TESTASSERT(pdu.N_bytes == tv.size()); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSLTE_SUCCESS; +} + +// This should fail unpacking because the PDU has reserved bits set +int rlc_um_nr_pdu_unpack_test5() +{ + std::array tv = {0x33, 0x01, 0x02, 0x11, 0x22, 0x33, 0x44}; + srslte::byte_buffer_t pdu = make_pdu_and_log(tv); + + // unpack PDU + rlc_um_nr_pdu_header_t header = {}; + TESTASSERT(rlc_um_nr_read_data_pdu_header(&pdu, srslte::rlc_um_nr_sn_size_t::size6bits, &header) == 0); + + return SRSLTE_SUCCESS; +} + +int main(int argc, char** argv) +{ +#if PCAP + pcap_handle = std::unique_ptr(new srslte::mac_nr_pcap()); + pcap_handle->open("rlc_um_nr_pdu_test.pcap"); +#endif + + if (rlc_um_nr_pdu_test1()) { + fprintf(stderr, "rlc_um_nr_pdu_test1() failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_pdu_test2()) { + fprintf(stderr, "rlc_um_nr_pdu_test2() failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_pdu_test3()) { + fprintf(stderr, "rlc_um_nr_pdu_test3() failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_pdu_test4()) { + fprintf(stderr, "rlc_um_nr_pdu_test4() failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_pdu_unpack_test5()) { + fprintf(stderr, "rlc_um_nr_pdu_unpack_test5() failed.\n"); + return SRSLTE_ERROR; + } + + return SRSLTE_SUCCESS; +} diff --git a/lib/test/upper/rlc_um_nr_test.cc b/lib/test/upper/rlc_um_nr_test.cc new file mode 100644 index 000000000..abd222ec0 --- /dev/null +++ b/lib/test/upper/rlc_um_nr_test.cc @@ -0,0 +1,210 @@ +/* + * 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 "rlc_test_common.h" +#include "srslte/common/log_filter.h" +#include "srslte/config.h" +#include "srslte/upper/rlc.h" +#include "srslte/upper/rlc_um.h" + +#include +#include +#include +#include + +#define TESTASSERT(cond) \ + { \ + if (!(cond)) { \ + std::cout << "[" << __FUNCTION__ << "][Line " << __LINE__ << "]: FAIL at " << (#cond) << std::endl; \ + return -1; \ + } \ + } + +#define PCAP 0 +#define PCAP_CRNTI (0x1001) +#define PCAP_TTI (666) + +using namespace srslte; + +#if PCAP +#include "srslte/common/mac_nr_pcap.h" +#include "srslte/common/mac_nr_pdu.h" +static std::unique_ptr pcap_handle = nullptr; +#endif + +int write_pdu_to_pcap(const uint32_t lcid, const uint8_t* payload, const uint32_t len) +{ +#if PCAP + if (pcap_handle) { + byte_buffer_t tx_buffer; + srslte::nr_mac_pdu tx_pdu; + tx_pdu.init_tx(&tx_buffer, len + 10); + tx_pdu.add_sdu(lcid, payload, len); + tx_pdu.pack(); + pcap_handle->write_dl_crnti(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); + return SRSLTE_SUCCESS; + } +#endif + return SRSLTE_ERROR; +} + +template +srslte::byte_buffer_t make_pdu_and_log(const std::array& tv) +{ + srslte::byte_buffer_t pdu; + memcpy(pdu.msg, tv.data(), tv.size()); + pdu.N_bytes = tv.size(); + write_pdu_to_pcap(4, tv.data(), tv.size()); + return pdu; +} + +// Basic test to write UM PDU with 6 bit SN +int rlc_um_nr_test1() +{ + srslte::log_filter log1("RLC_UM_1"); + srslte::log_filter log2("RLC_UM_2"); + log1.set_level(srslte::LOG_LEVEL_DEBUG); + log2.set_level(srslte::LOG_LEVEL_DEBUG); + log1.set_hex_limit(-1); + log2.set_hex_limit(-1); + rlc_um_tester tester; + srslte::timers timers(16); + const uint32_t num_sdus = 5; + int len = 0; + + rlc_um rlc1(&log1, 3, &tester, &tester, &timers); + rlc_um rlc2(&log2, 3, &tester, &tester, &timers); + + rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6); + + TESTASSERT(rlc1.configure(cnfg) == true); + TESTASSERT(rlc2.configure(cnfg) == true); + + tester.set_expected_sdu_len(1); + + // Push 5 SDUs into RLC1 + byte_buffer_pool* pool = byte_buffer_pool::get_instance(); + unique_byte_buffer_t sdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + *sdu_bufs[i]->msg = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + TESTASSERT(14 == rlc1.get_buffer_state()); + + // Read 5 PDUs from RLC1 (1 byte each) + unique_byte_buffer_t pdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + pdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + len = rlc1.read_pdu(pdu_bufs[i]->msg, 4); // 3 bytes for header + payload + pdu_bufs[i]->N_bytes = len; + + // write PCAP + write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + + TESTASSERT(0 == rlc1.get_buffer_state()); + + // TODO: add receive test + + return SRSLTE_SUCCESS; +} + +// Basic test for SDU segmentation +int rlc_um_nr_test2() +{ + srslte::log_filter log1("RLC_UM_1"); + srslte::log_filter log2("RLC_UM_2"); + log1.set_level(srslte::LOG_LEVEL_DEBUG); + log2.set_level(srslte::LOG_LEVEL_DEBUG); + log1.set_hex_limit(-1); + log2.set_hex_limit(-1); + rlc_um_tester tester; + srslte::timers timers(16); + const uint32_t num_sdus = 1; + const uint32_t sdu_size = 100; + int len = 0; + + rlc_um rlc1(&log1, 3, &tester, &tester, &timers); + rlc_um rlc2(&log2, 3, &tester, &tester, &timers); + + rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6); + + TESTASSERT(rlc1.configure(cnfg) == true); + TESTASSERT(rlc2.configure(cnfg) == true); + + tester.set_expected_sdu_len(1); + + // Push SDUs into RLC1 + byte_buffer_pool* pool = byte_buffer_pool::get_instance(); + unique_byte_buffer_t sdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + // Write the index into the buffer + for (uint32_t k = 0; k < sdu_size; ++k) { + sdu_bufs[i]->msg[k] = i; + } + sdu_bufs[i]->N_bytes = sdu_size; + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // FIXME: check buffer state calculation + TESTASSERT(103 == rlc1.get_buffer_state()); + + // Read PDUs from RLC1 with grant of 25 Bytes each + const uint32_t max_num_pdus = 10; + unique_byte_buffer_t pdu_bufs[max_num_pdus]; + for (uint32_t i = 0; i < max_num_pdus; i++) { + pdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + len = rlc1.read_pdu(pdu_bufs[i]->msg, 25); // 3 bytes for header + payload + pdu_bufs[i]->N_bytes = len; + + // write PCAP + write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + + TESTASSERT(0 == rlc1.get_buffer_state()); + + // TODO: add receive test + + return SRSLTE_SUCCESS; +} + +int main(int argc, char** argv) +{ +#if PCAP + pcap_handle = std::unique_ptr(new srslte::mac_nr_pcap()); + pcap_handle->open("rlc_um_nr_test.pcap"); +#endif + + if (rlc_um_nr_test1()) { + fprintf(stderr, "rlc_um_nr_test1() failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_test2()) { + fprintf(stderr, "rlc_um_nr_test2() failed.\n"); + return SRSLTE_ERROR; + } + return SRSLTE_SUCCESS; +} diff --git a/lib/test/upper/rlc_um_test.cc b/lib/test/upper/rlc_um_test.cc index 6f7190683..babd7e5eb 100644 --- a/lib/test/upper/rlc_um_test.cc +++ b/lib/test/upper/rlc_um_test.cc @@ -19,9 +19,10 @@ * */ -#include +#include "rlc_test_common.h" #include "srslte/common/log_filter.h" #include "srslte/upper/rlc_um.h" +#include #define TESTASSERT(cond) \ { \ @@ -38,43 +39,6 @@ using namespace srslte; using namespace srsue; using namespace asn1::rrc; -class rlc_um_tester : public pdcp_interface_rlc, public rrc_interface_rlc -{ -public: - rlc_um_tester(){ - bzero(sdus, sizeof(sdus)); - n_sdus = 0; - expected_sdu_len = 0; - } - - // PDCP interface - void write_pdu(uint32_t lcid, unique_byte_buffer_t sdu) - { - if (lcid != 3 && sdu->N_bytes != expected_sdu_len) { - printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, expected_sdu_len); - exit(-1); - } - sdus[n_sdus++] = std::move(sdu); - } - void write_pdu_bcch_bch(unique_byte_buffer_t sdu) {} - void write_pdu_bcch_dlsch(unique_byte_buffer_t sdu) {} - void write_pdu_pcch(unique_byte_buffer_t sdu) {} - void write_pdu_mch(uint32_t lcid, srslte::unique_byte_buffer_t sdu) - { - sdus[n_sdus++] = std::move(sdu); - } - - // RRC interface - void max_retx_attempted(){} - std::string get_rb_name(uint32_t lcid) { return std::string(""); } - void set_expected_sdu_len(uint32_t len) { expected_sdu_len = len; } - - unique_byte_buffer_t sdus[MAX_NBUFS]; - int n_sdus; - uint32_t expected_sdu_len; -}; - - int basic_test() { srslte::log_filter log1("RLC_UM_1"); @@ -135,10 +99,9 @@ int basic_test() TESTASSERT(0 == rlc2.get_buffer_state()); - TESTASSERT(NBUFS == tester.n_sdus); - for(int i=0; iN_bytes == 1); + TESTASSERT(NBUFS == tester.get_num_sdus()); + for (uint32_t i = 0; i < tester.sdus.size(); i++) { + TESTASSERT(tester.sdus.at(i)->N_bytes == 1); TESTASSERT(*(tester.sdus[i]->msg) == i); } @@ -201,7 +164,7 @@ int loss_test() while (!timers.get(1)->is_expired()) timers.get(1)->step(); - TESTASSERT(NBUFS - 1 == tester.n_sdus); + TESTASSERT(NBUFS - 1 == tester.sdus.size()); return 0; } @@ -257,9 +220,8 @@ int basic_mbsfn_test() TESTASSERT(0 == rlc2.get_buffer_state()); - TESTASSERT(NBUFS == tester.n_sdus); - for(int i=0; iN_bytes == 1); TESTASSERT(*(tester.sdus[i]->msg) == i); } @@ -372,8 +334,8 @@ int reassmble_test() } // We should have received one SDU less than we tx'ed - TESTASSERT(tester.n_sdus == n_sdus - 1); - for (int i = 0; i < tester.n_sdus; ++i) { + TESTASSERT(tester.sdus.size() == n_sdus - 1); + for (uint32_t i = 0; i < tester.sdus.size(); ++i) { TESTASSERT(tester.sdus[i]->N_bytes == sdu_len); } @@ -476,8 +438,8 @@ int reassmble_test2() } // We should have received one SDU less than we tx'ed - TESTASSERT(tester.n_sdus == n_sdus - 1); - for (int i = 0; i < tester.n_sdus; ++i) { + TESTASSERT(tester.sdus.size() == n_sdus - 1); + for (uint32_t i = 0; i < tester.sdus.size(); ++i) { TESTASSERT(tester.sdus[i]->N_bytes == sdu_len); }