refactor RLC UM and add NR receiver

master
Andre Puschmann 5 years ago
parent a9a33256e9
commit b20f7ba541

@ -257,21 +257,22 @@ struct rlc_um_nr_config_t {
rlc_um_nr_sn_size_t sn_field_length; // Number of bits used for sequence number rlc_um_nr_sn_size_t sn_field_length; // Number of bits used for sequence number
uint32_t UM_Window_Size; uint32_t UM_Window_Size;
uint32_t mod; // Rx/Tx counter modulus uint32_t mod; // Rx/Tx counter modulus
int32_t t_reassembly_ms; // Timer used by rx to detect PDU loss (ms)
}; };
#define RLC_TX_QUEUE_LEN (128) #define RLC_TX_QUEUE_LEN (128)
enum class rlc_type_t { lte, nr, nulltype }; enum class srslte_rat_t { lte, nr, nulltype };
inline std::string to_string(const rlc_type_t& type) inline std::string to_string(const srslte_rat_t& type)
{ {
constexpr static const char* options[] = {"LTE", "NR"}; constexpr static const char* options[] = {"LTE", "NR"};
return enum_to_text(options, (uint32_t)rlc_type_t::nulltype, (uint32_t)type); return enum_to_text(options, (uint32_t)srslte_rat_t::nulltype, (uint32_t)type);
} }
class rlc_config_t class rlc_config_t
{ {
public: public:
rlc_type_t type; srslte_rat_t rat;
rlc_mode_t rlc_mode; rlc_mode_t rlc_mode;
rlc_am_config_t am; rlc_am_config_t am;
rlc_um_config_t um; rlc_um_config_t um;
@ -279,7 +280,7 @@ public:
uint32_t tx_queue_length; uint32_t tx_queue_length;
rlc_config_t() : rlc_config_t() :
type(rlc_type_t::lte), rat(srslte_rat_t::lte),
rlc_mode(rlc_mode_t::tm), rlc_mode(rlc_mode_t::tm),
am(), am(),
um(), um(),
@ -290,7 +291,7 @@ public:
static rlc_config_t mch_config() static rlc_config_t mch_config()
{ {
rlc_config_t cfg = {}; rlc_config_t cfg = {};
cfg.type = rlc_type_t::lte; cfg.rat = srslte_rat_t::lte;
cfg.rlc_mode = rlc_mode_t::um; cfg.rlc_mode = rlc_mode_t::um;
cfg.um.t_reordering = 45; cfg.um.t_reordering = 45;
cfg.um.rx_sn_field_length = rlc_umd_sn_size_t::size5bits; cfg.um.rx_sn_field_length = rlc_umd_sn_size_t::size5bits;
@ -309,7 +310,7 @@ public:
} }
// SRB1 and SRB2 are AM // SRB1 and SRB2 are AM
rlc_config_t rlc_cfg = {}; rlc_config_t rlc_cfg = {};
rlc_cfg.type = rlc_type_t::lte; rlc_cfg.rat = srslte_rat_t::lte;
rlc_cfg.rlc_mode = rlc_mode_t::am; rlc_cfg.rlc_mode = rlc_mode_t::am;
rlc_cfg.am.t_poll_retx = 45; rlc_cfg.am.t_poll_retx = 45;
rlc_cfg.am.poll_pdu = -1; rlc_cfg.am.poll_pdu = -1;
@ -322,7 +323,7 @@ public:
static rlc_config_t default_rlc_um_config(uint32_t sn_size = 10) 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.rat = srslte_rat_t::lte;
cnfg.rlc_mode = rlc_mode_t::um; cnfg.rlc_mode = rlc_mode_t::um;
cnfg.um.t_reordering = 5; cnfg.um.t_reordering = 5;
if (sn_size == 10) { if (sn_size == 10) {
@ -345,7 +346,7 @@ public:
static rlc_config_t default_rlc_am_config() 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.rat = srslte_rat_t::lte;
rlc_cnfg.rlc_mode = rlc_mode_t::am; rlc_cnfg.rlc_mode = rlc_mode_t::am;
rlc_cnfg.am.t_reordering = 5; rlc_cnfg.am.t_reordering = 5;
rlc_cnfg.am.t_status_prohibit = 5; rlc_cnfg.am.t_status_prohibit = 5;
@ -358,7 +359,7 @@ public:
static rlc_config_t default_rlc_um_nr_config(uint32_t sn_size = 6) static rlc_config_t default_rlc_um_nr_config(uint32_t sn_size = 6)
{ {
rlc_config_t cnfg = {}; rlc_config_t cnfg = {};
cnfg.type = rlc_type_t::nr; cnfg.rat = srslte_rat_t::nr;
cnfg.rlc_mode = rlc_mode_t::um; cnfg.rlc_mode = rlc_mode_t::um;
if (sn_size == 6) { if (sn_size == 6) {
cnfg.um_nr.sn_field_length = rlc_um_nr_sn_size_t::size6bits; cnfg.um_nr.sn_field_length = rlc_um_nr_sn_size_t::size6bits;
@ -371,6 +372,7 @@ public:
} else { } else {
return {}; return {};
} }
cnfg.um_nr.t_reassembly_ms = 5; // lowest non-zero value
return cnfg; return cnfg;
} }
}; };

@ -86,8 +86,7 @@ public:
int read_pdu(uint8_t *payload, uint32_t nof_bytes); int read_pdu(uint8_t *payload, uint32_t nof_bytes);
void write_pdu(uint8_t *payload, uint32_t nof_bytes); void write_pdu(uint8_t *payload, uint32_t nof_bytes);
uint32_t get_num_tx_bytes(); rlc_bearer_metrics_t get_metrics();
uint32_t get_num_rx_bytes();
void reset_metrics(); void reset_metrics();
private: private:
@ -283,6 +282,8 @@ private:
// Rx and Tx objects // Rx and Tx objects
rlc_am_lte_tx tx; rlc_am_lte_tx tx;
rlc_am_lte_rx rx; rlc_am_lte_rx rx;
rlc_bearer_metrics_t metrics = {};
}; };
/**************************************************************************** /****************************************************************************

@ -23,6 +23,7 @@
#define SRSLTE_RLC_COMMON_H #define SRSLTE_RLC_COMMON_H
#include "srslte/common/block_queue.h" #include "srslte/common/block_queue.h"
#include "srslte/upper/rlc_metrics.h"
#include <stdlib.h> #include <stdlib.h>
namespace srslte { namespace srslte {
@ -64,6 +65,12 @@ inline std::string to_string(const rlc_nr_si_field_t& si)
return enum_to_text(options, (uint32_t)rlc_nr_si_field_t::nulltype, (uint32_t)si); return enum_to_text(options, (uint32_t)rlc_nr_si_field_t::nulltype, (uint32_t)si);
} }
inline std::string to_string_short(const rlc_nr_si_field_t& si)
{
constexpr static const char* options[] = {"full", "first", "last", "middle"};
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) static inline uint8_t operator&(rlc_nr_si_field_t lhs, int rhs)
{ {
return static_cast<uint8_t>(static_cast<std::underlying_type<rlc_nr_si_field_t>::type>(lhs) & return static_cast<uint8_t>(static_cast<std::underlying_type<rlc_nr_si_field_t>::type>(lhs) &
@ -91,7 +98,7 @@ typedef struct {
rlc_nr_si_field_t si; // Segmentation info rlc_nr_si_field_t si; // Segmentation info
rlc_um_nr_sn_size_t sn_size; // Sequence number size (6 or 12 bits) rlc_um_nr_sn_size_t sn_size; // Sequence number size (6 or 12 bits)
uint16_t sn; // Sequence number uint16_t sn; // Sequence number
uint16_t so; // Sequence offset uint16_t so; // Segment offset
} rlc_um_nr_pdu_header_t; } rlc_um_nr_pdu_header_t;
// AMD PDU Header // AMD PDU Header
@ -137,9 +144,10 @@ struct rlc_amd_pdu_header_t{
lsf = h.lsf; lsf = h.lsf;
so = h.so; so = h.so;
N_li = h.N_li; N_li = h.N_li;
for(uint32_t i=0;i<h.N_li;i++) for (uint32_t i = 0; i < h.N_li; i++) {
li[i] = h.li[i]; li[i] = h.li[i];
} }
}
}; };
// NACK helper // NACK helper
@ -215,8 +223,7 @@ public:
virtual rlc_mode_t get_mode() = 0; virtual rlc_mode_t get_mode() = 0;
virtual uint32_t get_bearer() = 0; virtual uint32_t get_bearer() = 0;
virtual uint32_t get_num_tx_bytes() = 0; virtual rlc_bearer_metrics_t get_metrics() = 0;
virtual uint32_t get_num_rx_bytes() = 0;
virtual void reset_metrics() = 0; virtual void reset_metrics() = 0;
// PDCP interface // PDCP interface

@ -26,12 +26,22 @@
namespace srslte { namespace srslte {
struct rlc_metrics_t typedef struct {
{ uint32_t num_tx_sdus;
float dl_tput_mbps[SRSLTE_N_RADIO_BEARERS]; uint32_t num_rx_sdus;
float ul_tput_mbps[SRSLTE_N_RADIO_BEARERS]; uint32_t num_tx_pdus;
float dl_tput_mrb_mbps[SRSLTE_N_MCH_LCIDS]; uint32_t num_rx_pdus;
}; uint64_t num_tx_bytes;
uint64_t num_rx_bytes;
uint32_t num_lost_pdus;
uint32_t num_dropped_sdus;
} rlc_bearer_metrics_t;
typedef struct {
rlc_bearer_metrics_t bearer[SRSLTE_N_RADIO_BEARERS];
rlc_bearer_metrics_t mrb_bearer[SRSLTE_N_MCH_LCIDS];
} rlc_metrics_t;
} // namespace srslte } // namespace srslte

@ -49,8 +49,7 @@ public:
rlc_mode_t get_mode(); rlc_mode_t get_mode();
uint32_t get_bearer(); uint32_t get_bearer();
uint32_t get_num_tx_bytes(); rlc_bearer_metrics_t get_metrics();
uint32_t get_num_rx_bytes();
void reset_metrics(); void reset_metrics();
// PDCP interface // PDCP interface
@ -71,8 +70,7 @@ private:
bool tx_enabled = true; bool tx_enabled = true;
uint32_t num_tx_bytes = 0; rlc_bearer_metrics_t metrics = {};
uint32_t num_rx_bytes = 0;
// Thread-safe queues for MAC messages // Thread-safe queues for MAC messages
rlc_tx_queue ul_queue; rlc_tx_queue ul_queue;

@ -1,281 +0,0 @@
/*
* 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_UM_H
#define SRSLTE_RLC_UM_H
#include "srslte/common/buffer_pool.h"
#include "srslte/common/common.h"
#include "srslte/common/log.h"
#include "srslte/interfaces/ue_interfaces.h"
#include "srslte/upper/rlc_common.h"
#include "srslte/upper/rlc_tx_queue.h"
#include <map>
#include <mutex>
#include <pthread.h>
#include <queue>
namespace srslte {
struct rlc_umd_pdu_t{
rlc_umd_pdu_header_t header;
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
{
public:
rlc_um(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_);
~rlc_um();
bool configure(rlc_config_t cnfg);
void reestablish();
void stop();
void empty_queue();
bool is_mrb();
rlc_mode_t get_mode();
uint32_t get_bearer();
// PDCP interface
void write_sdu(unique_byte_buffer_t sdu, bool blocking = true);
// MAC interface
bool has_data();
uint32_t get_buffer_state();
int read_pdu(uint8_t *payload, uint32_t nof_bytes);
void write_pdu(uint8_t *payload, uint32_t nof_bytes);
int get_increment_sequence_num();
uint32_t get_num_tx_bytes();
uint32_t get_num_rx_bytes();
void reset_metrics();
private:
// Transmitter sub-class base
class rlc_um_tx_base
{
public:
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();
void empty_queue();
void write_sdu(unique_byte_buffer_t sdu);
void try_write_sdu(unique_byte_buffer_t sdu);
uint32_t get_num_tx_bytes();
void reset_metrics();
bool has_data();
uint32_t get_buffer_state();
protected:
byte_buffer_pool* pool = nullptr;
srslte::log* log = nullptr;
std::string rb_name;
/****************************************************************************
* Configurable parameters
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
***************************************************************************/
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.
void debug_state();
};
// Transmitter sub-class for NR
class rlc_um_tx_nr : public rlc_um_tx_base
{
public:
rlc_um_tx_nr(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:
uint32_t TX_Next = 0; // send state as defined in TS 38.322 v15.3 Section 7
void debug_state();
};
// Receiver sub-class
class rlc_um_rx : public timer_callback {
public:
rlc_um_rx(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_);
~rlc_um_rx();
void stop();
void reestablish();
bool configure(rlc_config_t cfg, std::string rb_name);
void handle_data_pdu(uint8_t *payload, uint32_t nof_bytes);
void reassemble_rx_sdus();
bool pdu_belongs_to_rx_sdu();
bool inside_reordering_window(uint16_t sn);
uint32_t get_num_rx_bytes();
void reset_metrics();
// Timeout callback interface
void timer_expired(uint32_t timeout_id);
private:
void reset();
byte_buffer_pool* pool = nullptr;
srslte::log* log = nullptr;
srslte::timer_handler* timers = nullptr;
std::string rb_name;
/****************************************************************************
* Configurable parameters
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
***************************************************************************/
rlc_config_t cfg = {};
// Rx window
std::map<uint32_t, rlc_umd_pdu_t> rx_window;
// RX SDU buffers
unique_byte_buffer_t rx_sdu;
uint32_t vr_ur_in_rx_sdu = 0;
// Rx state variables and counter
uint32_t vr_ur = 0; // Receive state. SN of earliest PDU still considered for reordering.
uint32_t vr_ux = 0; // t_reordering state. SN following PDU which triggered t_reordering.
uint32_t vr_uh = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs.
bool pdu_lost = false;
uint32_t num_rx_bytes = 0;
// Upper layer handles and variables
srsue::pdcp_interface_rlc* pdcp = nullptr;
srsue::rrc_interface_rlc* rrc = nullptr;
uint32_t lcid = 0;
// Mutexes
std::mutex mutex;
bool rx_enabled = false;
/****************************************************************************
* Timers
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
***************************************************************************/
srslte::timer_handler::unique_timer reordering_timer;
// helper functions
void debug_state();
const char* get_rb_name();
};
// Common variables needed by parent class
srsue::rrc_interface_rlc* rrc = nullptr;
srslte::log* log = nullptr;
uint32_t lcid = 0;
rlc_config_t cfg = {};
std::string rb_name;
byte_buffer_pool* pool = nullptr;
std::string get_rb_name(srsue::rrc_interface_rlc *rrc, uint32_t lcid, bool is_mrb);
// Rx and Tx objects
std::unique_ptr<rlc_um_tx_base> tx;
rlc_um_rx rx;
};
/****************************************************************************
* Header pack/unpack helper functions
* Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1
***************************************************************************/
void rlc_um_read_data_pdu_header(byte_buffer_t* pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t* header);
void rlc_um_read_data_pdu_header(uint8_t *payload, uint32_t nof_bytes, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t *header);
void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu);
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

@ -0,0 +1,165 @@
/*
* 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_UM_BASE_H
#define SRSLTE_RLC_UM_BASE_H
#include "srslte/common/buffer_pool.h"
#include "srslte/common/common.h"
#include "srslte/common/log.h"
#include "srslte/interfaces/ue_interfaces.h"
#include "srslte/upper/rlc_common.h"
#include "srslte/upper/rlc_tx_queue.h"
#include <map>
#include <mutex>
#include <pthread.h>
#include <queue>
namespace srslte {
class rlc_um_base : public rlc_common
{
public:
rlc_um_base(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_);
virtual ~rlc_um_base();
void reestablish();
void stop();
void empty_queue();
bool is_mrb();
rlc_mode_t get_mode();
uint32_t get_bearer();
// PDCP interface
void write_sdu(unique_byte_buffer_t sdu, bool blocking = true);
// MAC interface
bool has_data();
uint32_t get_buffer_state();
int read_pdu(uint8_t* payload, uint32_t nof_bytes);
void write_pdu(uint8_t* payload, uint32_t nof_bytes);
int get_increment_sequence_num();
rlc_bearer_metrics_t get_metrics();
void reset_metrics();
protected:
// Transmitter sub-class base
class rlc_um_base_tx
{
public:
rlc_um_base_tx(rlc_um_base* parent_);
virtual ~rlc_um_base_tx();
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();
void empty_queue();
void write_sdu(unique_byte_buffer_t sdu);
void try_write_sdu(unique_byte_buffer_t sdu);
void reset_metrics();
bool has_data();
virtual uint32_t get_buffer_state() = 0;
protected:
byte_buffer_pool* pool = nullptr;
srslte::log* log = nullptr;
std::string rb_name;
rlc_config_t cfg = {};
// TX SDU buffers
rlc_tx_queue tx_sdu_queue;
unique_byte_buffer_t tx_sdu;
// Mutexes
std::mutex mutex;
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;
};
// Receiver sub-class base
class rlc_um_base_rx : public timer_callback
{
public:
rlc_um_base_rx(rlc_um_base* parent_);
virtual ~rlc_um_base_rx();
virtual bool configure() = 0;
virtual void stop() = 0;
virtual void reestablish() = 0;
virtual void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) = 0;
protected:
byte_buffer_pool* pool = nullptr;
srslte::log* log = nullptr;
srslte::timer_handler* timers = nullptr;
srsue::pdcp_interface_rlc* pdcp = nullptr;
srsue::rrc_interface_rlc* rrc = nullptr;
rlc_bearer_metrics_t& metrics;
std::string rb_name;
rlc_config_t cfg = {};
unique_byte_buffer_t rx_sdu;
uint32_t& lcid;
std::mutex mutex;
// helper functions
virtual void debug_state() = 0;
};
// Common variables needed by parent class
srsue::rrc_interface_rlc* rrc = nullptr;
srsue::pdcp_interface_rlc* pdcp = nullptr;
srslte::log* log = nullptr;
srslte::timer_handler* timers = nullptr;
uint32_t lcid = 0;
rlc_config_t cfg = {};
std::string rb_name;
byte_buffer_pool* pool = nullptr;
std::string get_rb_name(srsue::rrc_interface_rlc* rrc, uint32_t lcid, bool is_mrb);
// Rx and Tx objects
std::unique_ptr<rlc_um_base_tx> tx;
std::unique_ptr<rlc_um_base_rx> rx;
bool tx_enabled = false;
bool rx_enabled = false;
rlc_bearer_metrics_t metrics = {};
};
} // namespace srslte
#endif // SRSLTE_RLC_UM_BASE_H

@ -0,0 +1,135 @@
/*
* 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_UM_LTE_H
#define SRSLTE_RLC_UM_LTE_H
#include "srslte/common/buffer_pool.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_um_base.h"
#include <map>
#include <mutex>
#include <pthread.h>
#include <queue>
namespace srslte {
typedef struct {
rlc_umd_pdu_header_t header;
unique_byte_buffer_t buf;
} rlc_umd_pdu_t;
class rlc_um_lte : public rlc_um_base
{
public:
rlc_um_lte(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_);
~rlc_um_lte();
bool configure(rlc_config_t cnfg);
private:
// Transmitter sub-class for LTE
class rlc_um_lte_tx : public rlc_um_base_tx
{
public:
rlc_um_lte_tx(rlc_um_base* parent_);
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);
uint32_t get_buffer_state();
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.
void debug_state();
};
// Receiver sub-class for LTE
class rlc_um_lte_rx : public rlc_um_base_rx
{
public:
rlc_um_lte_rx(rlc_um_base* parent_);
~rlc_um_lte_rx();
void stop();
void reestablish();
bool configure();
void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes);
void reassemble_rx_sdus();
bool pdu_belongs_to_rx_sdu();
bool inside_reordering_window(uint16_t sn);
// Timeout callback interface
void timer_expired(uint32_t timeout_id);
private:
void reset();
// Rx window
std::map<uint32_t, rlc_umd_pdu_t> rx_window;
// RX SDU buffers
uint32_t vr_ur_in_rx_sdu = 0;
// Rx state variables and counter
uint32_t vr_ur = 0; // Receive state. SN of earliest PDU still considered for reordering.
uint32_t vr_ux = 0; // t_reordering state. SN following PDU which triggered t_reordering.
uint32_t vr_uh = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs.
bool pdu_lost = false;
/****************************************************************************
* Timers
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
***************************************************************************/
srslte::timer_handler::unique_timer reordering_timer;
// helper functions
void debug_state();
};
};
/****************************************************************************
* Header pack/unpack helper functions
* Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1
***************************************************************************/
void rlc_um_read_data_pdu_header(byte_buffer_t* pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t* header);
void rlc_um_read_data_pdu_header(uint8_t* payload,
uint32_t nof_bytes,
rlc_umd_sn_size_t sn_size,
rlc_umd_pdu_header_t* header);
void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu);
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);
} // namespace srslte
#endif // SRSLTE_RLC_UM_LTE_H

@ -0,0 +1,143 @@
/*
* 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_UM_NR_H
#define SRSLTE_RLC_UM_NR_H
#include "srslte/common/buffer_pool.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_um_base.h"
#include <map>
#include <mutex>
#include <pthread.h>
#include <queue>
namespace srslte {
typedef struct {
rlc_um_nr_pdu_header_t header;
unique_byte_buffer_t buf;
} rlc_umd_pdu_nr_t;
class rlc_um_nr : public rlc_um_base
{
public:
rlc_um_nr(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_);
~rlc_um_nr();
bool configure(rlc_config_t cnfg);
private:
// Transmitter sub-class for NR
class rlc_um_nr_tx : public rlc_um_base_tx
{
public:
rlc_um_nr_tx(rlc_um_base* parent_);
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);
uint32_t get_buffer_state();
private:
uint32_t TX_Next = 0; // send state as defined in TS 38.322 v15.3 Section 7
// It holds the value of the SN to be assigned for the next newly generated UMD PDU with
// segment. It is initially set to 0, and is updated after the UM RLC entity submits a UMD PDU
// including the last segment of an RLC SDU to lower layers.
uint32_t next_so = 0; // The segment offset for the next generated PDU
void debug_state();
};
// Receiver sub-class for NR
class rlc_um_nr_rx : public rlc_um_base_rx
{
public:
rlc_um_nr_rx(rlc_um_base* parent_);
bool configure();
void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes);
void reestablish();
void stop();
void timer_expired(uint32_t timeout_id);
private:
bool sn_in_reassembly_window(const uint32_t sn);
bool sn_invalid_for_rx_buffer(const uint32_t sn);
bool has_missing_byte_segment(const uint32_t sn);
unique_byte_buffer_t
rlc_um_nr_strip_pdu_header(const rlc_um_nr_pdu_header_t& header, const uint8_t* payload, const uint32_t nof_bytes);
void handle_rx_buffer_update(const uint32_t sn);
uint32_t RX_Next_Reassembly = 0; // the earliest SN that is still considered for reassembly
uint32_t RX_Timer_Trigger = 0; // the SN following the SN which triggered t-Reassembly
uint32_t RX_Next_Highest = 0; // the SN following the SN of the UMD PDU with the highest SN among
// received UMD PDUs. It serves as the higher edge of the reassembly window.
// Rx window
typedef struct {
std::map<uint32_t, rlc_umd_pdu_nr_t> segments; // Map of segments with SO as key
unique_byte_buffer_t sdu;
uint32_t next_expected_so;
uint32_t total_sdu_length;
} rlc_umd_pdu_segments_nr_t;
std::map<uint32_t, rlc_umd_pdu_segments_nr_t> rx_window;
void update_total_sdu_length(rlc_umd_pdu_segments_nr_t& pdu_segments, const rlc_umd_pdu_nr_t& rx_pdu);
// TS 38.322 Sec. 7.3
srslte::timer_handler::unique_timer reassembly_timer; // to detect loss of RLC PDUs at lower layers
// helper functions
void reset();
void debug_state();
};
};
/****************************************************************************
* 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_NR_H

@ -18,7 +18,17 @@
# and at http://www.gnu.org/licenses/. # and at http://www.gnu.org/licenses/.
# #
set(SOURCES gtpu.cc pdcp.cc pdcp_entity_base.cc pdcp_entity_lte.cc pdcp_entity_nr.cc rlc.cc rlc_am_lte.cc rlc_um.cc rlc_tm.cc) set(SOURCES gtpu.cc
pdcp.cc
pdcp_entity_base.cc
pdcp_entity_lte.cc
pdcp_entity_nr.cc
rlc.cc
rlc_tm.cc
rlc_um_base.cc
rlc_um_lte.cc
rlc_um_nr.cc
rlc_am_lte.cc)
add_library(srslte_upper STATIC ${SOURCES}) add_library(srslte_upper STATIC ${SOURCES})
target_link_libraries(srslte_upper srslte_common srslte_asn1) target_link_libraries(srslte_upper srslte_common srslte_asn1)
install(TARGETS srslte_upper DESTINATION ${LIBRARY_DIR}) install(TARGETS srslte_upper DESTINATION ${LIBRARY_DIR})

@ -22,7 +22,8 @@
#include "srslte/upper/rlc.h" #include "srslte/upper/rlc.h"
#include "srslte/upper/rlc_am_lte.h" #include "srslte/upper/rlc_am_lte.h"
#include "srslte/upper/rlc_tm.h" #include "srslte/upper/rlc_tm.h"
#include "srslte/upper/rlc_um.h" #include "srslte/upper/rlc_um_lte.h"
#include "srslte/upper/rlc_um_nr.h"
namespace srslte { namespace srslte {
@ -100,20 +101,21 @@ void rlc::get_metrics(rlc_metrics_t &m)
double secs = (double)metrics_time[0].tv_sec + metrics_time[0].tv_usec*1e-6; double secs = (double)metrics_time[0].tv_sec + metrics_time[0].tv_usec*1e-6;
for (rlc_map_t::iterator it = rlc_array.begin(); it != rlc_array.end(); ++it) { for (rlc_map_t::iterator it = rlc_array.begin(); it != rlc_array.end(); ++it) {
m.dl_tput_mbps[it->first] = (it->second->get_num_rx_bytes()*8/static_cast<double>(1e6))/secs; rlc_bearer_metrics_t metrics = it->second->get_metrics();
m.ul_tput_mbps[it->first] = (it->second->get_num_tx_bytes()*8/static_cast<double>(1e6))/secs;
rlc_log->info("LCID=%d, RX throughput: %4.6f Mbps. TX throughput: %4.6f Mbps.\n", rlc_log->info("LCID=%d, RX throughput: %4.6f Mbps. TX throughput: %4.6f Mbps.\n",
it->first, it->first,
(it->second->get_num_rx_bytes()*8/static_cast<double>(1e6))/secs, (metrics.num_rx_bytes * 8 / static_cast<double>(1e6)) / secs,
(it->second->get_num_tx_bytes()*8/static_cast<double>(1e6))/secs); (metrics.num_tx_bytes * 8 / static_cast<double>(1e6)) / secs);
m.bearer[it->first] = metrics;
} }
// Add multicast metrics // Add multicast metrics
for (rlc_map_t::iterator it = rlc_array_mrb.begin(); it != rlc_array_mrb.end(); ++it) { for (rlc_map_t::iterator it = rlc_array_mrb.begin(); it != rlc_array_mrb.end(); ++it) {
m.dl_tput_mbps[it->first] = (it->second->get_num_rx_bytes()*8/static_cast<double>(1e6))/secs; rlc_bearer_metrics_t metrics = it->second->get_metrics();
rlc_log->info("MCH_LCID=%d, RX throughput: %4.6f Mbps\n", rlc_log->info("MCH_LCID=%d, RX throughput: %4.6f Mbps\n",
it->first, it->first,
(it->second->get_num_rx_bytes()*8/static_cast<double>(1e6))/secs); (metrics.num_rx_bytes * 8 / static_cast<double>(1e6)) / secs);
m.bearer[it->first] = metrics;
} }
memcpy(&metrics_time[1], &metrics_time[2], sizeof(struct timeval)); memcpy(&metrics_time[1], &metrics_time[2], sizeof(struct timeval));
@ -382,6 +384,7 @@ void rlc::add_bearer(uint32_t lcid, rlc_config_t cnfg)
rlc_common *rlc_entity = NULL; rlc_common *rlc_entity = NULL;
if (not valid_lcid(lcid)) { if (not valid_lcid(lcid)) {
if (cnfg.rat == srslte_rat_t::lte) {
switch (cnfg.rlc_mode) { switch (cnfg.rlc_mode) {
case rlc_mode_t::tm: case rlc_mode_t::tm:
rlc_entity = new rlc_tm(rlc_log, lcid, pdcp, rrc, timers); rlc_entity = new rlc_tm(rlc_log, lcid, pdcp, rrc, timers);
@ -390,12 +393,25 @@ void rlc::add_bearer(uint32_t lcid, rlc_config_t cnfg)
rlc_entity = new rlc_am_lte(rlc_log, lcid, pdcp, rrc, timers); rlc_entity = new rlc_am_lte(rlc_log, lcid, pdcp, rrc, timers);
break; break;
case rlc_mode_t::um: case rlc_mode_t::um:
rlc_entity = new rlc_um(rlc_log, lcid, pdcp, rrc, timers); rlc_entity = new rlc_um_lte(rlc_log, lcid, pdcp, rrc, timers);
break; break;
default: default:
rlc_log->error("Cannot add RLC entity - invalid mode\n"); rlc_log->error("Cannot add RLC entity - invalid mode\n");
goto unlock_and_exit; goto unlock_and_exit;
} }
} else if (cnfg.rat == srslte_rat_t::nr) {
switch (cnfg.rlc_mode) {
case rlc_mode_t::tm:
rlc_entity = new rlc_tm(rlc_log, lcid, pdcp, rrc, timers);
break;
case rlc_mode_t::um:
rlc_entity = new rlc_um_nr(rlc_log, lcid, pdcp, rrc, timers);
break;
default:
rlc_log->error("Cannot add RLC entity - invalid mode\n");
goto unlock_and_exit;
}
}
if (not rlc_array.insert(rlc_map_pair_t(lcid, rlc_entity)).second) { if (not rlc_array.insert(rlc_map_pair_t(lcid, rlc_entity)).second) {
rlc_log->error("Error inserting RLC entity in to array\n."); rlc_log->error("Error inserting RLC entity in to array\n.");
@ -406,7 +422,7 @@ void rlc::add_bearer(uint32_t lcid, rlc_config_t cnfg)
} }
// configure and add to array // configure and add to array
if (cnfg.rlc_mode != rlc_mode_t::tm) { if (cnfg.rlc_mode != rlc_mode_t::tm and rlc_array.find(lcid) != rlc_array.end()) {
if (not rlc_array.at(lcid)->configure(cnfg)) { if (not rlc_array.at(lcid)->configure(cnfg)) {
rlc_log->error("Error configuring RLC entity\n."); rlc_log->error("Error configuring RLC entity\n.");
goto delete_and_exit; goto delete_and_exit;
@ -431,7 +447,7 @@ void rlc::add_bearer_mrb(uint32_t lcid)
rlc_common* rlc_entity = NULL; rlc_common* rlc_entity = NULL;
if (not valid_lcid_mrb(lcid)) { if (not valid_lcid_mrb(lcid)) {
rlc_entity = new rlc_um(rlc_log, lcid, pdcp, rrc, timers); rlc_entity = new rlc_um_lte(rlc_log, lcid, pdcp, rrc, timers);
// configure and add to array // configure and add to array
if (not rlc_entity->configure(rlc_config_t::mch_config())) { if (not rlc_entity->configure(rlc_config_t::mch_config())) {
rlc_log->error("Error configuring RLC entity\n."); rlc_log->error("Error configuring RLC entity\n.");

@ -102,14 +102,9 @@ uint32_t rlc_am_lte::get_bearer()
return lcid; return lcid;
} }
uint32_t rlc_am_lte::get_num_rx_bytes() rlc_bearer_metrics_t rlc_am_lte::get_metrics()
{ {
return rx.get_num_rx_bytes(); return metrics;
}
uint32_t rlc_am_lte::get_num_tx_bytes()
{
return tx.get_num_tx_bytes();
} }
void rlc_am_lte::reset_metrics() void rlc_am_lte::reset_metrics()

@ -126,20 +126,14 @@ uint32_t rlc_tm::get_buffer_state()
return ul_queue.size_bytes(); return ul_queue.size_bytes();
} }
uint32_t rlc_tm::get_num_tx_bytes() rlc_bearer_metrics_t rlc_tm::get_metrics()
{ {
return num_tx_bytes; return metrics;
}
uint32_t rlc_tm::get_num_rx_bytes()
{
return num_rx_bytes;
} }
void rlc_tm::reset_metrics() void rlc_tm::reset_metrics()
{ {
num_tx_bytes = 0; metrics = {};
num_rx_bytes = 0;
} }
int rlc_tm::read_pdu(uint8_t *payload, uint32_t nof_bytes) int rlc_tm::read_pdu(uint8_t *payload, uint32_t nof_bytes)
@ -163,7 +157,7 @@ int rlc_tm::read_pdu(uint8_t *payload, uint32_t nof_bytes)
ul_queue.size(), ul_queue.size(),
ul_queue.size_bytes()); ul_queue.size_bytes());
num_tx_bytes += pdu_size; metrics.num_tx_bytes += pdu_size;
return pdu_size; return pdu_size;
} else { } else {
log->warning("Queue empty while trying to read\n"); log->warning("Queue empty while trying to read\n");
@ -182,7 +176,7 @@ void rlc_tm::write_pdu(uint8_t *payload, uint32_t nof_bytes)
memcpy(buf->msg, payload, nof_bytes); memcpy(buf->msg, payload, nof_bytes);
buf->N_bytes = nof_bytes; buf->N_bytes = nof_bytes;
buf->set_timestamp(); buf->set_timestamp();
num_rx_bytes += nof_bytes; metrics.num_rx_bytes += nof_bytes;
if (rrc->get_rb_name(lcid) == "SRB0") { if (rrc->get_rb_name(lcid) == "SRB0") {
rrc->write_pdu(lcid, std::move(buf)); rrc->write_pdu(lcid, std::move(buf));
} else { } else {

File diff suppressed because it is too large Load Diff

@ -0,0 +1,285 @@
/*
* 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/upper/rlc_um_base.h"
#include <sstream>
using namespace asn1::rrc;
namespace srslte {
rlc_um_base::rlc_um_base(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_) :
log(log_), lcid(lcid_), pdcp(pdcp_), rrc(rrc_), timers(timers_), pool(byte_buffer_pool::get_instance())
{
}
rlc_um_base::~rlc_um_base() {}
void rlc_um_base::stop()
{
if (tx) {
tx->stop();
}
if (rx) {
rx->stop();
}
}
rlc_mode_t rlc_um_base::get_mode()
{
return rlc_mode_t::um;
}
uint32_t rlc_um_base::get_bearer()
{
return lcid;
}
bool rlc_um_base::is_mrb()
{
return cfg.um.is_mrb;
}
void rlc_um_base::reestablish()
{
tx_enabled = false;
if (tx) {
tx->reestablish(); // calls stop and enables tx again
}
if (rx) {
rx->reestablish(); // nothing else needed
}
tx_enabled = true;
}
void rlc_um_base::empty_queue()
{
// Drop all messages in TX SDU queue
if (tx) {
tx->empty_queue();
}
}
/****************************************************************************
* PDCP interface
***************************************************************************/
void rlc_um_base::write_sdu(unique_byte_buffer_t sdu, bool blocking)
{
if (not tx_enabled || not tx) {
log->debug("%s is currently deactivated. Dropping SDU (%d B)\n", rb_name.c_str(), sdu->N_bytes);
metrics.num_dropped_sdus++;
return;
}
if (blocking) {
tx->write_sdu(std::move(sdu));
} else {
tx->try_write_sdu(std::move(sdu));
}
}
/****************************************************************************
* MAC interface
***************************************************************************/
bool rlc_um_base::has_data()
{
if (tx) {
return tx->has_data();
}
return false;
}
uint32_t rlc_um_base::get_buffer_state()
{
if (tx) {
return tx->get_buffer_state();
}
return 0;
}
int rlc_um_base::read_pdu(uint8_t* payload, uint32_t nof_bytes)
{
if (tx && tx_enabled) {
uint32_t len = tx->build_data_pdu(payload, nof_bytes);
metrics.num_tx_bytes += len;
metrics.num_tx_pdus++;
return len;
}
return 0;
}
void rlc_um_base::write_pdu(uint8_t* payload, uint32_t nof_bytes)
{
if (rx && rx_enabled) {
metrics.num_rx_pdus++;
metrics.num_rx_bytes += nof_bytes;
rx->handle_data_pdu(payload, nof_bytes);
}
}
rlc_bearer_metrics_t rlc_um_base::get_metrics()
{
return metrics;
}
void rlc_um_base::reset_metrics()
{
metrics = {};
}
/****************************************************************************
* Helper functions
***************************************************************************/
std::string rlc_um_base::get_rb_name(srsue::rrc_interface_rlc* rrc, uint32_t lcid, bool is_mrb)
{
if (is_mrb) {
std::stringstream ss;
ss << "MRB" << lcid;
return ss.str();
} else {
return rrc->get_rb_name(lcid);
}
}
/****************************************************************************
* Rx subclass implementation (base)
***************************************************************************/
rlc_um_base::rlc_um_base_rx::rlc_um_base_rx(rlc_um_base* parent_) :
pool(parent_->pool),
log(parent_->log),
timers(parent_->timers),
pdcp(parent_->pdcp),
rrc(parent_->rrc),
cfg(parent_->cfg),
metrics(parent_->metrics),
lcid(parent_->lcid)
{
}
rlc_um_base::rlc_um_base_rx::~rlc_um_base_rx() {}
/****************************************************************************
* Tx subclass implementation (base)
***************************************************************************/
rlc_um_base::rlc_um_base_tx::rlc_um_base_tx(rlc_um_base* parent_) : log(parent_->log), pool(parent_->pool) {}
rlc_um_base::rlc_um_base_tx::~rlc_um_base_tx() {}
void rlc_um_base::rlc_um_base_tx::stop()
{
empty_queue();
}
void rlc_um_base::rlc_um_base_tx::reestablish()
{
stop();
// bearer is enabled in base class
}
void rlc_um_base::rlc_um_base_tx::empty_queue()
{
std::lock_guard<std::mutex> lock(mutex);
// deallocate all SDUs in transmit queue
while (not tx_sdu_queue.is_empty()) {
unique_byte_buffer_t buf = tx_sdu_queue.read();
}
// deallocate SDU that is currently processed
tx_sdu.reset();
}
bool rlc_um_base::rlc_um_base_tx::has_data()
{
return (tx_sdu != nullptr || !tx_sdu_queue.is_empty());
}
void rlc_um_base::rlc_um_base_tx::write_sdu(unique_byte_buffer_t sdu)
{
if (sdu) {
log->info_hex(sdu->msg,
sdu->N_bytes,
"%s Tx SDU (%d B, tx_sdu_queue_len=%d)",
rb_name.c_str(),
sdu->N_bytes,
tx_sdu_queue.size());
tx_sdu_queue.write(std::move(sdu));
} else {
log->warning("NULL SDU pointer in write_sdu()\n");
}
}
void rlc_um_base::rlc_um_base_tx::try_write_sdu(unique_byte_buffer_t sdu)
{
if (sdu) {
uint8_t* msg_ptr = sdu->msg;
uint32_t nof_bytes = sdu->N_bytes;
std::pair<bool, unique_byte_buffer_t> ret = tx_sdu_queue.try_write(std::move(sdu));
if (ret.first) {
log->info_hex(
msg_ptr, nof_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", rb_name.c_str(), nof_bytes, tx_sdu_queue.size());
} else {
log->info_hex(ret.second->msg,
ret.second->N_bytes,
"[Dropped SDU] %s Tx SDU (%d B, tx_sdu_queue_len=%d)",
rb_name.c_str(),
ret.second->N_bytes,
tx_sdu_queue.size());
}
} else {
log->warning("NULL SDU pointer in write_sdu()\n");
}
}
int rlc_um_base::rlc_um_base_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes)
{
unique_byte_buffer_t pdu;
{
std::lock_guard<std::mutex> lock(mutex);
log->debug("MAC opportunity - %d bytes\n", nof_bytes);
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);
}
} // namespace srslte

@ -0,0 +1,815 @@
/*
* 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/upper/rlc_um_lte.h"
#include <sstream>
#define RX_MOD_BASE(x) (((x)-vr_uh - cfg.um.rx_window_size) % cfg.um.rx_mod)
using namespace asn1::rrc;
namespace srslte {
rlc_um_lte::rlc_um_lte(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_) :
rlc_um_base(log_, lcid_, pdcp_, rrc_, timers_)
{
}
// Warning: must call stop() to properly deallocate all buffers
rlc_um_lte::~rlc_um_lte()
{
stop();
}
bool rlc_um_lte::configure(rlc_config_t cnfg_)
{
// determine bearer name and configure Rx/Tx objects
rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb);
// store config
cfg = cnfg_;
rx.reset(new rlc_um_lte_rx(this));
if (not rx->configure()) {
return false;
}
tx.reset(new rlc_um_lte_tx(this));
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));
rx_enabled = true;
tx_enabled = true;
return true;
}
/****************************************************************************
* Tx Subclass implementation for LTE
***************************************************************************/
rlc_um_lte::rlc_um_lte_tx::rlc_um_lte_tx(rlc_um_base* parent_) : rlc_um_base_tx(parent_) {}
uint32_t rlc_um_lte::rlc_um_lte_tx::get_buffer_state()
{
std::lock_guard<std::mutex> lock(mutex);
// Bytes needed for tx SDUs
uint32_t n_sdus = tx_sdu_queue.size();
uint32_t n_bytes = tx_sdu_queue.size_bytes();
if (tx_sdu) {
n_sdus++;
n_bytes += tx_sdu->N_bytes;
}
// Room needed for header extensions? (integer rounding)
if (n_sdus > 1) {
n_bytes += ((n_sdus - 1) * 1.5) + 0.5;
}
// Room needed for fixed header?
if (n_bytes > 0)
n_bytes += (cfg.um.is_mrb) ? 2 : 3;
return n_bytes;
}
bool rlc_um_lte::rlc_um_lte_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", rb_name.c_str());
return false;
}
tx_sdu_queue.resize(cnfg_.tx_queue_length);
rb_name = rb_name_;
return true;
}
int rlc_um_lte::rlc_um_lte_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes)
{
std::lock_guard<std::mutex> 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.um.tx_sn_field_length;
uint32_t to_move = 0;
uint32_t last_li = 0;
uint8_t* pdu_ptr = pdu->msg;
int head_len = rlc_um_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",
rb_name.c_str(),
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", rb_name.c_str(), to_move, tx_sdu->N_bytes);
memcpy(pdu_ptr, tx_sdu->msg, to_move);
last_li = 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", rb_name.c_str(), tx_sdu->get_latency_us());
tx_sdu.reset();
}
pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom());
header.fi |= RLC_FI_FIELD_NOT_START_ALIGNED; // First byte does not correspond to first byte of SDU
}
// Pull SDUs from queue
while (pdu_space > head_len + 1 && tx_sdu_queue.size() > 0) {
log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len);
if (last_li > 0)
header.li[header.N_li++] = last_li;
head_len = rlc_um_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 segment - %d bytes of %d remaining\n", rb_name.c_str(), to_move, tx_sdu->N_bytes);
memcpy(pdu_ptr, tx_sdu->msg, to_move);
last_li = 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", rb_name.c_str(), tx_sdu->get_latency_us());
tx_sdu.reset();
}
pdu_space -= to_move;
}
if (tx_sdu) {
header.fi |= RLC_FI_FIELD_NOT_END_ALIGNED; // Last byte does not correspond to last byte of SDU
}
// Set SN
header.sn = vt_us;
vt_us = (vt_us + 1) % cfg.um.tx_mod;
// Add header and TX
rlc_um_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", rb_name.c_str(), header.sn, pdu->N_bytes);
debug_state();
return ret;
}
void rlc_um_lte::rlc_um_lte_tx::debug_state()
{
log->debug("%s vt_us = %d\n", rb_name.c_str(), vt_us);
}
/****************************************************************************
* Rx subclass implementation
***************************************************************************/
rlc_um_lte::rlc_um_lte_rx::rlc_um_lte_rx(rlc_um_base* parent_) : rlc_um_base_rx(parent_), reordering_timer(timers->get_unique_timer())
{
}
rlc_um_lte::rlc_um_lte_rx::~rlc_um_lte_rx()
{
}
bool rlc_um_lte::rlc_um_lte_rx::configure()
{
if (cfg.um.rx_mod == 0) {
log->error("Error configuring %s RLC UM: rx_mod==0\n", rb_name.c_str());
return false;
}
// check timer
if (not reordering_timer.is_valid()) {
log->error("Configuring RLC UM RX: timers not configured\n");
return false;
}
// configure timer
if (cfg.um.t_reordering > 0) {
reordering_timer.set(static_cast<uint32_t>(cfg.um.t_reordering), [this](uint32_t tid) { timer_expired(tid); });
}
return true;
}
void rlc_um_lte::rlc_um_lte_rx::reestablish()
{
// try to reassemble any SDUs if possible
if (reordering_timer.is_valid() && reordering_timer.is_running()) {
reordering_timer.stop();
timer_expired(reordering_timer.id());
}
std::lock_guard<std::mutex> lock(mutex);
reset();
}
void rlc_um_lte::rlc_um_lte_rx::stop()
{
std::lock_guard<std::mutex> lock(mutex);
reset();
reordering_timer.stop();
}
void rlc_um_lte::rlc_um_lte_rx::reset()
{
vr_ur = 0;
vr_ux = 0;
vr_uh = 0;
pdu_lost = false;
rx_sdu.reset();
// Drop all messages in RX window
rx_window.clear();
}
void rlc_um_lte::rlc_um_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
{
std::lock_guard<std::mutex> lock(mutex);
rlc_umd_pdu_header_t 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)", rb_name.c_str(), header.sn, nof_bytes);
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", rb_name.c_str(), header.sn, vr_ur, vr_uh);
return;
}
std::map<uint32_t, rlc_umd_pdu_t>::iterator it = rx_window.find(header.sn);
if (rx_window.end() != it) {
log->info("%s Discarding duplicate SN: %d\n", rb_name.c_str(), header.sn);
return;
}
// Write to rx window
rlc_umd_pdu_t pdu = {};
pdu.buf = allocate_unique_buffer(*pool);
if (!pdu.buf) {
log->error("Discarting packet: no space in buffer pool\n");
return;
}
memcpy(pdu.buf->msg, payload, nof_bytes);
pdu.buf->N_bytes = nof_bytes;
// Strip header from PDU
int header_len = rlc_um_packed_length(&header);
pdu.buf->msg += header_len;
pdu.buf->N_bytes -= header_len;
pdu.header = header;
rx_window[header.sn] = std::move(pdu);
// Update vr_uh
if (!inside_reordering_window(header.sn)) {
vr_uh = (header.sn + 1) % cfg.um.rx_mod;
}
// Reassemble and deliver SDUs, while updating vr_ur
log->debug("Entering Reassemble from received PDU\n");
reassemble_rx_sdus();
log->debug("Finished reassemble from received PDU\n");
// Update reordering variables and timers
if (reordering_timer.is_running()) {
if (RX_MOD_BASE(vr_ux) <= RX_MOD_BASE(vr_ur) || (!inside_reordering_window(vr_ux) && vr_ux != vr_uh)) {
reordering_timer.stop();
}
}
if (!reordering_timer.is_running()) {
if (RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) {
reordering_timer.run();
vr_ux = vr_uh;
}
}
debug_state();
}
// No locking required as only called from within handle_data_pdu and timer_expired which lock
void rlc_um_lte::rlc_um_lte_rx::reassemble_rx_sdus()
{
if (!rx_sdu) {
rx_sdu = allocate_unique_buffer(*pool);
if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
}
}
// First catch up with lower edge of reordering window
while (!inside_reordering_window(vr_ur)) {
log->debug("SN=%d is not inside reordering windows\n", vr_ur);
if (rx_window.end() == rx_window.find(vr_ur)) {
log->debug("SN=%d not in rx_window. Reset received SDU\n", vr_ur);
rx_sdu->clear();
} else {
// Handle any SDU segments
for (uint32_t i = 0; i < rx_window[vr_ur].header.N_li; i++) {
int len = rx_window[vr_ur].header.li[i];
log->debug_hex(rx_window[vr_ur].buf->msg,
len,
"Handling segment %d/%d of length %d B of SN=%d\n",
i + 1,
rx_window[vr_ur].header.N_li,
len,
vr_ur);
// Check if we received a middle or end segment
if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->warning("Dropping PDU %d due to lost start segment\n", vr_ur);
// Advance data pointers and continue with next segment
rx_window[vr_ur].buf->msg += len;
rx_window[vr_ur].buf->N_bytes -= len;
rx_sdu->clear();
break;
}
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len);
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.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)",
rb_name.c_str(),
vr_ur,
i);
rx_sdu->set_timestamp();
if (cfg.um.is_mrb) {
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
} else {
pdcp->write_pdu(lcid, std::move(rx_sdu));
}
rx_sdu = allocate_unique_buffer(*pool);
if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
}
}
pdu_lost = false;
}
// Handle last segment
if (rx_sdu->N_bytes > 0 || rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->info("Writing last segment in SDU buffer. Lower edge vr_ur=%d, Buffer size=%d, segment size=%d\n",
vr_ur,
rx_sdu->N_bytes,
rx_window[vr_ur].buf->N_bytes);
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes);
rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes;
vr_ur_in_rx_sdu = vr_ur;
if (rlc_um_end_aligned(rx_window[vr_ur].header.fi)) {
if (pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->warning("Dropping remainder of lost PDU (lower edge last segments)\n");
rx_sdu->clear();
} else {
log->info_hex(
rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", rb_name.c_str(), vr_ur);
rx_sdu->set_timestamp();
if (cfg.um.is_mrb) {
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
} else {
pdcp->write_pdu(lcid, std::move(rx_sdu));
}
rx_sdu = allocate_unique_buffer(*pool);
if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
}
}
pdu_lost = false;
}
}
// Clean up rx_window
rx_window.erase(vr_ur);
}
vr_ur = (vr_ur + 1) % cfg.um.rx_mod;
}
// Now update vr_ur until we reach an SN we haven't yet received
while (rx_window.end() != rx_window.find(vr_ur)) {
log->debug("Reassemble loop for vr_ur=%d\n", vr_ur);
if (not pdu_belongs_to_rx_sdu()) {
log->warning(
"PDU SN=%d lost, stop reassambling SDU (vr_ur_in_rx_sdu=%d)\n", vr_ur_in_rx_sdu + 1, vr_ur_in_rx_sdu);
pdu_lost = false; // Reset flag to not prevent reassembling of further segments
rx_sdu->clear();
}
// Handle any SDU segments
for (uint32_t i = 0; i < rx_window[vr_ur].header.N_li; i++) {
uint16_t len = rx_window[vr_ur].header.li[i];
log->debug("Handling SDU segment i=%d with len=%d of vr_ur=%d N_li=%d [%s]\n",
i,
len,
vr_ur,
rx_window[vr_ur].header.N_li,
rlc_fi_field_text[rx_window[vr_ur].header.fi]);
// Check if the first part of the PDU is a middle or end segment
if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->warning_hex(
rx_window[vr_ur].buf->msg, len, "Dropping first %d B of SN %d due to lost start segment\n", len, vr_ur);
if (rx_window[vr_ur].buf->N_bytes < len) {
log->error("Dropping remaining remainder of SN %d too (N_bytes=%u < len=%d)\n",
vr_ur,
rx_window[vr_ur].buf->N_bytes,
len);
goto clean_up_rx_window;
}
// Advance data pointers and continue with next segment
rx_window[vr_ur].buf->msg += len;
rx_window[vr_ur].buf->N_bytes -= len;
rx_sdu->clear();
// Reset flag, it is safe to process all remaining segments of this PDU
pdu_lost = false;
continue;
}
// Check available space in SDU
if ((uint32_t)len > rx_sdu->get_tailroom()) {
log->error("Dropping PDU %d due to buffer mis-alignment (current segment len %d B, received %d B)\n",
vr_ur,
rx_sdu->N_bytes,
len);
rx_sdu->clear();
goto clean_up_rx_window;
}
if (not pdu_belongs_to_rx_sdu()) {
log->info_hex(rx_window[vr_ur].buf->msg, len, "Copying first %d bytes of new SDU\n", len);
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.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);
rx_sdu->N_bytes += len;
rx_window[vr_ur].buf->msg += len;
rx_window[vr_ur].buf->N_bytes -= len;
vr_ur_in_rx_sdu = vr_ur;
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)",
rb_name.c_str(),
vr_ur,
i);
rx_sdu->set_timestamp();
if (cfg.um.is_mrb) {
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
} else {
pdcp->write_pdu(lcid, std::move(rx_sdu));
}
rx_sdu = allocate_unique_buffer(*pool);
if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
}
} else {
log->warning("Dropping remainder of lost PDU (update vr_ur middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n",
vr_ur,
vr_ur_in_rx_sdu);
// Advance data pointers and continue with next segment
rx_window[vr_ur].buf->msg += len;
rx_window[vr_ur].buf->N_bytes -= len;
}
pdu_lost = false;
}
// Handle last segment
if (rx_sdu->N_bytes == 0 && rx_window[vr_ur].header.N_li == 0 &&
!rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->warning("Dropping PDU %d due to lost start segment\n", vr_ur);
rx_sdu->clear();
goto clean_up_rx_window;
}
if (rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES &&
rx_window[vr_ur].buf->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES &&
rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) {
log->info_hex(rx_window[vr_ur].buf->msg,
rx_window[vr_ur].buf->N_bytes,
"Writing last segment in SDU buffer. Updating vr_ur=%d, vr_ur_in_rx_sdu=%d, Buffer size=%d, "
"segment size=%d\n",
vr_ur,
vr_ur_in_rx_sdu,
rx_sdu->N_bytes,
rx_window[vr_ur].buf->N_bytes);
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes);
rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes;
} else {
log->error("Out of bounds while reassembling SDU buffer in UM: sdu_len=%d, window_buffer_len=%d, vr_ur=%d\n",
rx_sdu->N_bytes,
rx_window[vr_ur].buf->N_bytes,
vr_ur);
}
vr_ur_in_rx_sdu = vr_ur;
if (rlc_um_end_aligned(rx_window[vr_ur].header.fi)) {
if (pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->warning("Dropping remainder of lost PDU (update vr_ur last segments)\n");
rx_sdu->clear();
} else {
log->info_hex(
rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (update vr_ur last segments)", rb_name.c_str(), vr_ur);
rx_sdu->set_timestamp();
if (cfg.um.is_mrb) {
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
} else {
pdcp->write_pdu(lcid, std::move(rx_sdu));
}
rx_sdu = allocate_unique_buffer(*pool);
if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
}
}
pdu_lost = false;
}
clean_up_rx_window:
// Clean up rx_window
rx_window.erase(vr_ur);
vr_ur = (vr_ur + 1) % cfg.um.rx_mod;
}
}
// Only called when lock is hold
bool rlc_um_lte::rlc_um_lte_rx::pdu_belongs_to_rx_sdu()
{
// return true if the currently received 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;
}
// Only called when lock is hold
// 36.322 Section 5.1.2.2.1
bool rlc_um_lte::rlc_um_lte_rx::inside_reordering_window(uint16_t sn)
{
if (cfg.um.rx_window_size == 0 || rx_window.empty()) {
return true;
}
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;
}
}
/****************************************************************************
* Timeout callback interface
***************************************************************************/
void rlc_um_lte::rlc_um_lte_rx::timer_expired(uint32_t timeout_id)
{
std::lock_guard<std::mutex> lock(mutex);
if (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", rb_name.c_str());
log->warning("Lost PDU SN: %d\n", vr_ur);
pdu_lost = true;
if (rx_sdu != NULL) {
rx_sdu->clear();
}
while (RX_MOD_BASE(vr_ur) < RX_MOD_BASE(vr_ux)) {
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);
}
if (RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) {
reordering_timer.run();
vr_ux = vr_uh;
}
debug_state();
}
}
/****************************************************************************
* Helper functions
***************************************************************************/
void rlc_um_lte::rlc_um_lte_rx::debug_state()
{
log->debug("%s vr_ur = %d, vr_ux = %d, vr_uh = %d\n", rb_name.c_str(), vr_ur, vr_ux, vr_uh);
}
/****************************************************************************
* Header pack/unpack helper functions
* Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1
***************************************************************************/
void rlc_um_read_data_pdu_header(byte_buffer_t* pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t* header)
{
rlc_um_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header);
}
void rlc_um_read_data_pdu_header(uint8_t* payload,
uint32_t nof_bytes,
rlc_umd_sn_size_t sn_size,
rlc_umd_pdu_header_t* header)
{
uint8_t ext;
uint8_t* ptr = payload;
// Fixed part
if (sn_size == rlc_umd_sn_size_t::size5bits) {
header->fi = (rlc_fi_field_t)((*ptr >> 6) & 0x03); // 2 bits FI
ext = ((*ptr >> 5) & 0x01); // 1 bit EXT
header->sn = *ptr & 0x1F; // 5 bits SN
ptr++;
} else {
header->fi = (rlc_fi_field_t)((*ptr >> 3) & 0x03); // 2 bits FI
ext = ((*ptr >> 2) & 0x01); // 1 bit EXT
header->sn = (*ptr & 0x03) << 8; // 2 bits SN
ptr++;
header->sn |= (*ptr & 0xFF); // 8 bits SN
ptr++;
}
header->sn_size = sn_size;
// Extension part
header->N_li = 0;
while (ext) {
if (header->N_li % 2 == 0) {
ext = ((*ptr >> 7) & 0x01);
header->li[header->N_li] = (*ptr & 0x7F) << 4; // 7 bits of LI
ptr++;
header->li[header->N_li] |= (*ptr & 0xF0) >> 4; // 4 bits of LI
header->N_li++;
} else {
ext = (*ptr >> 3) & 0x01;
header->li[header->N_li] = (*ptr & 0x07) << 8; // 3 bits of LI
ptr++;
header->li[header->N_li] |= (*ptr & 0xFF); // 8 bits of LI
header->N_li++;
ptr++;
}
}
}
void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu)
{
uint32_t i;
uint8_t ext = (header->N_li > 0) ? 1 : 0;
// Make room for the header
uint32_t len = rlc_um_packed_length(header);
pdu->msg -= len;
uint8_t* ptr = pdu->msg;
// Fixed part
if (header->sn_size == rlc_umd_sn_size_t::size5bits) {
*ptr = (header->fi & 0x03) << 6; // 2 bits FI
*ptr |= (ext & 0x01) << 5; // 1 bit EXT
*ptr |= header->sn & 0x1F; // 5 bits SN
ptr++;
} else {
*ptr = (header->fi & 0x03) << 3; // 3 Reserved bits | 2 bits FI
*ptr |= (ext & 0x01) << 2; // 1 bit EXT
*ptr |= (header->sn & 0x300) >> 8; // 2 bits SN
ptr++;
*ptr = (header->sn & 0xFF); // 8 bits SN
ptr++;
}
// Extension part
i = 0;
while (i < header->N_li) {
ext = ((i + 1) == header->N_li) ? 0 : 1;
*ptr = (ext & 0x01) << 7; // 1 bit header
*ptr |= (header->li[i] & 0x7F0) >> 4; // 7 bits of LI
ptr++;
*ptr = (header->li[i] & 0x00F) << 4; // 4 bits of LI
i++;
if (i < header->N_li) {
ext = ((i + 1) == header->N_li) ? 0 : 1;
*ptr |= (ext & 0x01) << 3; // 1 bit header
*ptr |= (header->li[i] & 0x700) >> 8; // 3 bits of LI
ptr++;
*ptr = (header->li[i] & 0x0FF); // 8 bits of LI
ptr++;
i++;
}
}
// Pad if N_li is odd
if (header->N_li % 2 == 1)
ptr++;
pdu->N_bytes += ptr - pdu->msg;
}
uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t* header)
{
uint32_t len = 0;
if (header->sn_size == rlc_umd_sn_size_t::size5bits) {
len += 1; // Fixed part is 1 byte
} else {
len += 2; // Fixed part is 2 bytes
}
len += header->N_li * 1.5 + 0.5; // Extension part - integer rounding up
return len;
}
bool rlc_um_start_aligned(uint8_t fi)
{
return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_END_ALIGNED);
}
bool rlc_um_end_aligned(uint8_t fi)
{
return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED);
}
} // namespace srslte

@ -0,0 +1,688 @@
/*
* 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/upper/rlc_um_nr.h"
#include <sstream>
#define RX_MOD_NR_BASE(x) (((x)-RX_Next_Highest - cfg.um_nr.UM_Window_Size) % cfg.um_nr.mod)
using namespace asn1::rrc;
namespace srslte {
rlc_um_nr::rlc_um_nr(srslte::log* log_,
uint32_t lcid_,
srsue::pdcp_interface_rlc* pdcp_,
srsue::rrc_interface_rlc* rrc_,
srslte::timer_handler* timers_) :
rlc_um_base(log_, lcid_, pdcp_, rrc_, timers_)
{
}
rlc_um_nr::~rlc_um_nr()
{
stop();
}
bool rlc_um_nr::configure(rlc_config_t cnfg_)
{
// determine bearer name and configure Rx/Tx objects
rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb);
// store config
cfg = cnfg_;
rx.reset(new rlc_um_nr_rx(this));
if (not rx->configure()) {
return false;
}
tx.reset(new rlc_um_nr_tx(this));
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));
rx_enabled = true;
tx_enabled = true;
return true;
}
/****************************************************************************
* Tx Subclass implementation
***************************************************************************/
rlc_um_nr::rlc_um_nr_tx::rlc_um_nr_tx(rlc_um_base* parent_) : rlc_um_base_tx(parent_) {}
uint32_t rlc_um_nr::rlc_um_nr_tx::get_buffer_state()
{
std::lock_guard<std::mutex> lock(mutex);
// Bytes needed for tx SDUs
uint32_t n_sdus = tx_sdu_queue.size();
uint32_t n_bytes = tx_sdu_queue.size_bytes();
if (tx_sdu) {
n_sdus++;
n_bytes += tx_sdu->N_bytes;
}
// Room needed for header extensions? (integer rounding)
if (n_sdus > 1) {
n_bytes += ((n_sdus - 1) * 1.5) + 0.5;
}
// Room needed for fixed header?
if (n_bytes > 0)
n_bytes += (cfg.um.is_mrb) ? 2 : 3;
return n_bytes;
}
bool rlc_um_nr::rlc_um_nr_tx::configure(rlc_config_t cnfg_, std::string rb_name_)
{
cfg = cnfg_;
if (cfg.um_nr.mod == 0) {
log->error("Error configuring %s RLC UM: tx_mod==0\n", rb_name.c_str());
return false;
}
tx_sdu_queue.resize(cnfg_.tx_queue_length);
rb_name = rb_name_;
return true;
}
int rlc_um_nr::rlc_um_nr_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes)
{
std::lock_guard<std::mutex> 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;
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",
rb_name.c_str(),
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", rb_name.c_str(), 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", rb_name.c_str(), 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());
header.so = next_so;
} 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", rb_name.c_str(), 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", rb_name.c_str(), 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;
}
// advance SO offset
next_so += 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;
next_so = 0;
}
// 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", rb_name.c_str(), header.sn, pdu->N_bytes);
debug_state();
return ret;
}
void rlc_um_nr::rlc_um_nr_tx::debug_state()
{
log->debug("%s TX_Next = %d\n", rb_name.c_str(), TX_Next);
}
/****************************************************************************
* Rx Subclass implementation
***************************************************************************/
rlc_um_nr::rlc_um_nr_rx::rlc_um_nr_rx(rlc_um_base* parent_) :
rlc_um_base_rx(parent_), reassembly_timer(timers->get_unique_timer())
{
}
bool rlc_um_nr::rlc_um_nr_rx::configure()
{
if (cfg.um_nr.mod == 0) {
log->error("Error configuring %s RLC UM: rx_mod==0\n", rb_name.c_str());
return false;
}
// check timer
if (not reassembly_timer.is_valid()) {
log->error("Configuring RLC UM NR RX: timers not configured\n");
return false;
}
// configure timer
if (cfg.um_nr.t_reassembly_ms > 0) {
reassembly_timer.set(static_cast<uint32_t>(cfg.um_nr.t_reassembly_ms),
[this](uint32_t tid) { timer_expired(tid); });
}
return true;
}
void rlc_um_nr::rlc_um_nr_rx::stop()
{
std::lock_guard<std::mutex> lock(mutex);
reset();
reassembly_timer.stop();
}
void rlc_um_nr::rlc_um_nr_rx::reset()
{
RX_Next_Reassembly = 0;
RX_Timer_Trigger = 0;
RX_Next_Highest = 0;
rx_sdu.reset();
// Drop all messages in RX window
rx_window.clear();
// stop timer
if (reassembly_timer.is_valid()) {
reassembly_timer.stop();
}
}
// TS 38.322 Sec. 5.1.2
void rlc_um_nr::rlc_um_nr_rx::reestablish()
{
// drop all SDUs, SDU segments, PDUs and reset timers
reset();
}
// TS 38.322 v15.003 Section 5.2.2.2.4
void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id)
{
std::lock_guard<std::mutex> lock(mutex);
if (reassembly_timer.id() == timeout_id) {
log->info("%s reassembly timeout expiry - updating RX_Next_Reassembly and reassembling\n", rb_name.c_str());
log->warning("Lost PDU SN: %d\n", RX_Next_Reassembly);
metrics.num_lost_pdus++;
if (rx_sdu != nullptr) {
rx_sdu->clear();
}
// update RX_Next_Reassembly to the next SN that has not been reassembled yet
RX_Next_Reassembly = RX_Timer_Trigger;
while (RX_MOD_NR_BASE(RX_Next_Reassembly) < RX_MOD_NR_BASE(RX_Next_Highest)) {
RX_Next_Reassembly = (RX_Next_Reassembly + 1) % cfg.um_nr.mod;
debug_state();
}
// discard all segments with SN < updated RX_Next_Reassembly
for (auto it = rx_window.begin(); it != rx_window.end();) {
if (it->first < RX_Next_Reassembly) {
it = rx_window.erase(it);
} else {
++it;
}
}
// check start of t_reassembly
if (RX_MOD_NR_BASE(RX_Next_Highest) > RX_MOD_NR_BASE(RX_Next_Reassembly + 1) ||
(RX_MOD_NR_BASE(RX_Next_Highest) == RX_MOD_NR_BASE(RX_Next_Reassembly + 1) &&
has_missing_byte_segment(RX_Next_Reassembly))) {
reassembly_timer.run();
RX_Timer_Trigger = RX_Next_Highest;
}
debug_state();
}
}
// Sec 5.2.2.2.1
bool rlc_um_nr::rlc_um_nr_rx::sn_in_reassembly_window(const uint32_t sn)
{
return (RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size) <= RX_MOD_NR_BASE(sn) &&
RX_MOD_NR_BASE(sn) < RX_MOD_NR_BASE(RX_Next_Highest));
}
// Sec 5.2.2.2.2
bool rlc_um_nr::rlc_um_nr_rx::sn_invalid_for_rx_buffer(const uint32_t sn)
{
return (RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size) <= RX_MOD_NR_BASE(sn) &&
RX_MOD_NR_BASE(sn) < RX_MOD_NR_BASE(RX_Next_Reassembly));
}
unique_byte_buffer_t rlc_um_nr::rlc_um_nr_rx::rlc_um_nr_strip_pdu_header(const rlc_um_nr_pdu_header_t& header,
const uint8_t* payload,
const uint32_t nof_bytes)
{
unique_byte_buffer_t sdu = allocate_unique_buffer(*pool);
if (!sdu) {
log->error("Discarting packet: no space in buffer pool\n");
return nullptr;
}
memcpy(sdu->msg, payload, nof_bytes);
sdu->N_bytes = nof_bytes;
// strip RLC header
int header_len = rlc_um_nr_packed_length(header);
sdu->msg += header_len;
sdu->N_bytes -= header_len;
return sdu;
}
bool rlc_um_nr::rlc_um_nr_rx::has_missing_byte_segment(const uint32_t sn)
{
// is at least one missing byte segment of the RLC SDU associated with SN = RX_Next_Reassembly before the last byte of
// all received segments of this RLC SDU
// FIXME: check assumption
// if SN can be found in rx_window, it means that at least one segment is missing
return (rx_window.find(sn) != rx_window.end());
}
// Sect 5.2.2.2.3
void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn)
{
if (rx_window.find(sn) != rx_window.end()) {
// iterate over received segments and try to assemble full SDU
auto& pdu = rx_window.at(sn);
for (auto it = pdu.segments.begin(); it != pdu.segments.end();) {
log->debug("Have %s segment with SO=%d for SN=%d\n",
to_string_short(it->second.header.si).c_str(),
it->second.header.so,
it->second.header.sn);
if (it->second.header.so == pdu.next_expected_so) {
if (pdu.next_expected_so == 0) {
if (pdu.sdu == nullptr) {
// reuse buffer of first segment for final SDU
pdu.sdu = std::move(it->second.buf);
pdu.next_expected_so = pdu.sdu->N_bytes;
log->debug("Reusing first segment of SN=%d for final SDU\n", it->second.header.sn);
it = pdu.segments.erase(it);
} else {
log->debug("SDU buffer already allocated. Possible retransmission of first segment.\n");
if (it->second.header.so != pdu.next_expected_so) {
log->error("Invalid PDU. SO doesn't match. Discarting all segments of SN=%d.\n", sn);
rx_window.erase(sn);
return;
}
}
} else {
if (it->second.buf->N_bytes > pdu.sdu->get_tailroom()) {
log->error("Cannot fit RLC PDU in SDU buffer (tailroom=%d, len=%d), dropping both. Erasing SN=%d.\n",
rx_sdu->get_tailroom(),
it->second.buf->N_bytes,
it->second.header.sn);
rx_window.erase(sn);
metrics.num_lost_pdus++;
return;
}
// add this segment to the end of the SDU buffer
memcpy(pdu.sdu->msg + pdu.sdu->N_bytes, it->second.buf->msg, it->second.buf->N_bytes);
pdu.sdu->N_bytes += it->second.buf->N_bytes;
pdu.next_expected_so += it->second.buf->N_bytes;
log->debug("Appended SO=%d of SN=%d\n", it->second.header.so, it->second.header.sn);
it = pdu.segments.erase(it);
if (pdu.next_expected_so == pdu.total_sdu_length) {
// deliver full SDU to upper layers
log->info("Delivering %s SDU SN=%d (%d B)", rb_name.c_str(), sn, pdu.sdu->N_bytes);
pdcp->write_pdu(lcid, std::move(pdu.sdu));
// find next SN in rx buffer
if (sn == RX_Next_Reassembly) {
RX_Next_Reassembly = ((RX_Next_Reassembly + 1) % cfg.um_nr.mod);
while (RX_MOD_NR_BASE(RX_Next_Reassembly) < RX_MOD_NR_BASE(RX_Next_Highest)) {
RX_Next_Reassembly = (RX_Next_Reassembly + 1) % cfg.um_nr.mod;
}
log->debug("Updating RX_Next_Reassembly=%d\n", RX_Next_Reassembly);
}
// delete PDU from rx_window
rx_window.erase(sn);
return;
}
}
} else {
// handle next segment
++it;
}
}
// check for SN outside of rx window
if (not sn_in_reassembly_window(sn)) {
// update RX_Next_highest
RX_Next_Highest = sn + 1;
log->debug("Updating RX_Next_Highest=%d\n", RX_Next_Highest);
// drop all SNs outside of new rx window
for (auto it = rx_window.begin(); it != rx_window.end();) {
if (not sn_in_reassembly_window(it->first)) {
log->info("%s SN: %d outside rx window [%d:%d] - discarding\n",
rb_name.c_str(),
it->first,
RX_Next_Highest - cfg.um_nr.UM_Window_Size,
RX_Next_Highest);
it = rx_window.erase(it);
metrics.num_lost_pdus++;
} else {
++it;
}
}
if (not sn_in_reassembly_window(RX_Next_Reassembly)) {
// update RX_Next_Reassembly to first SN that has not been reassembled and delivered
for (const auto& rx_pdu : rx_window) {
if (rx_pdu.first >= RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size)) {
RX_Next_Reassembly = rx_pdu.first;
log->debug("Updating RX_Next_Reassembly=%d\n", RX_Next_Reassembly);
break;
}
}
}
if (reassembly_timer.is_running()) {
if (RX_Timer_Trigger <= RX_Next_Reassembly ||
(not sn_in_reassembly_window(RX_Timer_Trigger) and RX_Timer_Trigger != RX_Next_Highest) ||
((RX_Next_Highest == RX_Next_Reassembly + 1) && not has_missing_byte_segment(sn))) {
reassembly_timer.stop();
}
}
if (not reassembly_timer.is_running() && has_missing_byte_segment(sn)) {
if (RX_Next_Highest > RX_Next_Reassembly + 1) {
reassembly_timer.run();
RX_Timer_Trigger = RX_Next_Highest;
}
}
}
} else {
log->error("SN=%d does not exist in Rx buffer\n", sn);
}
}
inline void rlc_um_nr::rlc_um_nr_rx::update_total_sdu_length(rlc_umd_pdu_segments_nr_t& pdu_segments,
const rlc_umd_pdu_nr_t& rx_pdu)
{
if (rx_pdu.header.si == rlc_nr_si_field_t::last_segment) {
pdu_segments.total_sdu_length = rx_pdu.header.so + rx_pdu.buf->N_bytes;
log->info("%s updating total SDU length for SN=%d to %d B\n",
rb_name.c_str(),
rx_pdu.header.sn,
pdu_segments.total_sdu_length);
}
};
// Section 5.2.2.2.2
void rlc_um_nr::rlc_um_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
{
std::lock_guard<std::mutex> lock(mutex);
rlc_um_nr_pdu_header_t header = {};
rlc_um_nr_read_data_pdu_header(payload, nof_bytes, cfg.um_nr.sn_field_length, &header);
log->debug_hex(payload, nof_bytes, "RX %s Rx data PDU (%d B)", rb_name.c_str(), nof_bytes);
// check if PDU contains a SN
if (header.si == rlc_nr_si_field_t::full_sdu) {
// copy full PDU into buffer
unique_byte_buffer_t sdu = rlc_um_nr_strip_pdu_header(header, payload, nof_bytes);
// deliver to PDCP
log->info("Delivering %s SDU (%d B)", rb_name.c_str(), sdu->N_bytes);
pdcp->write_pdu(lcid, std::move(sdu));
} else if (sn_invalid_for_rx_buffer(header.sn)) {
log->info("%s Discarding SN=%d\n", rb_name.c_str(), header.sn);
// Nothing else to do here ..
} else {
// place PDU in receive buffer
rlc_umd_pdu_nr_t rx_pdu = {};
rx_pdu.header = header;
rx_pdu.buf = rlc_um_nr_strip_pdu_header(header, payload, nof_bytes);
// check if this SN is already present in rx buffer
if (rx_window.find(header.sn) == rx_window.end()) {
// first received segment of this SN, add to rx buffer
log->info("%s placing %s segment of SN=%d in Rx buffer\n",
rb_name.c_str(),
to_string_short(header.si).c_str(),
header.sn);
rlc_umd_pdu_segments_nr_t pdu_segments = {};
update_total_sdu_length(pdu_segments, rx_pdu);
pdu_segments.segments.emplace(header.so, std::move(rx_pdu));
rx_window[header.sn] = std::move(pdu_segments);
} else {
// other segment for this SN already present, update received data
log->info("%s updating SN=%d at SO=%d with %d B\n",
rb_name.c_str(),
rx_pdu.header.sn,
rx_pdu.header.so,
rx_pdu.buf->N_bytes);
auto& pdu_segments = rx_window.at(header.sn);
// calculate total SDU length
update_total_sdu_length(pdu_segments, rx_pdu);
// append to list of segments
pdu_segments.segments.emplace(header.so, std::move(rx_pdu));
}
// handle received segments
handle_rx_buffer_update(header.sn);
}
debug_state();
}
void rlc_um_nr::rlc_um_nr_rx::debug_state()
{
log->debug("%s RX_Next_Reassembly=%d, RX_Timer_Trigger=%d, RX_Next_Highest=%d, t_Reassembly=%s\n",
rb_name.c_str(),
RX_Next_Reassembly,
RX_Timer_Trigger,
RX_Next_Highest,
reassembly_timer.is_running() ? "running" : "stopped");
}
/****************************************************************************
* 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<uint8_t*>(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 !(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

@ -35,6 +35,7 @@ target_link_libraries(rlc_stress_test srslte_upper srslte_phy srslte_common ${Bo
add_test(rlc_am_stress_test rlc_stress_test --mode=AM --loglevel 1 --sdu_gen_delay 250) add_test(rlc_am_stress_test rlc_stress_test --mode=AM --loglevel 1 --sdu_gen_delay 250)
add_test(rlc_um_stress_test rlc_stress_test --mode=UM --loglevel 1) add_test(rlc_um_stress_test rlc_stress_test --mode=UM --loglevel 1)
add_test(rlc_tm_stress_test rlc_stress_test --mode=TM --loglevel 1 --random_opp=false) add_test(rlc_tm_stress_test rlc_stress_test --mode=TM --loglevel 1 --random_opp=false)
add_test(rlc_um_nr_stress_test rlc_stress_test --rat NR --mode=UM --loglevel 1)
set_tests_properties(rlc_am_stress_test PROPERTIES TIMEOUT 3000) set_tests_properties(rlc_am_stress_test PROPERTIES TIMEOUT 3000)
set_tests_properties(rlc_um_stress_test PROPERTIES TIMEOUT 3000) set_tests_properties(rlc_um_stress_test PROPERTIES TIMEOUT 3000)
set_tests_properties(rlc_tm_stress_test PROPERTIES TIMEOUT 3000) set_tests_properties(rlc_tm_stress_test PROPERTIES TIMEOUT 3000)

@ -146,6 +146,9 @@ bool basic_test()
rlc_am_lte rlc1(&log1, 1, &tester, &tester, &timers); rlc_am_lte rlc1(&log1, 1, &tester, &tester, &timers);
rlc_am_lte rlc2(&log2, 1, &tester, &tester, &timers); rlc_am_lte rlc2(&log2, 1, &tester, &tester, &timers);
// before configuring entity
assert(0 == rlc1.get_buffer_state());
if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) {
return -1; return -1;
} }
@ -181,10 +184,14 @@ bool basic_test()
} }
// Check statistics // Check statistics
if (rlc1.get_num_tx_bytes() != rlc2.get_num_rx_bytes()) { rlc_bearer_metrics_t rlc1_metrics = rlc1.get_metrics();
rlc_bearer_metrics_t rlc2_metrics = rlc2.get_metrics();
if (rlc1_metrics.num_tx_bytes != rlc2_metrics.num_rx_bytes) {
return -1; return -1;
} }
if (rlc2.get_num_tx_bytes() != rlc1.get_num_rx_bytes()) {
if (rlc2_metrics.num_tx_bytes != rlc1_metrics.num_rx_bytes) {
return -1; return -1;
} }
@ -245,11 +252,15 @@ bool concat_test()
assert(*(tester.sdus[i]->msg) == i); assert(*(tester.sdus[i]->msg) == i);
} }
// check statistics // Check statistics
if (rlc1.get_num_tx_bytes() != rlc2.get_num_rx_bytes()) { rlc_bearer_metrics_t rlc1_metrics = rlc1.get_metrics();
rlc_bearer_metrics_t rlc2_metrics = rlc2.get_metrics();
if (rlc1_metrics.num_tx_bytes != rlc2_metrics.num_rx_bytes) {
return -1; return -1;
} }
if (rlc2.get_num_tx_bytes() != rlc1.get_num_rx_bytes()) {
if (rlc2_metrics.num_tx_bytes != rlc1_metrics.num_rx_bytes) {
return -1; return -1;
} }
@ -340,10 +351,15 @@ bool segment_test(bool in_seq_rx)
assert(tester.sdus[i]->msg[j] == j); assert(tester.sdus[i]->msg[j] == j);
} }
if (rlc1.get_num_tx_bytes() != rlc2.get_num_rx_bytes()) { // Check statistics
rlc_bearer_metrics_t rlc1_metrics = rlc1.get_metrics();
rlc_bearer_metrics_t rlc2_metrics = rlc2.get_metrics();
if (rlc1_metrics.num_tx_bytes != rlc2_metrics.num_rx_bytes) {
return -1; return -1;
} }
if (rlc2.get_num_tx_bytes() != rlc1.get_num_rx_bytes()) {
if (rlc2_metrics.num_tx_bytes != rlc1_metrics.num_rx_bytes) {
return -1; return -1;
} }

@ -35,12 +35,44 @@
#define LOG_HEX_LIMIT (-1) #define LOG_HEX_LIMIT (-1)
#define PCAP 1
#define PCAP_CRNTI (0x1001)
#define PCAP_TTI (666)
#if PCAP
#include "srslte/common/mac_nr_pcap.h"
#include "srslte/common/mac_nr_pdu.h"
static std::unique_ptr<srslte::mac_nr_pcap> pcap_handle = nullptr;
#endif
int write_pdu_to_pcap(const bool is_dl, const uint32_t lcid, const uint8_t* payload, const uint32_t len)
{
#if PCAP
if (pcap_handle) {
srslte::byte_buffer_t tx_buffer;
srslte::mac_nr_sch_pdu tx_pdu;
tx_pdu.init_tx(&tx_buffer, len + 10);
tx_pdu.add_sdu(lcid, payload, len);
tx_pdu.pack();
if (is_dl) {
pcap_handle->write_dl_crnti(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI);
} else {
pcap_handle->write_ul_crnti(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI);
}
return SRSLTE_SUCCESS;
}
#endif
return SRSLTE_ERROR;
}
using namespace std; using namespace std;
using namespace srsue; using namespace srsue;
using namespace srslte; using namespace srslte;
namespace bpo = boost::program_options; namespace bpo = boost::program_options;
typedef struct { typedef struct {
std::string rat;
std::string mode; std::string mode;
uint32_t sdu_size; uint32_t sdu_size;
uint32_t test_duration_sec; uint32_t test_duration_sec;
@ -72,6 +104,7 @@ void parse_args(stress_test_args_t *args, int argc, char *argv[]) {
// Command line or config file options // Command line or config file options
bpo::options_description common("Configuration options"); bpo::options_description common("Configuration options");
common.add_options() common.add_options()
("rat", bpo::value<std::string>(&args->rat)->default_value("LTE"), "The RLC version to use (LTE/NR)")
("mode", bpo::value<std::string>(&args->mode)->default_value("AM"), "Whether to test RLC acknowledged or unacknowledged mode (AM/UM)") ("mode", bpo::value<std::string>(&args->mode)->default_value("AM"), "Whether to test RLC acknowledged or unacknowledged mode (AM/UM)")
("duration", bpo::value<uint32_t>(&args->test_duration_sec)->default_value(5), "Duration (sec)") ("duration", bpo::value<uint32_t>(&args->test_duration_sec)->default_value(5), "Duration (sec)")
("sdu_size", bpo::value<uint32_t>(&args->sdu_size)->default_value(1500), "Size of SDUs") ("sdu_size", bpo::value<uint32_t>(&args->sdu_size)->default_value(1500), "Size of SDUs")
@ -171,6 +204,8 @@ private:
pdu_len = cut_pdu_len; pdu_len = cut_pdu_len;
} }
rx_rlc->write_pdu(lcid, pdu->msg, pdu_len); rx_rlc->write_pdu(lcid, pdu->msg, pdu_len);
write_pdu_to_pcap(is_dl, 4, pdu->msg, pdu_len);
if (is_dl) { if (is_dl) {
pcap->write_dl_am_ccch(pdu->msg, pdu_len); pcap->write_dl_am_ccch(pdu->msg, pdu_len);
} else { } else {
@ -304,16 +339,13 @@ void stress_test(stress_test_args_t args)
rlc_pcap pcap; rlc_pcap pcap;
uint32_t lcid = 1; uint32_t lcid = 1;
if (args.write_pcap) { rlc_config_t cnfg_ = {};
pcap.open("rlc_stress_test.pcap", 0); if (args.rat == "LTE") {
}
rlc_config_t cnfg_;
if (args.mode == "AM") { if (args.mode == "AM") {
// config RLC AM bearer // config RLC AM bearer
cnfg_.rlc_mode = rlc_mode_t::am; cnfg_.rlc_mode = rlc_mode_t::am;
cnfg_.am.max_retx_thresh = 4; cnfg_.am.max_retx_thresh = 4;
cnfg_.am.poll_byte = 25*1000; cnfg_.am.poll_byte = 25 * 1000;
cnfg_.am.poll_pdu = 4; cnfg_.am.poll_pdu = 4;
cnfg_.am.t_poll_retx = 5; cnfg_.am.t_poll_retx = 5;
cnfg_.am.t_reordering = 5; cnfg_.am.t_reordering = 5;
@ -335,6 +367,31 @@ void stress_test(stress_test_args_t args)
exit(-1); exit(-1);
} }
#if PCAP
if (args.write_pcap) {
pcap.open("rlc_stress_test.pcap", 0);
}
#endif
} else if (args.rat == "NR") {
if (args.mode == "UM") {
cnfg_ = rlc_config_t::default_rlc_um_nr_config(6);
} else {
cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl;
exit(-1);
}
#if PCAP
if (args.write_pcap) {
pcap_handle = std::unique_ptr<srslte::mac_nr_pcap>(new srslte::mac_nr_pcap());
pcap_handle->open("rlc_stress_test_nr.pcap");
}
#endif
} else {
cout << "Unsupported RAT mode " << args.rat << ", exiting." << endl;
exit(-1);
}
srslte::timer_handler timers(8); srslte::timer_handler timers(8);
rlc rlc1(&log1); rlc rlc1(&log1);
@ -394,20 +451,20 @@ void stress_test(stress_test_args_t args)
rlc_metrics_t metrics = {}; rlc_metrics_t metrics = {};
rlc1.get_metrics(metrics); rlc1.get_metrics(metrics);
printf("RLC1 received %d SDUs in %ds (%.2f/s), Throughput: DL=%4.2f Mbps, UL=%4.2f Mbps\n", printf("RLC1 received %d SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n",
tester1.get_nof_rx_pdus(), tester1.get_nof_rx_pdus(),
args.test_duration_sec, args.test_duration_sec,
static_cast<double>(tester1.get_nof_rx_pdus()/args.test_duration_sec), static_cast<double>(tester1.get_nof_rx_pdus() / args.test_duration_sec),
metrics.dl_tput_mbps[lcid], metrics.bearer[lcid].num_tx_bytes,
metrics.ul_tput_mbps[lcid]); metrics.bearer[lcid].num_rx_bytes);
rlc2.get_metrics(metrics); rlc2.get_metrics(metrics);
printf("RLC2 received %d SDUs in %ds (%.2f/s), Throughput: DL=%4.2f Mbps, UL=%4.2f Mbps\n", printf("RLC2 received %d SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n",
tester2.get_nof_rx_pdus(), tester2.get_nof_rx_pdus(),
args.test_duration_sec, args.test_duration_sec,
static_cast<double>(tester2.get_nof_rx_pdus()/args.test_duration_sec), static_cast<double>(tester2.get_nof_rx_pdus() / args.test_duration_sec),
metrics.dl_tput_mbps[lcid], metrics.bearer[lcid].num_tx_bytes,
metrics.ul_tput_mbps[lcid]); metrics.bearer[lcid].num_rx_bytes);
} }

@ -35,10 +35,23 @@ public:
// PDCP interface // PDCP interface
void write_pdu(uint32_t lcid, unique_byte_buffer_t sdu) void write_pdu(uint32_t lcid, unique_byte_buffer_t sdu)
{ {
// check length
if (lcid != 3 && sdu->N_bytes != expected_sdu_len) { 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); printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, expected_sdu_len);
exit(-1); exit(-1);
} }
// check content
uint8_t first_byte = *sdu->msg;
for (uint32_t i = 0; i < sdu->N_bytes; i++) {
if (sdu->msg[i] != first_byte) {
printf("Received corrupted SDU with size %d. Exiting.\n", sdu->N_bytes);
srslte_vec_fprint_byte(stdout, sdu->msg, sdu->N_bytes);
exit(-1);
}
}
// srslte_vec_fprint_byte(stdout, sdu->msg, sdu->N_bytes);
sdus.push_back(std::move(sdu)); sdus.push_back(std::move(sdu));
} }
void write_pdu_bcch_bch(unique_byte_buffer_t sdu) {} void write_pdu_bcch_bch(unique_byte_buffer_t sdu) {}

@ -19,9 +19,9 @@
* *
*/ */
#include <iostream> #include "srslte/upper/rlc_um_lte.h"
#include "srslte/upper/rlc_um.h"
#include <assert.h> #include <assert.h>
#include <iostream>
// Fixed header only // Fixed header only
uint8_t pdu1[] = {0x18 ,0xE2}; uint8_t pdu1[] = {0x18 ,0xE2};

@ -21,7 +21,7 @@
#include "srslte/config.h" #include "srslte/config.h"
#include "srslte/upper/rlc.h" #include "srslte/upper/rlc.h"
#include "srslte/upper/rlc_um.h" #include "srslte/upper/rlc_um_nr.h"
#include <array> #include <array>
#include <iostream> #include <iostream>
@ -53,7 +53,7 @@ int write_pdu_to_pcap(const uint32_t lcid, const uint8_t* payload, const uint32_
#if PCAP #if PCAP
if (pcap_handle) { if (pcap_handle) {
byte_buffer_t tx_buffer; byte_buffer_t tx_buffer;
srslte::mac_nr_pdu tx_pdu; srslte::mac_nr_sch_pdu tx_pdu;
tx_pdu.init_tx(&tx_buffer, len + 10); tx_pdu.init_tx(&tx_buffer, len + 10);
tx_pdu.add_sdu(lcid, payload, len); tx_pdu.add_sdu(lcid, payload, len);
tx_pdu.pack(); tx_pdu.pack();

@ -23,7 +23,7 @@
#include "srslte/common/log_filter.h" #include "srslte/common/log_filter.h"
#include "srslte/config.h" #include "srslte/config.h"
#include "srslte/upper/rlc.h" #include "srslte/upper/rlc.h"
#include "srslte/upper/rlc_um.h" #include "srslte/upper/rlc_um_nr.h"
#include <array> #include <array>
#include <iostream> #include <iostream>
@ -55,7 +55,7 @@ int write_pdu_to_pcap(const uint32_t lcid, const uint8_t* payload, const uint32_
#if PCAP #if PCAP
if (pcap_handle) { if (pcap_handle) {
byte_buffer_t tx_buffer; byte_buffer_t tx_buffer;
srslte::nr_mac_pdu tx_pdu; srslte::mac_nr_sch_pdu tx_pdu;
tx_pdu.init_tx(&tx_buffer, len + 10); tx_pdu.init_tx(&tx_buffer, len + 10);
tx_pdu.add_sdu(lcid, payload, len); tx_pdu.add_sdu(lcid, payload, len);
tx_pdu.pack(); tx_pdu.pack();
@ -76,29 +76,47 @@ srslte::byte_buffer_t make_pdu_and_log(const std::array<uint8_t, N>& tv)
return pdu; return pdu;
} }
// Basic test to write UM PDU with 6 bit SN // Helper class to create two pre-configured RLC instances
int rlc_um_nr_test1() class rlc_um_nr_test_context1
{ {
srslte::log_filter log1("RLC_UM_1"); public:
srslte::log_filter log2("RLC_UM_2"); rlc_um_nr_test_context1() :
log1("RLC_UM_1"),
log2("RLC_UM_2"),
timers(16),
rlc1(&log1, 3, &tester, &tester, &timers),
rlc2(&log2, 3, &tester, &tester, &timers)
{
// setup logging
log1.set_level(srslte::LOG_LEVEL_DEBUG); log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG); log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(-1); log1.set_hex_limit(-1);
log2.set_hex_limit(-1); log2.set_hex_limit(-1);
rlc_um_tester tester;
srslte::timer_handler 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);
// configure RLC entities
rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6); rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6);
if (rlc1.configure(cnfg) != true) {
TESTASSERT(rlc1.configure(cnfg) == true); fprintf(stderr, "Couldn't configure RLC1 object\n");
TESTASSERT(rlc2.configure(cnfg) == true); }
if (rlc2.configure(cnfg) != true) {
fprintf(stderr, "Couldn't configure RLC2 object\n");
}
tester.set_expected_sdu_len(1); tester.set_expected_sdu_len(1);
}
srslte::log_filter log1, log2;
srslte::timer_handler timers;
rlc_um_tester tester;
rlc_um_nr rlc1, rlc2;
};
// Basic test to write UM PDU with 6 bit SN (full SDUs are transmitted in each PDU)
int rlc_um_nr_test1()
{
rlc_um_nr_test_context1 ctxt;
const uint32_t num_sdus = 5, num_pdus = 5;
// Push 5 SDUs into RLC1 // Push 5 SDUs into RLC1
byte_buffer_pool* pool = byte_buffer_pool::get_instance(); byte_buffer_pool* pool = byte_buffer_pool::get_instance();
@ -107,53 +125,193 @@ int rlc_um_nr_test1()
sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true);
*sdu_bufs[i]->msg = i; // Write the index into the buffer *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 sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
TESTASSERT(14 == rlc1.get_buffer_state()); TESTASSERT(14 == ctxt.rlc1.get_buffer_state());
// Read 5 PDUs from RLC1 (1 byte each) // Read 5 PDUs from RLC1 (1 byte each)
unique_byte_buffer_t pdu_bufs[num_sdus]; unique_byte_buffer_t pdu_bufs[num_pdus];
for (uint32_t i = 0; i < num_sdus; i++) { for (uint32_t i = 0; i < num_pdus; i++) {
pdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); pdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true);
len = rlc1.read_pdu(pdu_bufs[i]->msg, 4); // 3 bytes for header + payload int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, 4); // 3 bytes for header + payload
pdu_bufs[i]->N_bytes = len; pdu_bufs[i]->N_bytes = len;
// write PCAP // write PCAP
write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
} }
TESTASSERT(0 == rlc1.get_buffer_state()); TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write 5 PDUs into RLC2
for (uint32_t i = 0; i < num_pdus; i++) {
ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
}
// TODO: add receive test TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(num_sdus == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == 1);
TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i);
}
return SRSLTE_SUCCESS; return SRSLTE_SUCCESS;
} }
// Basic test for SDU segmentation // Basic test for SDU segmentation
int rlc_um_nr_test2() int rlc_um_nr_test2(bool reverse_rx = false)
{ {
srslte::log_filter log1("RLC_UM_1"); rlc_um_nr_test_context1 ctxt;
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::timer_handler timers(16);
const uint32_t num_sdus = 1; const uint32_t num_sdus = 1;
const uint32_t sdu_size = 100; const uint32_t sdu_size = 100;
int len = 0;
rlc_um rlc1(&log1, 3, &tester, &tester, &timers); ctxt.tester.set_expected_sdu_len(sdu_size);
rlc_um rlc2(&log2, 3, &tester, &tester, &timers);
rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6); // 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;
ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
}
TESTASSERT(rlc1.configure(cnfg) == true); // FIXME: check buffer state calculation
TESTASSERT(rlc2.configure(cnfg) == true); TESTASSERT(103 == ctxt.rlc1.get_buffer_state());
tester.set_expected_sdu_len(1); // Read PDUs from RLC1 with grant of 25 Bytes each
const uint32_t max_num_pdus = 10;
uint32 num_pdus = 0;
unique_byte_buffer_t pdu_bufs[max_num_pdus];
while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) {
pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true);
int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 25); // 3 bytes for header + payload
pdu_bufs[num_pdus]->N_bytes = len;
// write PCAP
write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes);
num_pdus++;
}
TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write PDUs into RLC2
if (reverse_rx) {
// receive PDUs in reverse order
for (uint32_t i = num_pdus; i > 0; i--) {
ctxt.rlc2.write_pdu(pdu_bufs[i - 1]->msg, pdu_bufs[i - 1]->N_bytes);
}
} else {
// receive PDUs in order
for (uint32_t i = 0; i < num_pdus; i++) {
ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
}
}
TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(num_sdus == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size);
TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i);
}
return SRSLTE_SUCCESS;
}
// Test reception of segmented RLC PDUs (two different SDUs with same PDU segmentation)
int rlc_um_nr_test4()
{
rlc_um_nr_test_context1 ctxt;
const uint32_t num_sdus = 2;
const uint32_t sdu_size = 100;
ctxt.tester.set_expected_sdu_len(sdu_size);
// 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;
ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
}
// FIXME: check buffer state calculation
int bsr = ctxt.rlc1.get_buffer_state();
TESTASSERT(bsr == 205);
// Read PDUs from RLC1 with grant of 25 Bytes each
const uint32_t max_num_pdus = 20;
uint32 num_pdus = 0;
unique_byte_buffer_t pdu_bufs[max_num_pdus];
while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) {
pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true);
int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 25); // 3 bytes for header + payload
pdu_bufs[num_pdus]->N_bytes = len;
num_pdus++;
}
TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write PDUs into RLC2 (except 1 and 6
for (uint32_t i = 0; i < num_pdus; i++) {
if (i != 1 && i != 6) {
ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
}
}
// write remaining two PDUs in reverse-order (so SN=1 is received first)
ctxt.rlc2.write_pdu(pdu_bufs[6]->msg, pdu_bufs[6]->N_bytes);
write_pdu_to_pcap(4, pdu_bufs[6]->msg, pdu_bufs[6]->N_bytes);
ctxt.rlc2.write_pdu(pdu_bufs[1]->msg, pdu_bufs[1]->N_bytes);
write_pdu_to_pcap(4, pdu_bufs[1]->msg, pdu_bufs[1]->N_bytes);
TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(num_sdus == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size);
// common tester makes sure the all bytes within the SDU are the same, but it doesn't verify the SDUs are the ones
// transmitted, so check this here
if (i == 0) {
// first SDU is SN=1
TESTASSERT(*(ctxt.tester.sdus.at(i)->msg) == 0x01);
} else {
// second SDU is SN=0
TESTASSERT(*(ctxt.tester.sdus.at(i)->msg) == 0x00);
}
}
return SRSLTE_SUCCESS;
}
// Handling of re-transmitted segments (e.g. after PHY retransmission)
int rlc_um_nr_test5(const uint32_t last_sn)
{
rlc_um_nr_test_context1 ctxt;
const uint32_t num_sdus = 1;
const uint32_t sdu_size = 100;
ctxt.tester.set_expected_sdu_len(sdu_size);
// Push SDUs into RLC1 // Push SDUs into RLC1
byte_buffer_pool* pool = byte_buffer_pool::get_instance(); byte_buffer_pool* pool = byte_buffer_pool::get_instance();
@ -165,27 +323,242 @@ int rlc_um_nr_test2()
sdu_bufs[i]->msg[k] = i; sdu_bufs[i]->msg[k] = i;
} }
sdu_bufs[i]->N_bytes = sdu_size; sdu_bufs[i]->N_bytes = sdu_size;
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
// FIXME: check buffer state calculation // FIXME: check buffer state calculation
TESTASSERT(103 == rlc1.get_buffer_state()); TESTASSERT(103 == ctxt.rlc1.get_buffer_state());
// Read PDUs from RLC1 with grant of 25 Bytes each // Read PDUs from RLC1 with grant of 25 Bytes each
const uint32_t max_num_pdus = 10; const uint32_t max_num_pdus = 10;
uint32 num_pdus = 0;
unique_byte_buffer_t pdu_bufs[max_num_pdus]; 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); while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) {
len = rlc1.read_pdu(pdu_bufs[i]->msg, 25); // 3 bytes for header + payload pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true);
pdu_bufs[i]->N_bytes = len; int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 25); // 3 bytes for header + payload
pdu_bufs[num_pdus]->N_bytes = len;
// write PCAP // write PCAP
write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes);
num_pdus++;
}
TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write alls PDUs twice into RLC2 (except third)
for (uint32_t k = 0; k < 2; k++) {
for (uint32_t i = 0; i < num_pdus; i++) {
if (i != last_sn) {
ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
}
}
}
// Write third PDU
ctxt.rlc2.write_pdu(pdu_bufs[last_sn]->msg, pdu_bufs[last_sn]->N_bytes);
TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(num_sdus == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size);
TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i);
}
return SRSLTE_SUCCESS;
}
// Test of wrap-around of reassembly window
int rlc_um_nr_test6()
{
rlc_um_nr_test_context1 ctxt;
const uint32_t num_sdus = 64;
const uint32_t sdu_size = 10;
ctxt.tester.set_expected_sdu_len(sdu_size);
// 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;
ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
TESTASSERT(0 == rlc1.get_buffer_state()); // FIXME: check buffer state calculation
// TESTASSERT(103 == ctxt.rlc1.get_buffer_state());
// TODO: add receive test // Read PDUs from RLC1 with grant of 8 Bytes each
const uint32_t max_num_pdus = num_sdus * 2; // we need 2 PDUs for each SDU
uint32 num_pdus = 0;
unique_byte_buffer_t pdu_bufs[max_num_pdus];
while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) {
pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true);
int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 8); // 3 bytes for header + payload
pdu_bufs[num_pdus]->N_bytes = len;
// write PCAP
write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes);
num_pdus++;
}
TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write PDUs into RLC2
for (uint32_t i = 0; i < num_pdus; i++) {
ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
}
TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(num_sdus == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size);
TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i);
}
return SRSLTE_SUCCESS;
}
// Segment loss received too many new PDUs (lost PDU outside of reassembly window)
int rlc_um_nr_test7()
{
rlc_um_nr_test_context1 ctxt;
const uint32_t num_sdus = 64;
const uint32_t sdu_size = 10;
ctxt.tester.set_expected_sdu_len(sdu_size);
// 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;
ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
}
// FIXME: check buffer state calculation
// TESTASSERT(103 == ctxt.rlc1.get_buffer_state());
// Read PDUs from RLC1 with grant of 8 Bytes each
const uint32_t max_num_pdus = num_sdus * 2; // we need 2 PDUs for each SDU
uint32 num_pdus = 0;
unique_byte_buffer_t pdu_bufs[max_num_pdus];
while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) {
pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true);
int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 8); // 3 bytes for header + payload
pdu_bufs[num_pdus]->N_bytes = len;
// write PCAP
write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes);
num_pdus++;
}
TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write PDUs into RLC2 (except 11th)
for (uint32_t i = 0; i < num_pdus; i++) {
if (i != 10) {
ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
}
}
TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(num_sdus - 1 == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size);
}
rlc_bearer_metrics_t rlc2_metrics = ctxt.rlc2.get_metrics();
TESTASSERT(rlc2_metrics.num_lost_pdus == 1);
return SRSLTE_SUCCESS;
}
// Segment loss and expiry of reassembly timer
int rlc_um_nr_test8()
{
rlc_um_nr_test_context1 ctxt;
const uint32_t num_sdus = 10;
const uint32_t sdu_size = 10;
ctxt.tester.set_expected_sdu_len(sdu_size);
// 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;
ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
}
// FIXME: check buffer state calculation
// TESTASSERT(103 == ctxt.rlc1.get_buffer_state());
// Read PDUs from RLC1 with grant of 8 Bytes each
const uint32_t max_num_pdus = 20 * 2; // we need 2 PDUs for each SDU
uint32 num_pdus = 0;
unique_byte_buffer_t pdu_bufs[max_num_pdus];
while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) {
pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true);
int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 8); // 3 bytes for header + payload
pdu_bufs[num_pdus]->N_bytes = len;
// write PCAP
write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes);
num_pdus++;
}
TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write PDUs into RLC2 (except 2nd)
for (uint32_t i = 0; i < num_pdus; i++) {
if (i != 2) {
ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
}
}
TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
// let t-reassembly expire
while (ctxt.timers.nof_running_timers() != 0) {
ctxt.timers.step_all();
}
TESTASSERT(num_sdus - 1 == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size);
}
rlc_bearer_metrics_t rlc2_metrics = ctxt.rlc2.get_metrics();
TESTASSERT(rlc2_metrics.num_lost_pdus == 1);
return SRSLTE_SUCCESS; return SRSLTE_SUCCESS;
} }
@ -206,5 +579,39 @@ int main(int argc, char** argv)
fprintf(stderr, "rlc_um_nr_test2() failed.\n"); fprintf(stderr, "rlc_um_nr_test2() failed.\n");
return SRSLTE_ERROR; return SRSLTE_ERROR;
} }
// same like above but PDUs delivered in reverse order
if (rlc_um_nr_test2(true)) {
fprintf(stderr, "rlc_um_nr_test2(true) failed.\n");
return SRSLTE_ERROR;
}
if (rlc_um_nr_test4()) {
fprintf(stderr, "rlc_um_nr_test4() failed.\n");
return SRSLTE_ERROR;
}
for (uint32_t i = 0; i < 5; ++i) {
if (rlc_um_nr_test5(i)) {
fprintf(stderr, "rlc_um_nr_test5() for i=%d failed.\n", i);
return SRSLTE_ERROR;
}
}
if (rlc_um_nr_test6()) {
fprintf(stderr, "rlc_um_nr_test6() failed.\n");
return SRSLTE_ERROR;
}
if (rlc_um_nr_test7()) {
fprintf(stderr, "rlc_um_nr_test7() failed.\n");
return SRSLTE_ERROR;
}
if (rlc_um_nr_test8()) {
fprintf(stderr, "rlc_um_nr_test8() failed.\n");
return SRSLTE_ERROR;
}
return SRSLTE_SUCCESS; return SRSLTE_SUCCESS;
} }

@ -21,7 +21,7 @@
#include "rlc_test_common.h" #include "rlc_test_common.h"
#include "srslte/common/log_filter.h" #include "srslte/common/log_filter.h"
#include "srslte/upper/rlc_um.h" #include "srslte/upper/rlc_um_lte.h"
#include <iostream> #include <iostream>
#define TESTASSERT(cond) \ #define TESTASSERT(cond) \
@ -39,34 +39,44 @@ using namespace srslte;
using namespace srsue; using namespace srsue;
using namespace asn1::rrc; using namespace asn1::rrc;
int basic_test() // Helper class to create two pre-configured RLC instances
class rlc_um_lte_test_context1
{ {
srslte::log_filter log1("RLC_UM_1"); public:
srslte::log_filter log2("RLC_UM_2"); rlc_um_lte_test_context1() :
log1("RLC_UM_1"),
log2("RLC_UM_2"),
timers(16),
rlc1(&log1, 3, &tester, &tester, &timers),
rlc2(&log2, 3, &tester, &tester, &timers)
{
// setup logging
log1.set_level(srslte::LOG_LEVEL_DEBUG); log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG); log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(-1); log1.set_hex_limit(-1);
log2.set_hex_limit(-1); log2.set_hex_limit(-1);
rlc_um_tester tester;
srslte::timer_handler timers(16);
int len = 0;
rlc_um rlc1(&log1, 3, &tester, &tester, &timers);
rlc_um rlc2(&log2, 3, &tester, &tester, &timers);
// configure RLC entities
rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(10); rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(10);
cnfg.rlc_mode = rlc_mode_t::um; if (rlc1.configure(cnfg) != true) {
cnfg.um.t_reordering = 5; fprintf(stderr, "Couldn't configure RLC1 object\n");
cnfg.um.rx_sn_field_length = rlc_umd_sn_size_t::size10bits; }
cnfg.um.rx_window_size = 512; if (rlc2.configure(cnfg) != true) {
cnfg.um.rx_mod = 1024; fprintf(stderr, "Couldn't configure RLC2 object\n");
cnfg.um.tx_sn_field_length = rlc_umd_sn_size_t::size10bits; }
cnfg.um.tx_mod = 1024;
TESTASSERT(rlc1.configure(cnfg) == true);
TESTASSERT(rlc2.configure(cnfg) == true);
tester.set_expected_sdu_len(1); tester.set_expected_sdu_len(1);
}
srslte::log_filter log1, log2;
srslte::timer_handler timers;
rlc_um_tester tester;
rlc_um_lte rlc1, rlc2;
};
int basic_test()
{
rlc_um_lte_test_context1 ctxt;
// Push 5 SDUs into RLC1 // Push 5 SDUs into RLC1
byte_buffer_pool* pool = byte_buffer_pool::get_instance(); byte_buffer_pool* pool = byte_buffer_pool::get_instance();
@ -76,33 +86,31 @@ int basic_test()
sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true);
*sdu_bufs[i]->msg = i; // Write the index into the buffer *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 sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
TESTASSERT(14 == rlc1.get_buffer_state()); TESTASSERT(14 == ctxt.rlc1.get_buffer_state());
// Read 5 PDUs from RLC1 (1 byte each) // Read 5 PDUs from RLC1 (1 byte each)
byte_buffer_t pdu_bufs[NBUFS]; byte_buffer_t pdu_bufs[NBUFS];
for(int i=0;i<NBUFS;i++) for (int i = 0; i < NBUFS; i++) {
{ int len = ctxt.rlc1.read_pdu(pdu_bufs[i].msg, 4); // 3 bytes for header + payload
len = rlc1.read_pdu(pdu_bufs[i].msg, 4); // 3 bytes for header + payload
pdu_bufs[i].N_bytes = len; pdu_bufs[i].N_bytes = len;
} }
TESTASSERT(0 == rlc1.get_buffer_state()); TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write 5 PDUs into RLC2 // Write 5 PDUs into RLC2
for(int i=0;i<NBUFS;i++) for (int i = 0; i < NBUFS; i++) {
{ ctxt.rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
} }
TESTASSERT(0 == rlc2.get_buffer_state()); TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(NBUFS == tester.get_num_sdus()); TESTASSERT(NBUFS == ctxt.tester.get_num_sdus());
for (uint32_t i = 0; i < tester.sdus.size(); i++) { for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(tester.sdus.at(i)->N_bytes == 1); TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == 1);
TESTASSERT(*(tester.sdus[i]->msg) == i); TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i);
} }
return 0; return 0;
@ -110,87 +118,53 @@ int basic_test()
int loss_test() int loss_test()
{ {
srslte::log_filter log1("RLC_UM_1"); rlc_um_lte_test_context1 ctxt;
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::timer_handler timers(16);
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_config(10);
rlc1.configure(cnfg);
rlc2.configure(cnfg);
tester.set_expected_sdu_len(1);
// Push 5 SDUs into RLC1 // Push 5 SDUs into RLC1
byte_buffer_pool* pool = byte_buffer_pool::get_instance(); byte_buffer_pool* pool = byte_buffer_pool::get_instance();
unique_byte_buffer_t sdu_bufs[NBUFS]; unique_byte_buffer_t sdu_bufs[NBUFS];
for(int i=0;i<NBUFS;i++) for (int i = 0; i < NBUFS; i++) {
{
sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true);
sdu_bufs[i]->msg[0] = i; // Write the index into the buffer sdu_bufs[i]->msg[0] = i; // Write the index into the buffer
sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
TESTASSERT(14 == rlc1.get_buffer_state()); TESTASSERT(14 == ctxt.rlc1.get_buffer_state());
// Read 5 PDUs from RLC1 (1 byte each) // Read 5 PDUs from RLC1 (1 byte each)
byte_buffer_t pdu_bufs[NBUFS]; byte_buffer_t pdu_bufs[NBUFS];
for(int i=0;i<NBUFS;i++) for (int i = 0; i < NBUFS; i++) {
{ int len = ctxt.rlc1.read_pdu(pdu_bufs[i].msg, 4); // 3 bytes for header + payload
len = rlc1.read_pdu(pdu_bufs[i].msg, 4); // 3 bytes for header + payload
pdu_bufs[i].N_bytes = len; pdu_bufs[i].N_bytes = len;
} }
TESTASSERT(0 == rlc1.get_buffer_state()); TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write 5 PDUs into RLC2 (skip SN 1) // Write 5 PDUs into RLC2 (skip SN 1)
for(int i=0;i<NBUFS;i++) for (int i = 0; i < NBUFS; i++) {
{ if (i != 1) {
if(i != 1) ctxt.rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes); }
} }
// Step the reordering timer until expiry // Step the reordering timer until expiry
// while (!timers.get(1)->is_expired()) while (ctxt.timers.nof_running_timers() != 0) {
// timers.get(1)->step(); ctxt.timers.step_all();
while (timers.nof_running_timers() != 0) {
timers.step_all();
} }
TESTASSERT(NBUFS - 1 == tester.sdus.size()); TESTASSERT(NBUFS - 1 == ctxt.tester.sdus.size());
return 0; return 0;
} }
int basic_mbsfn_test() int basic_mbsfn_test()
{ {
srslte::log_filter log1("RLC_UM_1"); rlc_um_lte_test_context1 ctxt;
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::timer_handler timers(16);
int len = 0;
rlc_um rlc1(&log1, 3, &tester, &tester, &timers); // configure as MCH
rlc_um rlc2(&log2, 3, &tester, &tester, &timers); ctxt.rlc1.configure(rlc_config_t::mch_config());
ctxt.rlc2.configure(rlc_config_t::mch_config());
rlc1.configure(rlc_config_t::mch_config());
rlc2.configure(rlc_config_t::mch_config());
tester.set_expected_sdu_len(1);
// Push 5 SDUs into RLC1 // Push 5 SDUs into RLC1
byte_buffer_pool* pool = byte_buffer_pool::get_instance(); byte_buffer_pool* pool = byte_buffer_pool::get_instance();
@ -200,33 +174,32 @@ int basic_mbsfn_test()
sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true);
sdu_bufs[i]->msg[0] = i; // Write the index into the buffer sdu_bufs[i]->msg[0] = i; // Write the index into the buffer
sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
TESTASSERT(13 == rlc1.get_buffer_state()); TESTASSERT(13 == ctxt.rlc1.get_buffer_state());
// Read 5 PDUs from RLC1 (1 byte each) // Read 5 PDUs from RLC1 (1 byte each)
byte_buffer_t pdu_bufs[NBUFS*2]; byte_buffer_t pdu_bufs[NBUFS*2];
for(int i=0;i<NBUFS;i++) for(int i=0;i<NBUFS;i++)
{ {
len = rlc1.read_pdu(pdu_bufs[i].msg, 3); // 2 bytes for header + payload int len = ctxt.rlc1.read_pdu(pdu_bufs[i].msg, 3); // 2 bytes for header + payload
pdu_bufs[i].N_bytes = len; pdu_bufs[i].N_bytes = len;
} }
TESTASSERT(0 == rlc1.get_buffer_state()); TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// Write 5 PDUs into RLC2 // Write 5 PDUs into RLC2
for(int i=0;i<NBUFS;i++) for (int i = 0; i < NBUFS; i++) {
{ ctxt.rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
} }
TESTASSERT(0 == rlc2.get_buffer_state()); TESTASSERT(0 == ctxt.rlc2.get_buffer_state());
TESTASSERT(NBUFS == tester.sdus.size()); TESTASSERT(NBUFS == ctxt.tester.sdus.size());
for (uint32_t i = 0; i < tester.sdus.size(); i++) { for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) {
TESTASSERT(tester.sdus[i]->N_bytes == 1); TESTASSERT(ctxt.tester.sdus[i]->N_bytes == 1);
TESTASSERT(*(tester.sdus[i]->msg) == i); TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i);
} }
return 0; return 0;
@ -245,30 +218,18 @@ int basic_mbsfn_test()
// PDUs than rx_mod are received. // PDUs than rx_mod are received.
int reassmble_test() int reassmble_test()
{ {
srslte::log_filter log1("RLC_UM_1"); rlc_um_lte_test_context1 ctxt;
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::timer_handler timers(16);
int len = 0;
rlc_um rlc1(&log1, 3, &tester, &tester, &timers);
rlc_um rlc2(&log2, 3, &tester, &tester, &timers);
// reconfigure them with 5bit SNs
rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(5); rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(5);
ctxt.rlc1.configure(cnfg);
rlc1.configure(cnfg); ctxt.rlc2.configure(cnfg);
rlc2.configure(cnfg);
// Push SDUs into RLC1 // Push SDUs into RLC1
const int n_sdus = 25; const int n_sdus = 25;
const int sdu_len = 100; const int sdu_len = 100;
tester.set_expected_sdu_len(sdu_len); ctxt.tester.set_expected_sdu_len(sdu_len);
const int n_sdu_first_batch = 17; const int n_sdu_first_batch = 17;
@ -280,7 +241,7 @@ int reassmble_test()
sdu_bufs[i]->msg[k] = i; sdu_bufs[i]->msg[k] = i;
} }
sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
// Read PDUs from RLC1 (use smaller grant for first PDU and large for the rest) // Read PDUs from RLC1 (use smaller grant for first PDU and large for the rest)
@ -290,7 +251,7 @@ int reassmble_test()
for(int i=0;i<max_n_pdus;i++) for(int i=0;i<max_n_pdus;i++)
{ {
pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate(); pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate();
len = rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * 3 / 4 : sdu_len * 1.25); int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * 3 / 4 : sdu_len * 1.25);
pdu_bufs[i]->N_bytes = len; pdu_bufs[i]->N_bytes = len;
if (len) { if (len) {
n_pdus++; n_pdus++;
@ -300,7 +261,7 @@ int reassmble_test()
} }
printf("Generated %d PDUs in first batch\n", n_pdus); printf("Generated %d PDUs in first batch\n", n_pdus);
TESTASSERT(0 == rlc1.get_buffer_state()); TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// push second batch of SDUs // push second batch of SDUs
for (int i = n_sdu_first_batch; i < n_sdus; ++i) { for (int i = n_sdu_first_batch; i < n_sdus; ++i) {
@ -309,14 +270,13 @@ int reassmble_test()
sdu_bufs[i]->msg[k] = i; sdu_bufs[i]->msg[k] = i;
} }
sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
// Read second batch of PDUs (use large grants) // Read second batch of PDUs (use large grants)
for(int i=n_pdus;i<max_n_pdus;i++) for (int i = n_pdus; i < max_n_pdus; i++) {
{
pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate(); pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate();
len = rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25); int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25);
pdu_bufs[i]->N_bytes = len; pdu_bufs[i]->N_bytes = len;
if (len) { if (len) {
n_pdus++; n_pdus++;
@ -332,14 +292,14 @@ int reassmble_test()
for(int i=0;i<n_pdus;i++) for(int i=0;i<n_pdus;i++)
{ {
if (i!=0) { if (i!=0) {
rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
} }
} }
// We should have received one SDU less than we tx'ed // We should have received one SDU less than we tx'ed
TESTASSERT(tester.sdus.size() == n_sdus - 1); TESTASSERT(ctxt.tester.sdus.size() == n_sdus - 1);
for (uint32_t i = 0; i < tester.sdus.size(); ++i) { for (uint32_t i = 0; i < ctxt.tester.sdus.size(); ++i) {
TESTASSERT(tester.sdus[i]->N_bytes == sdu_len); TESTASSERT(ctxt.tester.sdus[i]->N_bytes == sdu_len);
} }
return 0; return 0;
@ -354,29 +314,18 @@ int reassmble_test()
// Therefore, one SDU less should be received than was tx'ed. // Therefore, one SDU less should be received than was tx'ed.
int reassmble_test2() int reassmble_test2()
{ {
srslte::log_filter log1("RLC_UM_1"); rlc_um_lte_test_context1 ctxt;
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::timer_handler timers(16);
int len = 0;
rlc_um rlc1(&log1, 3, &tester, &tester, &timers);
rlc_um rlc2(&log2, 3, &tester, &tester, &timers);
// reconfigure them with 5bit SNs
rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(5); rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(5);
ctxt.rlc1.configure(cnfg);
rlc1.configure(cnfg); ctxt.rlc2.configure(cnfg);
rlc2.configure(cnfg);
// Push SDUs into RLC1 // Push SDUs into RLC1
const int n_sdus = 25; const int n_sdus = 25;
const int sdu_len = 100; const int sdu_len = 100;
tester.set_expected_sdu_len(sdu_len); ctxt.tester.set_expected_sdu_len(sdu_len);
const int n_sdu_first_batch = 17; const int n_sdu_first_batch = 17;
byte_buffer_pool* pool = byte_buffer_pool::get_instance(); byte_buffer_pool* pool = byte_buffer_pool::get_instance();
@ -387,16 +336,15 @@ int reassmble_test2()
sdu_bufs[i]->msg[k] = i; sdu_bufs[i]->msg[k] = i;
} }
sdu_bufs[i]->N_bytes = sdu_len; sdu_bufs[i]->N_bytes = sdu_len;
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
const int max_n_pdus = 100; const int max_n_pdus = 100;
int n_pdus = 0; int n_pdus = 0;
byte_buffer_t* pdu_bufs[max_n_pdus]; byte_buffer_t* pdu_bufs[max_n_pdus];
for(int i=0;i<max_n_pdus;i++) for (int i = 0; i < max_n_pdus; i++) {
{
pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate(); pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate();
len = rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * .75 : sdu_len * .25); int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * .75 : sdu_len * .25);
pdu_bufs[i]->N_bytes = len; pdu_bufs[i]->N_bytes = len;
if (len) { if (len) {
n_pdus++; n_pdus++;
@ -406,7 +354,7 @@ int reassmble_test2()
} }
printf("Generated %d PDUs in first batch\n", n_pdus); printf("Generated %d PDUs in first batch\n", n_pdus);
TESTASSERT(0 == rlc1.get_buffer_state()); TESTASSERT(0 == ctxt.rlc1.get_buffer_state());
// push second batch of SDUs // push second batch of SDUs
for (int i = n_sdu_first_batch; i < n_sdus; ++i) { for (int i = n_sdu_first_batch; i < n_sdus; ++i) {
@ -415,14 +363,14 @@ int reassmble_test2()
sdu_bufs[i]->msg[k] = i; sdu_bufs[i]->msg[k] = i;
} }
sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(std::move(sdu_bufs[i])); ctxt.rlc1.write_sdu(std::move(sdu_bufs[i]));
} }
// Read second batch of PDUs // Read second batch of PDUs
for(int i=n_pdus;i<max_n_pdus;i++) for(int i=n_pdus;i<max_n_pdus;i++)
{ {
pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate(); pdu_bufs[i] = byte_buffer_pool::get_instance()->allocate();
len = rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25); int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25);
pdu_bufs[i]->N_bytes = len; pdu_bufs[i]->N_bytes = len;
if (len) { if (len) {
n_pdus++; n_pdus++;
@ -436,14 +384,14 @@ int reassmble_test2()
// Write all PDUs into RLC2 except first one // Write all PDUs into RLC2 except first one
for(int i=0;i<n_pdus;i++) { for(int i=0;i<n_pdus;i++) {
if (i!=0) { if (i!=0) {
rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes);
} }
} }
// We should have received one SDU less than we tx'ed // We should have received one SDU less than we tx'ed
TESTASSERT(tester.sdus.size() == n_sdus - 1); TESTASSERT(ctxt.tester.sdus.size() == n_sdus - 1);
for (uint32_t i = 0; i < tester.sdus.size(); ++i) { for (uint32_t i = 0; i < ctxt.tester.sdus.size(); ++i) {
TESTASSERT(tester.sdus[i]->N_bytes == sdu_len); TESTASSERT(ctxt.tester.sdus[i]->N_bytes == sdu_len);
} }
return 0; return 0;

Loading…
Cancel
Save