Merge pull request #191 from softwareradiosystems/rlc_updates

RLC updates
master
Ismael Gomez 7 years ago committed by GitHub
commit d51bec49bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -34,6 +34,7 @@
#define MAC_LTE_DLT 147
#define NAS_LTE_DLT 148
#define RLC_LTE_DLT 149 // UDP needs to be selected as protocol
/* This structure gets written to the start of the file */
@ -104,6 +105,67 @@ typedef struct NAS_Context_Info_s {
} NAS_Context_Info_t;
/* RLC-LTE disector */
/* rlcMode */
#define RLC_TM_MODE 1
#define RLC_UM_MODE 2
#define RLC_AM_MODE 4
#define RLC_PREDEF 8
/* priority ? */
/* channelType */
#define CHANNEL_TYPE_CCCH 1
#define CHANNEL_TYPE_BCCH_BCH 2
#define CHANNEL_TYPE_PCCH 3
#define CHANNEL_TYPE_SRB 4
#define CHANNEL_TYPE_DRB 5
#define CHANNEL_TYPE_BCCH_DL_SCH 6
#define CHANNEL_TYPE_MCCH 7
#define CHANNEL_TYPE_MTCH 8
/* sequenceNumberLength */
#define UM_SN_LENGTH_5_BITS 5
#define UM_SN_LENGTH_10_BITS 10
#define AM_SN_LENGTH_10_BITS 10
#define AM_SN_LENGTH_16_BITS 16
/* Narrow band mode */
typedef enum {
rlc_no_nb_mode = 0,
rlc_nb_mode = 1
} rlc_lte_nb_mode;
/* Context information for every RLC PDU that will be logged */
typedef struct {
unsigned char rlcMode;
unsigned char direction;
unsigned char priority;
unsigned char sequenceNumberLength;
unsigned short ueid;
unsigned short channelType;
unsigned short channelId; /* for SRB: 1=SRB1, 2=SRB2, 3=SRB1bis; for DRB: DRB ID */
unsigned short pduLength;
bool extendedLiField;
rlc_lte_nb_mode nbMode;
} RLC_Context_Info_t;
// See Wireshark's packet-rlc-lte.h for details
#define RLC_LTE_START_STRING "rlc-lte"
#define RLC_LTE_SN_LENGTH_TAG 0x02
#define RLC_LTE_DIRECTION_TAG 0x03
#define RLC_LTE_PRIORITY_TAG 0x04
#define RLC_LTE_UEID_TAG 0x05
#define RLC_LTE_CHANNEL_TYPE_TAG 0x06
#define RLC_LTE_CHANNEL_ID_TAG 0x07
#define RLC_LTE_EXT_LI_FIELD_TAG 0x08
#define RLC_LTE_NB_MODE_TAG 0x09
#define RLC_LTE_PAYLOAD_TAG 0x01
/**************************************************************************
* API functions for opening/closing LTE PCAP files *
**************************************************************************/
@ -247,4 +309,93 @@ inline int LTE_PCAP_NAS_WritePDU(FILE *fd, NAS_Context_Info_t *context,
return 1;
}
/**************************************************************************
* API functions for writing RLC-LTE PCAP files *
**************************************************************************/
/* Write an individual RLC PDU (PCAP packet header + UDP header + rlc-context + rlc-pdu) */
inline int LTE_PCAP_RLC_WritePDU(FILE *fd, RLC_Context_Info_t *context,
const unsigned char *PDU, unsigned int length)
{
pcaprec_hdr_t packet_header;
char context_header[256];
int offset = 0;
uint16_t tmp16;
/* Can't write if file wasn't successfully opened */
if (fd == NULL) {
printf("Error: Can't write to empty file handle\n");
return 0;
}
/*****************************************************************/
// Add dummy UDP header, start with src and dest port
context_header[offset++] = 0xde;
context_header[offset++] = 0xad;
context_header[offset++] = 0xbe;
context_header[offset++] = 0xef;
// length
tmp16 = length + 12;
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
// dummy CRC
context_header[offset++] = 0xde;
context_header[offset++] = 0xad;
// Start magic string
memcpy(&context_header[offset], RLC_LTE_START_STRING, strlen(RLC_LTE_START_STRING));
offset += strlen(RLC_LTE_START_STRING);
// Fixed field RLC mode
context_header[offset++] = context->rlcMode;
// Conditional fields
if (context->rlcMode == RLC_UM_MODE) {
context_header[offset++] = RLC_LTE_SN_LENGTH_TAG;
context_header[offset++] = context->sequenceNumberLength;
}
// Optional fields
context_header[offset++] = RLC_LTE_DIRECTION_TAG;
context_header[offset++] = context->direction;
context_header[offset++] = RLC_LTE_PRIORITY_TAG;
context_header[offset++] = context->priority;
context_header[offset++] = RLC_LTE_UEID_TAG;
tmp16 = htons(context->ueid);
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
context_header[offset++] = RLC_LTE_CHANNEL_TYPE_TAG;
tmp16 = htons(context->channelType);
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
context_header[offset++] = RLC_LTE_CHANNEL_ID_TAG;
tmp16 = htons(context->channelId);
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
// Now the actual PDU
context_header[offset++] = RLC_LTE_PAYLOAD_TAG;
// PCAP header
struct timeval t;
gettimeofday(&t, NULL);
packet_header.ts_sec = t.tv_sec;
packet_header.ts_usec = t.tv_usec;
packet_header.incl_len = offset + length;
packet_header.orig_len = offset + length;
// Write everything to file
fwrite(&packet_header, sizeof(pcaprec_hdr_t), 1, fd);
fwrite(context_header, 1, offset, fd);
fwrite(PDU, 1, length, fd);
return 1;
}
#endif // SRSLTE_PCAP_H

@ -0,0 +1,65 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2015 Software Radio Systems Limited
*
* \section LICENSE
*
* This file is part of the srsUE library.
*
* srsUE 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.
*
* srsUE 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 RLCPCAP_H
#define RLCPCAP_H
#include <stdint.h>
#include "srslte/common/pcap.h"
namespace srslte {
class rlc_pcap
{
public:
rlc_pcap() {enable_write=false; ue_id=0; pcap_file = NULL; };
void enable(bool en);
void open(const char *filename, uint32_t ue_id = 0);
void close();
void set_ue_id(uint16_t ue_id);
void write_dl_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes);
void write_ul_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes);
private:
bool enable_write;
FILE *pcap_file;
uint32_t ue_id;
void pack_and_write(uint8_t* pdu,
uint32_t pdu_len_bytes,
uint8_t mode,
uint8_t direction,
uint8_t priority,
uint8_t seqnumberlength,
uint16_t ueid,
uint16_t channel_type,
uint16_t channel_id);
};
} // namespace srsue
#endif // RLCPCAP_H

@ -217,8 +217,10 @@ bool rlc_am_is_control_pdu(byte_buffer_t *pdu);
bool rlc_am_is_control_pdu(uint8_t *payload);
bool rlc_am_is_pdu_segment(uint8_t *payload);
std::string rlc_am_to_string(rlc_status_pdu_t *status);
bool rlc_am_start_aligned(uint8_t fi);
bool rlc_am_end_aligned(uint8_t fi);
bool rlc_am_start_aligned(const uint8_t fi);
bool rlc_am_end_aligned(const uint8_t fi);
bool rlc_am_is_unaligned(const uint8_t fi);
bool rlc_am_not_start_aligned(const uint8_t fi);
} // namespace srslte

@ -83,6 +83,9 @@ public:
srslte_rlc_am_config_t am;
srslte_rlc_um_config_t um;
// Default ctor
srslte_rlc_config_t(): rlc_mode(LIBLTE_RRC_RLC_MODE_AM), am(), um() {};
// Constructor based on liblte's RLC config
srslte_rlc_config_t(LIBLTE_RRC_RLC_CONFIG_STRUCT *cnfg) : rlc_mode(cnfg->rlc_mode), am(), um()
{

@ -0,0 +1,90 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2015 Software Radio Systems Limited
*
* \section LICENSE
*
* This file is part of the srsUE library.
*
* srsUE 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.
*
* srsUE 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 <stdint.h>
#include "srslte/srslte.h"
#include "srslte/common/pcap.h"
#include "srslte/common/rlc_pcap.h"
namespace srslte {
void rlc_pcap::enable(bool en)
{
enable_write = true;
}
void rlc_pcap::open(const char* filename, uint32_t ue_id)
{
fprintf(stdout, "Opening RLC PCAP with DLT=%d\n", RLC_LTE_DLT);
pcap_file = LTE_PCAP_Open(RLC_LTE_DLT, filename);
this->ue_id = ue_id;
enable_write = true;
}
void rlc_pcap::close()
{
fprintf(stdout, "Saving RLC PCAP file\n");
LTE_PCAP_Close(pcap_file);
}
void rlc_pcap::set_ue_id(uint16_t ue_id) {
this->ue_id = ue_id;
}
void rlc_pcap::pack_and_write(uint8_t* pdu, uint32_t pdu_len_bytes, uint8_t mode, uint8_t direction, uint8_t priority, uint8_t seqnumberlength, uint16_t ueid, uint16_t channel_type, uint16_t channel_id)
{
if (enable_write) {
RLC_Context_Info_t context;
context.rlcMode = mode;
context.direction = direction;
context.priority = priority;
context.sequenceNumberLength = seqnumberlength;
context.ueid = ueid;
context.channelType = channel_type;
context.channelId = channel_id;
context.pduLength = pdu_len_bytes;
if (pdu) {
LTE_PCAP_RLC_WritePDU(pcap_file, &context, pdu, pdu_len_bytes);
}
}
}
void rlc_pcap::write_dl_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes)
{
uint8_t priority = 0;
uint8_t seqnumberlength = 0; // normal length of 10bit
uint8_t channel_id = 0;
pack_and_write(pdu, pdu_len_bytes, RLC_AM_MODE, DIRECTION_DOWNLINK, priority, seqnumberlength, ue_id, CHANNEL_TYPE_CCCH, channel_id);
}
void rlc_pcap::write_ul_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes)
{
uint8_t priority = 0;
uint8_t seqnumberlength = 0; // normal length of 10bit
uint8_t channel_id = 0;
pack_and_write(pdu, pdu_len_bytes, RLC_AM_MODE, DIRECTION_UPLINK, priority, seqnumberlength, ue_id, CHANNEL_TYPE_CCCH, channel_id);
}
}

@ -670,6 +670,13 @@ int rlc_am::build_segment(uint8_t *payload, uint32_t nof_bytes, rlc_amd_retx_t r
lower += old_header.li[i];
}
// Make sure LI is not deleted in case the SDU boundary is crossed
// FIXME: fix if N_li > 1
if (new_header.N_li == 1 && retx.so_start + new_header.li[0] < retx.so_end && retx.so_end <= retx.so_start + pdu_space) {
// This segment crosses a SDU boundary
new_header.N_li++;
}
// Update retx_queue
if(tx_window[retx.sn].buf->N_bytes == retx.so_end) {
retx_queue.pop_front();
@ -913,6 +920,13 @@ void rlc_am::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes, rlc_amd_pdu_h
#endif
}
// check available space for payload
if (nof_bytes > pdu.buf->get_tailroom()) {
log->error("%s Discarding SN: %d of size %d B (available space %d B)\n",
rrc->get_rb_name(lcid).c_str(), header.sn, nof_bytes, pdu.buf->get_tailroom());
pool->deallocate(pdu.buf);
return;
}
memcpy(pdu.buf->msg, payload, nof_bytes);
pdu.buf->N_bytes = nof_bytes;
memcpy(&pdu.header, &header, sizeof(rlc_amd_pdu_header_t));
@ -1173,6 +1187,11 @@ void rlc_am::reassemble_rx_sdus()
for(uint32_t i=0; i<rx_window[vr_r].header.N_li; i++)
{
uint32_t len = rx_window[vr_r].header.li[i];
// sanity check to avoid zero-size SDUs
if (len == 0) {
break;
}
if (rx_sdu->get_tailroom() >= len) {
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_r].buf->msg, len);
rx_sdu->N_bytes += len;
@ -1345,7 +1364,13 @@ bool rlc_am::add_segment_and_check(rlc_amd_rx_pdu_segments_t *pdu, rlc_amd_rx_pd
count += it->header.li[i];
}
}
carryover = it->buf->N_bytes - count;
// accumulate segment sizes until end aligned PDU is received
if (rlc_am_not_start_aligned(it->header.fi)) {
carryover += it->buf->N_bytes - count;
} else {
carryover = it->buf->N_bytes - count;
}
tmpit = it;
if(rlc_am_end_aligned(it->header.fi) && ++tmpit != pdu->segments.end()) {
header.li[header.N_li++] = carryover;
@ -1741,14 +1766,24 @@ std::string rlc_am_to_string(rlc_status_pdu_t *status)
return ss.str();
}
bool rlc_am_start_aligned(uint8_t fi)
bool rlc_am_start_aligned(const uint8_t fi)
{
return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_END_ALIGNED);
}
bool rlc_am_end_aligned(uint8_t fi)
bool rlc_am_end_aligned(const uint8_t fi)
{
return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED);
}
bool rlc_am_is_unaligned(const uint8_t fi)
{
return (fi == RLC_FI_FIELD_NOT_START_OR_END_ALIGNED);
}
bool rlc_am_not_start_aligned(const uint8_t fi)
{
return (fi == RLC_FI_FIELD_NOT_START_ALIGNED || fi == RLC_FI_FIELD_NOT_START_OR_END_ALIGNED);
}
} // namespace srsue

@ -52,7 +52,7 @@ void rlc_tm::init(srslte::log *log_,
void rlc_tm::configure(srslte_rlc_config_t cnfg)
{
log->error("Attempted to configure TM RLC entity");
log->error("Attempted to configure TM RLC entity\n");
}
void rlc_tm::empty_queue()

@ -31,7 +31,7 @@
namespace srslte {
rlc_um::rlc_um() : tx_sdu_queue(16)
rlc_um::rlc_um() : tx_sdu_queue(32)
{
log = NULL;
pdcp = NULL;
@ -467,6 +467,17 @@ void rlc_um::reassemble_rx_sdus()
for(uint32_t i=0; i<rx_window[vr_ur].header.N_li; i++)
{
int len = rx_window[vr_ur].header.li[i];
// 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->reset();
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;
@ -488,27 +499,30 @@ void rlc_um::reassemble_rx_sdus()
}
// Handle last segment
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;
log->debug("Writting 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);
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->reset();
} else {
log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", rrc->get_rb_name(lcid).c_str(), vr_ur);
rx_sdu->set_timestamp();
pdcp->write_pdu(lcid, rx_sdu);
rx_sdu = pool_allocate;
if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
if (rx_sdu->N_bytes > 0 || rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->debug("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->reset();
} else {
log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", rrc->get_rb_name(lcid).c_str(), vr_ur);
rx_sdu->set_timestamp();
pdcp->write_pdu(lcid, rx_sdu);
rx_sdu = pool_allocate;
if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
}
}
pdu_lost = false;
}
pdu_lost = false;
}
// Clean up rx_window
@ -527,10 +541,21 @@ void rlc_um::reassemble_rx_sdus()
for(uint32_t i=0; i<rx_window[vr_ur].header.N_li; i++)
{
int len = rx_window[vr_ur].header.li[i];
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len);
// 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("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->reset();
break;
}
log->debug("Concatenating %d bytes in to current length %d. rx_window remaining bytes=%d, vr_ur_in_rx_sdu=%d, vr_ur=%d, rx_mod=%d, last_mod=%d\n",
len, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes, vr_ur_in_rx_sdu, vr_ur, cfg.rx_mod, (vr_ur_in_rx_sdu+1)%cfg.rx_mod);
rx_sdu->N_bytes += len;
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.rx_mod))) {
@ -548,8 +573,14 @@ void rlc_um::reassemble_rx_sdus()
}
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->reset();
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)
@ -557,7 +588,7 @@ void rlc_um::reassemble_rx_sdus()
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;
log->debug("Writting last segment in SDU buffer. Updating vr_ur=%d, Buffer size=%d, segment size=%d\n",
log->debug("Writing last segment in SDU buffer. Updating vr_ur=%d, Buffer size=%d, segment size=%d\n",
vr_ur, 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",
@ -582,6 +613,8 @@ void rlc_um::reassemble_rx_sdus()
pdu_lost = false;
}
clean_up_rx_window:
// Clean up rx_window
pool->deallocate(rx_window[vr_ur].buf);
rx_window.erase(vr_ur);

@ -30,9 +30,11 @@ add_executable(rlc_am_test rlc_am_test.cc)
target_link_libraries(rlc_am_test srslte_upper srslte_phy srslte_common)
add_test(rlc_am_test rlc_am_test)
add_executable(rlc_am_stress_test rlc_am_stress_test.cc)
target_link_libraries(rlc_am_stress_test srslte_upper srslte_phy srslte_common ${Boost_LIBRARIES})
add_test(rlc_am_stress_test rlc_am_stress_test --duration 10)
add_executable(rlc_stress_test rlc_stress_test.cc)
target_link_libraries(rlc_stress_test srslte_upper srslte_phy srslte_common ${Boost_LIBRARIES})
add_test(rlc_am_stress_test rlc_stress_test --mode=AM)
add_test(rlc_um_stress_test rlc_stress_test --mode=UM)
add_test(rlc_tm_stress_test rlc_stress_test --mode=TM --opp_sdu_ratio=1.0)
add_executable(rlc_um_data_test rlc_um_data_test.cc)
target_link_libraries(rlc_um_data_test srslte_upper srslte_phy srslte_common)

@ -40,6 +40,20 @@ uint32_t PDU2_LEN = 5;
uint8_t pdu3[] = {0x8C, 0x00, 0xDD, 0xCD, 0xDC, 0x5D, 0xC0};
uint32_t PDU3_LEN = 7;
// D/C = 1 = Data PDU
// RF = 0 = AMD PDU
// P = 0 = Status PDU is not requested
// FI = 11 = First byte of the Data field does not corresponds to the first byte of a RLC SDU,
// Last byte of the Data field does not corresponds to the last byte of a RLC SDU
// E = 1 = A set of E field and LI field follows from the octet following the fixed part of the header
// SN = 0000000010 -> SN 2
// E = 1
// LI1 = 1010011 1110 (1342 Dec)
// E = 0
// LI2 = 10111011100 (1500 Dec)
uint8_t pdu4[] = {0x9C, 0x02, 0xD3, 0xE5, 0xDC };
uint32_t PDU4_LEN = 5;
using namespace srslte;
int main(int argc, char **argv) {
@ -106,4 +120,26 @@ int main(int argc, char **argv) {
assert(b2.N_bytes == PDU3_LEN);
for(uint32_t i=0;i<b2.N_bytes;i++)
assert(b2.msg[i] == b1.msg[i]);
b1.reset();
b2.reset();
memset(&h, 0, sizeof(srslte::rlc_amd_pdu_header_t));
memcpy(b1.msg, &pdu4[0], PDU4_LEN);
b1.N_bytes = PDU4_LEN;
rlc_am_read_data_pdu_header(&b1, &h);
assert(RLC_DC_FIELD_DATA_PDU == h.dc);
assert(0x03 == h.fi);
assert(2 == h.N_li);
assert(1342 == h.li[0]);
assert(1500 == h.li[1]);
assert(0 == h.lsf);
assert(0 == h.p);
assert(0 == h.rf);
assert(0 == h.so);
assert(2 == h.sn);
rlc_am_write_data_pdu_header(&h, &b2);
assert(b2.N_bytes == PDU4_LEN);
for(uint32_t i=0;i<b2.N_bytes;i++)
assert(b2.msg[i] == b1.msg[i]);
}

@ -28,8 +28,10 @@
#include "srslte/common/log_filter.h"
#include "srslte/common/logger_stdout.h"
#include "srslte/upper/rlc_am.h"
#include "srslte/common/rlc_pcap.h"
#include <assert.h>
#define NBUFS 5
#define HAVE_PCAP 0
using namespace srsue;
using namespace srslte;
@ -54,9 +56,11 @@ class rlc_am_tester
,public rrc_interface_rlc
{
public:
rlc_am_tester(){
rlc_am_tester(rlc_pcap *pcap_ = NULL)
{
bzero(sdus, sizeof(sdus));
n_sdus = 0;
pcap = pcap_;
}
~rlc_am_tester(){
@ -83,6 +87,7 @@ public:
byte_buffer_t *sdus[10];
int n_sdus;
rlc_pcap *pcap;
};
void basic_test()
@ -1056,6 +1061,311 @@ void resegment_test_6()
}
}
// Retransmission of PDU segments of the same size
void resegment_test_7()
{
// SDUs: | 30 | 30 |
// PDUs: | 13 | 13 | 11 | 13 | 10 |
// Rxed PDUs | 13 | 13 | | 13 | 10 |
// Retx PDU segments: | 4 | 7 |
// Retx PDU segments: |3|3]3|2|
const uint32_t N_SDU_BUFS = 2;
const uint32_t N_PDU_BUFS = 5;
const uint32_t sdu_size = 30;
srslte::log_filter log1("RLC_AM_1");
srslte::log_filter log2("RLC_AM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(100);
log2.set_hex_limit(100);
#if HAVE_PCAP
rlc_pcap pcap;
pcap.open("rlc_am_test7.pcap", 0);
rlc_am_tester tester(&pcap);
#else
rlc_am_tester tester(NULL);
#endif
mac_dummy_timers timers;
rlc_am rlc1;
rlc_am rlc2;
int len;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 1, &tester, &tester, &timers);
rlc2.init(&log2, 1, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_AM;
cnfg.dl_am_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_am_rlc.t_status_prohibit = LIBLTE_RRC_T_STATUS_PROHIBIT_MS5;
cnfg.ul_am_rlc.max_retx_thresh = LIBLTE_RRC_MAX_RETX_THRESHOLD_T4;
cnfg.ul_am_rlc.poll_byte = LIBLTE_RRC_POLL_BYTE_KB25;
cnfg.ul_am_rlc.poll_pdu = LIBLTE_RRC_POLL_PDU_P4;
cnfg.ul_am_rlc.t_poll_retx = LIBLTE_RRC_T_POLL_RETRANSMIT_MS5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push 2 SDUs into RLC1
byte_buffer_t sdu_bufs[N_SDU_BUFS];
for(uint32_t i=0;i<N_SDU_BUFS;i++)
{
for(uint32_t j=0;j<sdu_size;j++) {
sdu_bufs[i].msg[j] = i;
}
sdu_bufs[i].N_bytes = sdu_size; // Give each buffer a size of 15 bytes
rlc1.write_sdu(&sdu_bufs[i]);
}
assert(65 == rlc1.get_buffer_state());
// Read PDUs from RLC1 (15 bytes each)
byte_buffer_t pdu_bufs[N_PDU_BUFS];
for(uint32_t i=0;i<N_PDU_BUFS;i++)
{
pdu_bufs[i].N_bytes = rlc1.read_pdu(pdu_bufs[i].msg, 15); // 2 bytes for header + 12 B payload
assert(pdu_bufs[i].N_bytes);
}
assert(0 == rlc1.get_buffer_state());
// Skip PDU with SN 2
for(uint32_t i=0;i<N_PDU_BUFS;i++) {
if (i!=2) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#endif
}
}
// Sleep to let reordering timeout expire
usleep(10000);
assert(12 == rlc1.get_buffer_state());
// first round of retx, forcing resegmentation
byte_buffer_t retx[4];
for (uint32_t i = 0; i < 4; i++) {
assert(rlc1.get_buffer_state());
retx[i].N_bytes = rlc1.read_pdu(retx[i].msg, 7);
assert(retx[i].N_bytes);
// Write the last two segments to RLC2
if (i > 1) {
rlc2.write_pdu(retx[i].msg, retx[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
}
usleep(10000);
// Read status PDU from RLC2
assert(rlc2.get_buffer_state());
byte_buffer_t status_buf;
status_buf.N_bytes = rlc2.read_pdu(status_buf.msg, 10); // 10 bytes is enough to hold the status
// Write status PDU to RLC1
rlc1.write_pdu(status_buf.msg, status_buf.N_bytes);
#if HAVE_PCAP
pcap.write_ul_am_ccch(status_buf.msg, status_buf.N_bytes);
#endif
assert(15 == rlc1.get_buffer_state());
// second round of retx, forcing resegmentation
byte_buffer_t retx2[4];
for (uint32_t i = 0; i < 4; i++) {
assert(rlc1.get_buffer_state() != 0);
retx2[i].N_bytes = rlc1.read_pdu(retx2[i].msg, 7);
assert(retx2[i].N_bytes != 0);
rlc2.write_pdu(retx2[i].msg, retx2[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
// check buffer states
assert(0 == rlc1.get_buffer_state());
assert(0 == rlc2.get_buffer_state());
// Check number of SDUs and their content
assert(tester.n_sdus == N_SDU_BUFS);
for(int i=0; i<tester.n_sdus; i++)
{
assert(tester.sdus[i]->N_bytes == sdu_size);
for(uint32_t j=0;j<N_SDU_BUFS;j++) {
assert(tester.sdus[i]->msg[j] == i);
}
}
#if HAVE_PCAP
pcap.close();
#endif
}
// Retransmission of PDU segments with different size
void resegment_test_8()
{
// SDUs: | 30 | 30 |
// PDUs: | 15 | 15 | 15 | 15 | 15 |
// Rxed PDUs | 15 | | 15 | 15 |
// Retx PDU segments: | 7 | 7 | 7 | 7 |
// Retx PDU segments: | 6 | 6 ] 6 | 6 | 6 | 6 | 6 | 6 |
const uint32_t N_SDU_BUFS = 2;
const uint32_t N_PDU_BUFS = 5;
const uint32_t sdu_size = 30;
srslte::log_filter log1("RLC_AM_1");
srslte::log_filter log2("RLC_AM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(100);
log2.set_hex_limit(100);
#if HAVE_PCAP
rlc_pcap pcap;
pcap.open("rlc_am_test8.pcap", 0);
rlc_am_tester tester(&pcap);
#else
rlc_am_tester tester(NULL);
#endif
mac_dummy_timers timers;
rlc_am rlc1;
rlc_am rlc2;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 1, &tester, &tester, &timers);
rlc2.init(&log2, 1, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_AM;
cnfg.dl_am_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_am_rlc.t_status_prohibit = LIBLTE_RRC_T_STATUS_PROHIBIT_MS5;
cnfg.ul_am_rlc.max_retx_thresh = LIBLTE_RRC_MAX_RETX_THRESHOLD_T4;
cnfg.ul_am_rlc.poll_byte = LIBLTE_RRC_POLL_BYTE_KB25;
cnfg.ul_am_rlc.poll_pdu = LIBLTE_RRC_POLL_PDU_P4;
cnfg.ul_am_rlc.t_poll_retx = LIBLTE_RRC_T_POLL_RETRANSMIT_MS5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push 2 SDUs into RLC1
byte_buffer_t sdu_bufs[N_SDU_BUFS];
for(uint32_t i=0;i<N_SDU_BUFS;i++)
{
for(uint32_t j=0;j<sdu_size;j++) {
sdu_bufs[i].msg[j] = i;
}
sdu_bufs[i].N_bytes = sdu_size; // Give each buffer a size of 15 bytes
rlc1.write_sdu(&sdu_bufs[i]);
}
assert(65 == rlc1.get_buffer_state());
// Read PDUs from RLC1 (15 bytes each)
byte_buffer_t pdu_bufs[N_PDU_BUFS];
for(uint32_t i=0;i<N_PDU_BUFS;i++)
{
pdu_bufs[i].N_bytes = rlc1.read_pdu(pdu_bufs[i].msg, 15); // 12 bytes for header + payload
assert(pdu_bufs[i].N_bytes);
}
assert(0 == rlc1.get_buffer_state());
// Skip PDU one and two
for(uint32_t i=0;i<N_PDU_BUFS;i++) {
if (i < 1 || i > 2) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#endif
}
}
// Sleep to let reordering timeout expire
usleep(10000);
assert(12 == rlc1.get_buffer_state());
// first round of retx, forcing resegmentation
byte_buffer_t retx[4];
for (uint32_t i = 0; i < 3; i++) {
assert(rlc1.get_buffer_state());
retx[i].N_bytes = rlc1.read_pdu(retx[i].msg, 8);
assert(retx[i].N_bytes);
// Write the last two segments to RLC2
if (i > 1) {
rlc2.write_pdu(retx[i].msg, retx[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
}
usleep(20000);
// Read status PDU from RLC2
assert(rlc2.get_buffer_state());
byte_buffer_t status_buf;
status_buf.N_bytes = rlc2.read_pdu(status_buf.msg, 10); // 10 bytes is enough to hold the status
// Write status PDU to RLC1
rlc1.write_pdu(status_buf.msg, status_buf.N_bytes);
#if HAVE_PCAP
pcap.write_ul_am_ccch(status_buf.msg, status_buf.N_bytes);
#endif
assert(15 == rlc1.get_buffer_state());
// second round of retx, reduce grant size to force different segment sizes
byte_buffer_t retx2[20];
for (uint32_t i = 0; i < 9; i++) {
assert(rlc1.get_buffer_state() != 0);
retx2[i].N_bytes = rlc1.read_pdu(retx2[i].msg, 7);
assert(retx2[i].N_bytes != 0);
rlc2.write_pdu(retx2[i].msg, retx2[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
// check buffer states
assert(0 == rlc1.get_buffer_state());
assert(0 == rlc2.get_buffer_state());
// Check number of SDUs and their content
assert(tester.n_sdus == N_SDU_BUFS);
for(int i=0; i<tester.n_sdus; i++)
{
assert(tester.sdus[i]->N_bytes == sdu_size);
for(uint32_t j=0;j<N_SDU_BUFS;j++) {
assert(tester.sdus[i]->msg[j] == i);
}
}
#if HAVE_PCAP
pcap.close();
#endif
}
void reset_test()
{
srslte::log_filter log1("RLC_AM_1");
@ -1141,6 +1451,12 @@ int main(int argc, char **argv) {
resegment_test_6();
byte_buffer_pool::get_instance()->cleanup();
resegment_test_7();
byte_buffer_pool::get_instance()->cleanup();
resegment_test_8();
byte_buffer_pool::get_instance()->cleanup();
reset_test();
byte_buffer_pool::get_instance()->cleanup();
}

@ -30,10 +30,14 @@
#include "srslte/common/log_filter.h"
#include "srslte/common/logger_stdout.h"
#include "srslte/common/threads.h"
#include "srslte/common/rlc_pcap.h"
#include "srslte/upper/rlc.h"
#include <boost/program_options.hpp>
#include <boost/program_options/parsers.hpp>
#include <assert.h>
#include <srslte/upper/rlc_interface.h>
#define SDU_SIZE 1500
using namespace std;
using namespace srsue;
@ -41,12 +45,16 @@ using namespace srslte;
namespace bpo = boost::program_options;
typedef struct {
uint32_t test_duration_sec;
float error_rate;
uint32_t sdu_gen_delay_usec;
uint32_t pdu_tx_delay_usec;
bool reestablish;
uint32_t log_level;
std::string mode;
uint32_t test_duration_sec;
float error_rate;
uint32_t sdu_gen_delay_usec;
uint32_t pdu_tx_delay_usec;
bool reestablish;
uint32_t log_level;
bool single_tx;
bool write_pcap;
float opp_sdu_ratio;
} stress_test_args_t;
void parse_args(stress_test_args_t *args, int argc, char *argv[]) {
@ -61,12 +69,16 @@ void parse_args(stress_test_args_t *args, int argc, char *argv[]) {
// Command line or config file options
bpo::options_description common("Configuration options");
common.add_options()
("duration", bpo::value<uint32_t>(&args->test_duration_sec)->default_value(10), "Duration (sec)")
("sdu_gen_delay", bpo::value<uint32_t>(&args->sdu_gen_delay_usec)->default_value(10), "SDU generation delay (usec)")
("pdu_tx_delay", bpo::value<uint32_t>(&args->pdu_tx_delay_usec)->default_value(10), "Delay in MAC for transfering PDU from tx'ing RLC to rx'ing RLC (usec)")
("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)")
("sdu_gen_delay", bpo::value<uint32_t>(&args->sdu_gen_delay_usec)->default_value(0), "SDU generation delay (usec)")
("pdu_tx_delay", bpo::value<uint32_t>(&args->pdu_tx_delay_usec)->default_value(0), "Delay in MAC for transfering PDU from tx'ing RLC to rx'ing RLC (usec)")
("error_rate", bpo::value<float>(&args->error_rate)->default_value(0.1), "Rate at which RLC PDUs are dropped")
("opp_sdu_ratio", bpo::value<float>(&args->opp_sdu_ratio)->default_value(0.0), "Ratio between MAC opportunity and SDU size (0==random)")
("reestablish", bpo::value<bool>(&args->reestablish)->default_value(false), "Mimic RLC reestablish during execution")
("loglevel", bpo::value<uint32_t>(&args->log_level)->default_value(srslte::LOG_LEVEL_DEBUG), "Log level (1=Error,2=Warning,3=Info,4=Debug");
("loglevel", bpo::value<uint32_t>(&args->log_level)->default_value(srslte::LOG_LEVEL_DEBUG), "Log level (1=Error,2=Warning,3=Info,4=Debug)")
("singletx", bpo::value<bool>(&args->single_tx)->default_value(false), "If set to true, only one node is generating data")
("pcap", bpo::value<bool>(&args->write_pcap)->default_value(false), "Whether to write all RLC PDU to PCAP file");
// these options are allowed on the command line
bpo::options_description cmdline_options;
@ -94,14 +106,18 @@ class mac_reader
:public thread
{
public:
mac_reader(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, uint32_t pdu_tx_delay_usec_)
mac_reader(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, float opp_sdu_ratio_, uint32_t pdu_tx_delay_usec_, rlc_pcap *pcap_, uint32_t lcid_, bool is_dl_ = true)
{
rlc1 = rlc1_;
rlc2 = rlc2_;
fail_rate = fail_rate_;
opp_sdu_ratio = opp_sdu_ratio_;
run_enable = true;
running = false;
pdu_tx_delay_usec = pdu_tx_delay_usec_;
pcap = pcap_;
is_dl = is_dl_;
lcid = lcid_;
}
void stop()
@ -129,14 +145,23 @@ private:
}
while(run_enable) {
float r = (float)rand()/RAND_MAX;
int opp_size = r*1500;
rlc1->get_buffer_state(1);
int read = rlc1->read_pdu(1, pdu->msg, opp_size);
if(((float)rand()/RAND_MAX > fail_rate) && read>0) {
rlc2->write_pdu(1, pdu->msg, opp_size);
// generate MAC opportunities of random size or with fixed ratio
float r = opp_sdu_ratio ? opp_sdu_ratio : (float)rand()/RAND_MAX;
int opp_size = r*SDU_SIZE;
uint32_t buf_state = rlc1->get_buffer_state(lcid);
if (buf_state) {
int read = rlc1->read_pdu(lcid, pdu->msg, opp_size);
if (pdu_tx_delay_usec) usleep(pdu_tx_delay_usec);
if(((float)rand()/RAND_MAX > fail_rate) && read>0) {
pdu->N_bytes = read;
rlc2->write_pdu(lcid, pdu->msg, pdu->N_bytes);
if (is_dl) {
pcap->write_dl_am_ccch(pdu->msg, pdu->N_bytes);
} else {
pcap->write_ul_am_ccch(pdu->msg, pdu->N_bytes);
}
}
}
usleep(pdu_tx_delay_usec);
}
running = false;
byte_buffer_pool::get_instance()->deallocate(pdu);
@ -145,7 +170,11 @@ private:
rlc_interface_mac *rlc1;
rlc_interface_mac *rlc2;
float fail_rate;
float opp_sdu_ratio;
uint32_t pdu_tx_delay_usec;
rlc_pcap *pcap;
uint32_t lcid;
bool is_dl;
bool run_enable;
bool running;
@ -155,9 +184,9 @@ class mac_dummy
:public srslte::mac_interface_timers
{
public:
mac_dummy(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, uint32_t pdu_tx_delay)
:r1(rlc1_, rlc2_, fail_rate_, pdu_tx_delay)
,r2(rlc2_, rlc1_, fail_rate_, pdu_tx_delay)
mac_dummy(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, float opp_sdu_ratio_, int32_t pdu_tx_delay, uint32_t lcid, rlc_pcap* pcap = NULL)
:r1(rlc1_, rlc2_, fail_rate_, opp_sdu_ratio_, pdu_tx_delay, pcap, lcid, true)
,r2(rlc2_, rlc1_, fail_rate_, opp_sdu_ratio_, pdu_tx_delay, pcap, lcid, false)
{
}
@ -189,19 +218,20 @@ private:
class rlc_am_tester
class rlc_tester
:public pdcp_interface_rlc
,public rrc_interface_rlc
,public thread
{
public:
rlc_am_tester(rlc_interface_pdcp *rlc_, std::string name_, uint32_t sdu_gen_delay_usec_){
rlc_tester(rlc_interface_pdcp *rlc_, std::string name_, uint32_t sdu_gen_delay_usec_, uint32_t lcid_){
rlc = rlc_;
run_enable = true;
running = false;
rx_pdus = 0;
name = name_;
sdu_gen_delay_usec = sdu_gen_delay_usec_;
lcid = lcid_;
}
void stop()
@ -219,11 +249,16 @@ public:
}
// PDCP interface
void write_pdu(uint32_t lcid, byte_buffer_t *sdu)
void write_pdu(uint32_t rx_lcid, byte_buffer_t *sdu)
{
assert(lcid == 1);
assert(rx_lcid == lcid);
if (sdu->N_bytes != SDU_SIZE) {
printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, SDU_SIZE);
exit(-1);
}
byte_buffer_pool::get_instance()->deallocate(sdu);
std::cout << "rlc_am_tester " << name << " received " << rx_pdus++ << " PDUs" << std::endl;
rx_pdus++;
}
void write_pdu_bcch_bch(byte_buffer_t *sdu) {}
void write_pdu_bcch_dlsch(byte_buffer_t *sdu) {}
@ -231,7 +266,9 @@ public:
// RRC interface
void max_retx_attempted(){}
std::string get_rb_name(uint32_t lcid) { return std::string(""); }
std::string get_rb_name(uint32_t rx_lcid) { return std::string(""); }
int get_nof_rx_pdus() { return rx_pdus; }
private:
void run_thread()
@ -239,15 +276,18 @@ private:
uint8_t sn = 0;
running = true;
while(run_enable) {
byte_buffer_t *pdu = byte_buffer_pool::get_instance()->allocate("rlc_am_tester::run_thread");
byte_buffer_t *pdu = byte_buffer_pool::get_instance()->allocate("rlc_tester::run_thread");
if (!pdu) {
printf("Fatal Error: Could not allocate PDU in rlc_am_tester::run_thread\n");
printf("Fatal Error: Could not allocate PDU in rlc_tester::run_thread\n");
exit(-1);
}
pdu->N_bytes = 1500;
pdu->msg[0] = sn++;
rlc->write_sdu(1, pdu);
usleep(sdu_gen_delay_usec);
for (uint32_t i = 0; i < SDU_SIZE; i++) {
pdu->msg[i] = sn;
}
sn++;
pdu->N_bytes = SDU_SIZE;
rlc->write_sdu(lcid, pdu);
if (sdu_gen_delay_usec) usleep(sdu_gen_delay_usec);
}
running = false;
}
@ -255,6 +295,7 @@ private:
bool run_enable;
bool running;
long rx_pdus;
uint32_t lcid;
std::string name;
@ -265,40 +306,67 @@ private:
void stress_test(stress_test_args_t args)
{
srslte::log_filter log1("RLC_AM_1");
srslte::log_filter log2("RLC_AM_2");
srslte::log_filter log1("RLC_1");
srslte::log_filter log2("RLC_2");
log1.set_level((LOG_LEVEL_ENUM)args.log_level);
log2.set_level((LOG_LEVEL_ENUM)args.log_level);
log1.set_hex_limit(-1);
log2.set_hex_limit(-1);
rlc_pcap pcap;
uint32_t lcid = 1;
if (args.write_pcap) {
pcap.open("rlc_stress_test.pcap", 0);
}
srslte_rlc_config_t cnfg_;
if (args.mode == "AM") {
// config RLC AM bearer
cnfg_.rlc_mode = LIBLTE_RRC_RLC_MODE_AM;
cnfg_.am.max_retx_thresh = 4;
cnfg_.am.poll_byte = 25*1000;
cnfg_.am.poll_pdu = 4;
cnfg_.am.t_poll_retx = 5;
cnfg_.am.t_reordering = 5;
cnfg_.am.t_status_prohibit = 5;
} else if (args.mode == "UM") {
// config UM bearer
cnfg_.rlc_mode = LIBLTE_RRC_RLC_MODE_UM_BI;
cnfg_.um.t_reordering = 5;
cnfg_.um.rx_mod = 32;
cnfg_.um.rx_sn_field_length = RLC_UMD_SN_SIZE_5_BITS;
cnfg_.um.rx_window_size = 16;
cnfg_.um.tx_sn_field_length = RLC_UMD_SN_SIZE_5_BITS;
cnfg_.um.tx_mod = 32;
} else if (args.mode == "TM") {
// use default LCID in TM
lcid = 0;
} else {
cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl;
exit(-1);
}
rlc rlc1;
rlc rlc2;
rlc_am_tester tester1(&rlc1, "tester1", args.sdu_gen_delay_usec);
rlc_am_tester tester2(&rlc2, "tester2", args.sdu_gen_delay_usec);
mac_dummy mac(&rlc1, &rlc2, args.error_rate, args.pdu_tx_delay_usec);
rlc_tester tester1(&rlc1, "tester1", args.sdu_gen_delay_usec, lcid);
rlc_tester tester2(&rlc2, "tester2", args.sdu_gen_delay_usec, lcid);
mac_dummy mac(&rlc1, &rlc2, args.error_rate, args.opp_sdu_ratio, args.pdu_tx_delay_usec, lcid, &pcap);
ue_interface ue;
rlc1.init(&tester1, &tester1, &ue, &log1, &mac, 0);
rlc2.init(&tester2, &tester2, &ue, &log2, &mac, 0);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_AM;
cnfg.dl_am_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_am_rlc.t_status_prohibit = LIBLTE_RRC_T_STATUS_PROHIBIT_MS5;
cnfg.ul_am_rlc.max_retx_thresh = LIBLTE_RRC_MAX_RETX_THRESHOLD_T4;
cnfg.ul_am_rlc.poll_byte = LIBLTE_RRC_POLL_BYTE_KB25;
cnfg.ul_am_rlc.poll_pdu = LIBLTE_RRC_POLL_PDU_P4;
cnfg.ul_am_rlc.t_poll_retx = LIBLTE_RRC_T_POLL_RETRANSMIT_MS5;
srslte_rlc_config_t cnfg_(&cnfg);
rlc1.add_bearer(1, cnfg_);
rlc2.add_bearer(1, cnfg_);
// only add AM and UM bearers
if (args.mode != "TM") {
rlc1.add_bearer(lcid, cnfg_);
rlc2.add_bearer(lcid, cnfg_);
}
tester1.start(7);
tester2.start(7);
if (!args.single_tx) {
tester2.start(7);
}
mac.start();
for (uint32_t i = 0; i < args.test_duration_sec; i++) {
@ -313,6 +381,19 @@ void stress_test(stress_test_args_t args)
tester1.stop();
tester2.stop();
mac.stop();
if (args.write_pcap) {
pcap.close();
}
printf("RLC1 received %d SDUs in %ds (%.2f PDU/s)\n",
tester1.get_nof_rx_pdus(),
args.test_duration_sec,
(float)tester1.get_nof_rx_pdus()/args.test_duration_sec);
printf("RLC2 received %d SDUs in %ds (%.2f PDU/s)\n",
tester2.get_nof_rx_pdus(),
args.test_duration_sec,
(float)tester2.get_nof_rx_pdus()/args.test_duration_sec);
}
@ -322,4 +403,6 @@ int main(int argc, char **argv) {
stress_test(args);
byte_buffer_pool::get_instance()->cleanup();
exit(0);
}

@ -29,6 +29,7 @@
#include "srslte/upper/rlc_um.h"
#include <assert.h>
#define MAX_NBUFS 100
#define NBUFS 5
using namespace srslte;
@ -60,6 +61,7 @@ public:
rlc_um_tester(){
bzero(sdus, sizeof(sdus));
n_sdus = 0;
expected_sdu_len = 0;
}
~rlc_um_tester(){
@ -74,6 +76,10 @@ public:
void write_pdu(uint32_t lcid, byte_buffer_t *sdu)
{
assert(lcid == 3);
if (sdu->N_bytes != expected_sdu_len) {
printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, expected_sdu_len);
exit(-1);
}
sdus[n_sdus++] = sdu;
}
void write_pdu_bcch_bch(byte_buffer_t *sdu) {}
@ -83,9 +89,11 @@ public:
// RRC interface
void max_retx_attempted(){}
std::string get_rb_name(uint32_t lcid) { return std::string(""); }
void set_expected_sdu_len(uint32_t len) { expected_sdu_len = len; }
byte_buffer_t *sdus[5];
byte_buffer_t *sdus[MAX_NBUFS];
int n_sdus;
uint32_t expected_sdu_len;
};
void basic_test()
@ -119,6 +127,8 @@ void basic_test()
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
tester.set_expected_sdu_len(1);
// Push 5 SDUs into RLC1
byte_buffer_t sdu_bufs[NBUFS];
for(int i=0;i<NBUFS;i++)
@ -187,6 +197,8 @@ void loss_test()
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
tester.set_expected_sdu_len(1);
// Push 5 SDUs into RLC1
byte_buffer_t sdu_bufs[NBUFS];
for(int i=0;i<NBUFS;i++)
@ -222,9 +234,243 @@ void loss_test()
assert(NBUFS-1 == tester.n_sdus);
}
// This test checks the reassembly routines when a PDU
// is lost that contains the beginning of SDU segment.
// The PDU that contains the end of this SDU _also_ contains
// a segment of another SDU.
// On reassembly of the SDUs, the missing start segment
// should be detected and the complete SDU be discarded
// Therefore, one SDU less should be received than was tx'ed.
// This test sends PDU in two batches so it's not the reordering
// timeout that detects the missing PDU but the fact more
// PDUs than rx_mod are received.
void reassmble_test()
{
srslte::log_filter log1("RLC_UM_1");
srslte::log_filter log2("RLC_UM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(-1);
log2.set_hex_limit(-1);
rlc_um_tester tester;
mac_dummy_timers timers;
rlc_um rlc1;
rlc_um rlc2;
int len;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 3, &tester, &tester, &timers);
rlc2.init(&log2, 3, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_UM_BI;
cnfg.dl_um_bi_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
cnfg.ul_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push SDUs into RLC1
const int n_sdus = 25;
const int sdu_len = 100;
tester.set_expected_sdu_len(sdu_len);
byte_buffer_t sdu_bufs[n_sdus];
const int n_sdu_first_batch = 17;
for(int i=0;i<n_sdu_first_batch;i++) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(&sdu_bufs[i]);
}
// Read PDUs from RLC1 (use smaller grant for first PDU and large for the rest)
const int max_n_pdus = 100;
int n_pdus = 0;
byte_buffer_t pdu_bufs[max_n_pdus];
for(int i=0;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, (i == 0) ? sdu_len*3/4 : sdu_len*1.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
break;
}
}
printf("Generated %d PDUs in first batch\n", n_pdus);
assert(0 == rlc1.get_buffer_state());
// push second batch of SDUs
for (int i = n_sdu_first_batch; i < n_sdus; ++i) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(&sdu_bufs[i]);
}
// Read second batch of PDUs (use large grants)
for(int i=n_pdus;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, sdu_len*1.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
// stop reading PDUs after first zero length PDU
break;
}
}
printf("Generated %d PDUs in total\n", n_pdus);
// Write all PDUs into RLC2 except first one
for(int i=0;i<n_pdus;i++)
{
if (i!=0) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
}
}
// We should have received one SDU less than we tx'ed
assert(tester.n_sdus == n_sdus - 1);
for (int i = 0; i < tester.n_sdus; ++i) {
assert(tester.sdus[i]->N_bytes == sdu_len);
}
}
// This reassmble test checks the reassembly routines when a PDU
// is lost that _only_ contains the beginning of SDU segment,
// while the next PDU contains the middle part of this SDU (and
// yet another PDU the end part).
// On reassembly of the SDUs, the missing start segment
// should be detected and the complete SDU be discarded
// Therefore, one SDU less should be received than was tx'ed.
void reassmble_test2()
{
srslte::log_filter log1("RLC_UM_1");
srslte::log_filter log2("RLC_UM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(-1);
log2.set_hex_limit(-1);
rlc_um_tester tester;
mac_dummy_timers timers;
rlc_um rlc1;
rlc_um rlc2;
int len;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 3, &tester, &tester, &timers);
rlc2.init(&log2, 3, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_UM_BI;
cnfg.dl_um_bi_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
cnfg.ul_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push SDUs into RLC1
const int n_sdus = 25;
const int sdu_len = 100;
tester.set_expected_sdu_len(sdu_len);
byte_buffer_t sdu_bufs[n_sdus];
const int n_sdu_first_batch = 17;
for(int i=0;i<n_sdu_first_batch;i++) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len;
rlc1.write_sdu(&sdu_bufs[i]);
}
const int max_n_pdus = 100;
int n_pdus = 0;
byte_buffer_t pdu_bufs[max_n_pdus];
for(int i=0;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, (i == 0) ? sdu_len*.75 : sdu_len*.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
break;
}
}
printf("Generated %d PDUs in first batch\n", n_pdus);
assert(0 == rlc1.get_buffer_state());
// push second batch of SDUs
for (int i = n_sdu_first_batch; i < n_sdus; ++i) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(&sdu_bufs[i]);
}
// Read second batch of PDUs
for(int i=n_pdus;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, sdu_len*1.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
break;
}
}
printf("Generated %d PDUs in total\n", n_pdus);
// Write all PDUs into RLC2 except first one
for(int i=0;i<n_pdus;i++) {
if (i!=0) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
}
}
// We should have received one SDU less than we tx'ed
assert(tester.n_sdus == n_sdus - 1);
for (int i = 0; i < tester.n_sdus; ++i) {
assert(tester.sdus[i]->N_bytes == sdu_len);
}
}
int main(int argc, char **argv) {
basic_test();
byte_buffer_pool::get_instance()->cleanup();
loss_test();
byte_buffer_pool::get_instance()->cleanup();
reassmble_test();
byte_buffer_pool::get_instance()->cleanup();
reassmble_test2();
byte_buffer_pool::get_instance()->cleanup();
}

@ -366,7 +366,6 @@ void phch_recv::run_thread()
bool is_end_of_burst = false;
cf_t *dummy_buffer[SRSLTE_MAX_PORTS];
for (int i=0;i<SRSLTE_MAX_PORTS;i++) {
dummy_buffer[i] = (cf_t*) malloc(sizeof(cf_t)*SRSLTE_SF_LEN_PRB(100));
}
@ -555,6 +554,12 @@ void phch_recv::run_thread()
// Increase TTI counter
tti = (tti+1) % 10240;
}
for (int i=0;i<SRSLTE_MAX_PORTS;i++) {
if (dummy_buffer[i]) {
free(dummy_buffer[i]);
}
}
}

Loading…
Cancel
Save