From 1a46b66d7478626a441bcb970f045b7206a47469 Mon Sep 17 00:00:00 2001
From: Cedric Roux <cedric.roux@eurecom.fr>
Date: Fri, 22 Nov 2019 15:10:27 +0100
Subject: [PATCH] nr rlc: first commit

Initial implementation.

nr_rlc/nr_rlc_oai_api.c will need some clean rewrite at some point.

More testing is also needed.
---
 cmake_targets/CMakeLists.txt               |  108 +-
 executables/nr-softmodem.c                 |    7 +-
 openair2/LAYER2/MAC/main.c                 |    4 +-
 openair2/LAYER2/MAC/main_ue.c              |    2 +-
 openair2/LAYER2/NR_MAC_UE/main_ue_nr.c     |    2 +-
 openair2/LAYER2/NR_MAC_gNB/main.c          |    2 +-
 openair2/LAYER2/RLC/rlc.c                  |    3 +-
 openair2/LAYER2/RLC/rlc.h                  |    4 +-
 openair2/LAYER2/nr_rlc/Makefile            |   15 +
 openair2/LAYER2/nr_rlc/TODO                |   10 +
 openair2/LAYER2/nr_rlc/asn1_utils.c        |  129 ++
 openair2/LAYER2/nr_rlc/asn1_utils.h        |   33 +
 openair2/LAYER2/nr_rlc/nr_rlc_entity.c     |  174 ++
 openair2/LAYER2/nr_rlc/nr_rlc_entity.h     |  104 ++
 openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c  | 1844 ++++++++++++++++++++
 openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h  |  102 ++
 openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.c  |  181 ++
 openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.h  |   55 +
 openair2/LAYER2/nr_rlc/nr_rlc_entity_um.c  |  697 ++++++++
 openair2/LAYER2/nr_rlc/nr_rlc_entity_um.h  |   79 +
 openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c    |  874 ++++++++++
 openair2/LAYER2/nr_rlc/nr_rlc_pdu.c        |  190 ++
 openair2/LAYER2/nr_rlc/nr_rlc_pdu.h        |   84 +
 openair2/LAYER2/nr_rlc/nr_rlc_sdu.c        |  121 ++
 openair2/LAYER2/nr_rlc/nr_rlc_sdu.h        |   56 +
 openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.c |  190 ++
 openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h |   60 +
 openair2/LAYER2/nr_rlc/test.c              |   32 +
 openair2/LAYER2/nr_rlc/tests/LOG/log.h     |   10 +
 openair2/LAYER2/nr_rlc/tests/Makefile      |   33 +
 openair2/LAYER2/nr_rlc/tests/run_tests.sh  |   12 +
 openair2/LAYER2/nr_rlc/tests/test.c        |  483 +++++
 openair2/LAYER2/nr_rlc/tests/test1.h       |   14 +
 openair2/LAYER2/nr_rlc/tests/test1.txt.gz  |  Bin 0 -> 246 bytes
 openair2/LAYER2/nr_rlc/tests/test10.h      |   13 +
 openair2/LAYER2/nr_rlc/tests/test10.txt.gz |  Bin 0 -> 248 bytes
 openair2/LAYER2/nr_rlc/tests/test11.h      |   13 +
 openair2/LAYER2/nr_rlc/tests/test11.txt.gz |  Bin 0 -> 251 bytes
 openair2/LAYER2/nr_rlc/tests/test12.h      |   18 +
 openair2/LAYER2/nr_rlc/tests/test12.txt.gz |  Bin 0 -> 227 bytes
 openair2/LAYER2/nr_rlc/tests/test13.h      |   18 +
 openair2/LAYER2/nr_rlc/tests/test13.txt.gz |  Bin 0 -> 230 bytes
 openair2/LAYER2/nr_rlc/tests/test2.h       |   14 +
 openair2/LAYER2/nr_rlc/tests/test2.txt.gz  |  Bin 0 -> 250 bytes
 openair2/LAYER2/nr_rlc/tests/test3.h       |   14 +
 openair2/LAYER2/nr_rlc/tests/test3.txt.gz  |  Bin 0 -> 200 bytes
 openair2/LAYER2/nr_rlc/tests/test4.h       |   14 +
 openair2/LAYER2/nr_rlc/tests/test4.txt.gz  |  Bin 0 -> 200 bytes
 openair2/LAYER2/nr_rlc/tests/test5.h       |   15 +
 openair2/LAYER2/nr_rlc/tests/test5.txt.gz  |  Bin 0 -> 42 bytes
 openair2/LAYER2/nr_rlc/tests/test6.h       |   15 +
 openair2/LAYER2/nr_rlc/tests/test6.txt.gz  |  Bin 0 -> 42 bytes
 openair2/LAYER2/nr_rlc/tests/test7.h       |   14 +
 openair2/LAYER2/nr_rlc/tests/test7.txt.gz  |  Bin 0 -> 192 bytes
 openair2/LAYER2/nr_rlc/tests/test8.h       |   20 +
 openair2/LAYER2/nr_rlc/tests/test8.txt.gz  |  Bin 0 -> 330 bytes
 openair2/LAYER2/nr_rlc/tests/test9.h       |   20 +
 openair2/LAYER2/nr_rlc/tests/test9.txt.gz  |  Bin 0 -> 328 bytes
 targets/TEST/PDCP/with_rlc/test_pdcp_rlc.c |    2 +-
 59 files changed, 5824 insertions(+), 80 deletions(-)
 create mode 100644 openair2/LAYER2/nr_rlc/Makefile
 create mode 100644 openair2/LAYER2/nr_rlc/TODO
 create mode 100644 openair2/LAYER2/nr_rlc/asn1_utils.c
 create mode 100644 openair2/LAYER2/nr_rlc/asn1_utils.h
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity.h
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.h
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity_um.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_entity_um.h
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_pdu.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_pdu.h
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_sdu.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_sdu.h
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.c
 create mode 100644 openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h
 create mode 100644 openair2/LAYER2/nr_rlc/test.c
 create mode 100644 openair2/LAYER2/nr_rlc/tests/LOG/log.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/Makefile
 create mode 100755 openair2/LAYER2/nr_rlc/tests/run_tests.sh
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test.c
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test1.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test1.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test10.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test10.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test11.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test11.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test12.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test12.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test13.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test13.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test2.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test2.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test3.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test3.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test4.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test4.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test5.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test5.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test6.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test6.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test7.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test7.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test8.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test8.txt.gz
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test9.h
 create mode 100644 openair2/LAYER2/nr_rlc/tests/test9.txt.gz

diff --git a/cmake_targets/CMakeLists.txt b/cmake_targets/CMakeLists.txt
index 273aad0c20..a0620d8345 100644
--- a/cmake_targets/CMakeLists.txt
+++ b/cmake_targets/CMakeLists.txt
@@ -1473,15 +1473,8 @@ set(RRC_DIR ${OPENAIR2_DIR}/RRC/LTE)
 set(NR_RRC_DIR ${OPENAIR2_DIR}/RRC/NR)
 set(NR_UE_RRC_DIR ${OPENAIR2_DIR}/RRC/NR_UE)
 set(PDCP_DIR  ${OPENAIR2_DIR}/LAYER2/PDCP_v10.1.0)
-set(L2_SRC
-  ${OPENAIR2_DIR}/LAYER2/openair2_proc.c
-  ${PDCP_DIR}/pdcp.c
-  ${PDCP_DIR}/pdcp_fifo.c
-  ${PDCP_DIR}/pdcp_sequence_manager.c
-  ${PDCP_DIR}/pdcp_primitives.c
-  ${PDCP_DIR}/pdcp_util.c
-  ${PDCP_DIR}/pdcp_security.c
-  ${PDCP_DIR}/pdcp_netlink.c
+
+set(LTE_RLC_SRC
   ${RLC_AM_DIR}/rlc_am.c
   ${RLC_AM_DIR}/rlc_am_init.c
   ${RLC_AM_DIR}/rlc_am_timer_poll_retransmit.c
@@ -1509,6 +1502,29 @@ set(L2_SRC
   ${RLC_DIR}/rlc.c
   ${RLC_DIR}/rlc_rrc.c
   ${RLC_DIR}/rlc_mpls.c
+  )
+
+set(NR_RLC_SRC
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/asn1_utils.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_entity.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_entity_am.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_entity_tm.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_entity_um.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_oai_api.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_pdu.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_sdu.c
+  ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_ue_manager.c
+  )
+
+set(L2_SRC
+  ${OPENAIR2_DIR}/LAYER2/openair2_proc.c
+  ${PDCP_DIR}/pdcp.c
+  ${PDCP_DIR}/pdcp_fifo.c
+  ${PDCP_DIR}/pdcp_sequence_manager.c
+  ${PDCP_DIR}/pdcp_primitives.c
+  ${PDCP_DIR}/pdcp_util.c
+  ${PDCP_DIR}/pdcp_security.c
+  ${PDCP_DIR}/pdcp_netlink.c
 #  ${RRC_DIR}/rrc_UE.c
   ${RRC_DIR}/rrc_eNB.c
   ${RRC_DIR}/rrc_eNB_S1AP.c
@@ -1519,7 +1535,12 @@ set(L2_SRC
   ${RRC_DIR}/L2_interface_ue.c
   )
 
+set(L2_LTE_SRC
+  ${LTE_RLC_SRC}
+  )
+
 set(L2_NR_SRC
+  ${NR_RLC_SRC}
   ${NR_RRC_DIR}/rrc_gNB.c
   ${NR_RRC_DIR}/nr_rrc_common.c
   ${NR_RRC_DIR}/L2_nr_interface.c
@@ -1534,40 +1555,13 @@ set(L2_SRC_UE
   ${PDCP_DIR}/pdcp_util.c
   ${PDCP_DIR}/pdcp_security.c
   ${PDCP_DIR}/pdcp_netlink.c
-  ${RLC_AM_DIR}/rlc_am.c
-  ${RLC_AM_DIR}/rlc_am_init.c
-  ${RLC_AM_DIR}/rlc_am_timer_poll_retransmit.c
-  ${RLC_AM_DIR}/rlc_am_timer_reordering.c
-  ${RLC_AM_DIR}/rlc_am_timer_status_prohibit.c
-  ${RLC_AM_DIR}/rlc_am_segment.c
-  ${RLC_AM_DIR}/rlc_am_segments_holes.c
-  ${RLC_AM_DIR}/rlc_am_in_sdu.c
-  ${RLC_AM_DIR}/rlc_am_receiver.c
-  ${RLC_AM_DIR}/rlc_am_retransmit.c
-  ${RLC_AM_DIR}/rlc_am_windows.c
-  ${RLC_AM_DIR}/rlc_am_rx_list.c
-  ${RLC_AM_DIR}/rlc_am_reassembly.c
-  ${RLC_AM_DIR}/rlc_am_status_report.c
-  ${RLC_TM_DIR}/rlc_tm.c
-  ${RLC_TM_DIR}/rlc_tm_init.c
-  ${RLC_UM_DIR}/rlc_um.c
-  ${RLC_UM_DIR}/rlc_um_fsm.c
-  ${RLC_UM_DIR}/rlc_um_control_primitives.c
-  ${RLC_UM_DIR}/rlc_um_segment.c
-  ${RLC_UM_DIR}/rlc_um_reassembly.c
-  ${RLC_UM_DIR}/rlc_um_receiver.c
-  ${RLC_UM_DIR}/rlc_um_dar.c
-  ${RLC_DIR}/rlc_mac.c
-  ${RLC_DIR}/rlc.c
-  ${RLC_DIR}/rlc_rrc.c
-  ${RLC_DIR}/rlc_mpls.c
   ${RRC_DIR}/rrc_UE.c
   ${RRC_DIR}/rrc_common.c
   ${RRC_DIR}/L2_interface_common.c
   ${RRC_DIR}/L2_interface_ue.c
   )
   
-  set(LTE_NR_L2_SRC_UE
+set(LTE_NR_L2_SRC_UE
   ${PDCP_DIR}/pdcp.c
   ${PDCP_DIR}/pdcp_fifo.c
   ${PDCP_DIR}/pdcp_sequence_manager.c
@@ -1575,37 +1569,11 @@ set(L2_SRC_UE
   ${PDCP_DIR}/pdcp_util.c
   ${PDCP_DIR}/pdcp_security.c
   ${PDCP_DIR}/pdcp_netlink.c
-  ${RLC_AM_DIR}/rlc_am.c
-  ${RLC_AM_DIR}/rlc_am_init.c
-  ${RLC_AM_DIR}/rlc_am_timer_poll_retransmit.c
-  ${RLC_AM_DIR}/rlc_am_timer_reordering.c
-  ${RLC_AM_DIR}/rlc_am_timer_status_prohibit.c
-  ${RLC_AM_DIR}/rlc_am_segment.c
-  ${RLC_AM_DIR}/rlc_am_segments_holes.c
-  ${RLC_AM_DIR}/rlc_am_in_sdu.c
-  ${RLC_AM_DIR}/rlc_am_receiver.c
-  ${RLC_AM_DIR}/rlc_am_retransmit.c
-  ${RLC_AM_DIR}/rlc_am_windows.c
-  ${RLC_AM_DIR}/rlc_am_rx_list.c
-  ${RLC_AM_DIR}/rlc_am_reassembly.c
-  ${RLC_AM_DIR}/rlc_am_status_report.c
-  ${RLC_TM_DIR}/rlc_tm.c
-  ${RLC_TM_DIR}/rlc_tm_init.c
-  ${RLC_UM_DIR}/rlc_um.c
-  ${RLC_UM_DIR}/rlc_um_fsm.c
-  ${RLC_UM_DIR}/rlc_um_control_primitives.c
-  ${RLC_UM_DIR}/rlc_um_segment.c
-  ${RLC_UM_DIR}/rlc_um_reassembly.c
-  ${RLC_UM_DIR}/rlc_um_receiver.c
-  ${RLC_UM_DIR}/rlc_um_dar.c
-  ${RLC_DIR}/rlc_mac.c
-  ${RLC_DIR}/rlc.c
-  ${RLC_DIR}/rlc_rrc.c
-  ${RLC_DIR}/rlc_mpls.c
-  ${RRC_DIR}/L2_interface_common.c  
+  ${LTE_RLC_SRC}
   )
 
 set(NR_L2_SRC_UE
+  ${NR_RLC_SRC}
   ${NR_UE_RRC_DIR}/L2_interface_ue.c
   ${NR_UE_RRC_DIR}/main_ue.c
   ${NR_UE_RRC_DIR}/rrc_UE.c
@@ -1693,6 +1661,10 @@ add_library(MAC_UE_NR
   ${MAC_NR_SRC_UE}
   )
 
+add_library(L2_LTE
+  ${L2_LTE_SRC}
+  )
+
 add_library(L2_NR
   ${L2_NR_SRC}
   ${MAC_NR_SRC}
@@ -2345,7 +2317,7 @@ add_dependencies(lte-softmodem rrc_flag s1ap_flag x2_flag)
 target_link_libraries (lte-softmodem
   -Wl,--start-group
   RRC_LIB S1AP_LIB S1AP_ENB F1AP_LIB F1AP X2AP_LIB X2AP_ENB GTPV1U SECU_CN SECU_OSA UTIL HASHTABLE SCTP_CLIENT UDP SCHED_LIB SCHED_RU_LIB 
-  PHY_COMMON PHY PHY_RU LFDS L2 NFAPI_COMMON_LIB NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB LFDS7
+  PHY_COMMON PHY PHY_RU LFDS L2 L2_LTE NFAPI_COMMON_LIB NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB LFDS7
   ${MSC_LIB} ${RAL_LIB} ${NAS_UE_LIB} ${ITTI_LIB} ${FLPT_MSG_LIB} ${ASYNC_IF_LIB} ${FLEXRAN_AGENT_LIB} ${FSPT_MSG_LIB} ${PROTO_AGENT_LIB}
   -Wl,--end-group z dl)
   
@@ -2417,7 +2389,7 @@ endif()
 target_link_libraries (lte-uesoftmodem
   -Wl,--start-group
   RRC_LIB S1AP_LIB S1AP_ENB X2AP_LIB X2AP_ENB F1AP F1AP_LIB GTPV1U SECU_CN SECU_OSA UTIL HASHTABLE SCTP_CLIENT UDP SCHED_RU_LIB SCHED_UE_LIB PHY_COMMON 
-  PHY_UE PHY_RU LFDS L2_UE SIMU ${MSC_LIB} ${RAL_LIB} ${NAS_UE_LIB} ${ITTI_LIB} ${FLPT_MSG_LIB} ${ASYNC_IF_LIB} LFDS7 ${ATLAS_LIBRARIES}
+  PHY_UE PHY_RU LFDS L2_UE L2_LTE SIMU ${MSC_LIB} ${RAL_LIB} ${NAS_UE_LIB} ${ITTI_LIB} ${FLPT_MSG_LIB} ${ASYNC_IF_LIB} LFDS7 ${ATLAS_LIBRARIES}
   NFAPI_COMMON_LIB NFAPI_LIB NFAPI_PNF_LIB NFAPI_USER_LIB
   -Wl,--end-group z dl)
 
@@ -2745,7 +2717,7 @@ if (${T_TRACER})
         SECU_OSA SECU_CN SCHED_LIB SCHED_NR_LIB SCHED_RU_LIB SCHED_UE_LIB
         SCHED_NR_UE_LIB NFAPI_COMMON_LIB NFAPI_LIB NFAPI_PNF_LIB NFAPI_VNF_LIB
         NFAPI_USER_LIB PHY_COMMON PHY PHY_UE PHY_NR PHY_NR_UE PHY_RU PHY_MEX
-        L2 L2_NR L2_UE NR_L2_UE MAC_NR_COMMON MAC_NR MAC_UE_NR CN_UTILS GTPV1U
+        L2 L2_LTE L2_NR L2_UE NR_L2_UE MAC_NR_COMMON MAC_NR MAC_UE_NR CN_UTILS GTPV1U
         SCTP_CLIENT UDP LIB_NAS_UE NB_IoT LFDS LFDS7 SIMU SIMU_ETH OPENAIR0_LIB)
     if (TARGET ${i})
       add_dependencies(${i} generate_T)
diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c
index 931e2c1cd4..0a638f7265 100644
--- a/executables/nr-softmodem.c
+++ b/executables/nr-softmodem.c
@@ -924,6 +924,10 @@ init_opt();
 #  define PACKAGE_VERSION "UNKNOWN-EXPERIMENTAL"
 #endif
   LOG_I(HW, "Version: %s\n", PACKAGE_VERSION);
+
+  if(IS_SOFTMODEM_NOS1)
+	  init_pdcp();
+
 #if defined(ENABLE_ITTI)
 
   if (RC.nb_nr_inst > 0)  {
@@ -942,9 +946,6 @@ init_opt();
     flexran_agent_start(i);
   }
 
-  if(IS_SOFTMODEM_NOS1)
-	  init_pdcp();
-
   // init UE_PF_PO and mutex lock
   pthread_mutex_init(&ue_pf_po_mutex, NULL);
   memset (&UE_PF_PO[0][0], 0, sizeof(UE_PF_PO_t)*NUMBER_OF_UE_MAX*MAX_NUM_CCs);
diff --git a/openair2/LAYER2/MAC/main.c b/openair2/LAYER2/MAC/main.c
index 8fb8d44e0a..4e139dcf92 100644
--- a/openair2/LAYER2/MAC/main.c
+++ b/openair2/LAYER2/MAC/main.c
@@ -146,7 +146,7 @@ void mac_top_init_eNB(void)
 
   RC.mac = mac;
 
-  AssertFatal(rlc_module_init() == 0,
+  AssertFatal(rlc_module_init(1) == 0,
       "Could not initialize RLC layer\n");
 
   // These should be out of here later
@@ -184,7 +184,7 @@ int rlcmac_init_global_param(void)
 
     LOG_I(MAC, "[MAIN] CALLING RLC_MODULE_INIT...\n");
 
-    if (rlc_module_init() != 0) {
+    if (rlc_module_init(1) != 0) {
 	return (-1);
     }
 
diff --git a/openair2/LAYER2/MAC/main_ue.c b/openair2/LAYER2/MAC/main_ue.c
index f482806e1d..e8b0593717 100644
--- a/openair2/LAYER2/MAC/main_ue.c
+++ b/openair2/LAYER2/MAC/main_ue.c
@@ -110,7 +110,7 @@ mac_top_init_ue(int eMBMS_active, char *uecap_xer,
 int rlcmac_init_global_param_ue(void) {
   LOG_I(MAC, "[MAIN] CALLING RLC_MODULE_INIT...\n");
 
-  if (rlc_module_init() != 0) {
+  if (rlc_module_init(0) != 0) {
     return (-1);
   }
 
diff --git a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c
index c96acccf9b..0478a6b7d5 100644
--- a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c
+++ b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c
@@ -47,7 +47,7 @@ nr_l2_init_ue(void)
     nr_ue_mac_inst = (NR_UE_MAC_INST_t *)malloc(sizeof(NR_UE_MAC_INST_t)*NB_NR_UE_MAC_INST);
     
     if (IS_SOFTMODEM_NOS1){
-    	if (rlc_module_init() != 0) {
+        if (rlc_module_init(0) != 0) {
     		LOG_I(RLC, "Problem at RLC initiation \n");
     	}
     	pdcp_layer_init();
diff --git a/openair2/LAYER2/NR_MAC_gNB/main.c b/openair2/LAYER2/NR_MAC_gNB/main.c
index 88f6c38758..c5dad59d98 100644
--- a/openair2/LAYER2/NR_MAC_gNB/main.c
+++ b/openair2/LAYER2/NR_MAC_gNB/main.c
@@ -128,7 +128,7 @@ void mac_top_init_gNB(void)
 
     }//END for (i = 0; i < RC.nb_nr_macrlc_inst; i++)
 
-  AssertFatal(rlc_module_init() == 0,"Could not initialize RLC layer\n");
+  AssertFatal(rlc_module_init(1) == 0,"Could not initialize RLC layer\n");
 
   // These should be out of here later
   pdcp_layer_init();
diff --git a/openair2/LAYER2/RLC/rlc.c b/openair2/LAYER2/RLC/rlc.c
index 1563a01de3..bf51faef75 100644
--- a/openair2/LAYER2/RLC/rlc.c
+++ b/openair2/LAYER2/RLC/rlc.c
@@ -601,7 +601,8 @@ void rlc_data_conf     (const protocol_ctxt_t *const ctxt_pP,
 }
 //-----------------------------------------------------------------------------
 int
-rlc_module_init (void) {
+rlc_module_init (int enb_flag) { /* enb_flag is unused, but needed for binary
+                                  * compatibility with rlc_v2 */
   //-----------------------------------------------------------------------------
   int          k;
   module_id_t  module_id1;
diff --git a/openair2/LAYER2/RLC/rlc.h b/openair2/LAYER2/RLC/rlc.h
index dddffed241..ca3aaf1d90 100644
--- a/openair2/LAYER2/RLC/rlc.h
+++ b/openair2/LAYER2/RLC/rlc.h
@@ -620,10 +620,10 @@ rlc_op_status_t rlc_stat_req     (
   unsigned int *const stat_timer_poll_retransmit_timed_out,
   unsigned int *const stat_timer_status_prohibit_timed_out);
 
-/*! \fn int rlc_module_init(void)
+/*! \fn int rlc_module_init(int enb_flag)
 * \brief    RAZ the memory of the RLC layer, initialize the memory pool manager (mem_block_t structures mainly used in RLC module).
 */
-int rlc_module_init(void);
+int rlc_module_init(int enb_flag);
 
 /** @} */
 
diff --git a/openair2/LAYER2/nr_rlc/Makefile b/openair2/LAYER2/nr_rlc/Makefile
new file mode 100644
index 0000000000..071930c4f4
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/Makefile
@@ -0,0 +1,15 @@
+CC=gcc
+CFLAGS=-Wall -g -Itests
+
+OBJS=nr_rlc_entity.o nr_rlc_entity_am.o nr_rlc_entity_um.o \
+     nr_rlc_entity_tm.o nr_rlc_sdu.o nr_rlc_pdu.o test.o
+PROG=test
+
+$(PROG): $(OBJS)
+	$(CC) $(CFLAGS) -o $@ $^
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c -o $@ $<
+
+clean:
+	rm -f test *.o
diff --git a/openair2/LAYER2/nr_rlc/TODO b/openair2/LAYER2/nr_rlc/TODO
new file mode 100644
index 0000000000..4e2d3147c9
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/TODO
@@ -0,0 +1,10 @@
+RLC AM:
+
+    - status reporting (38.322 5.3.4): implement this (not done because not
+      clearly understood):
+
+        "delay triggering the STATUS report until x < RX_Highest_Status
+         or x >= RX_Next + AM_Window_Size."
+
+    - send indication of successful delivery as soon as possible. Today we
+      signal successful delivery in order. Probably not a big issue though.
diff --git a/openair2/LAYER2/nr_rlc/asn1_utils.c b/openair2/LAYER2/nr_rlc/asn1_utils.c
new file mode 100644
index 0000000000..46f7d90da5
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/asn1_utils.c
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "rlc.h"
+
+int decode_t_reordering(int v)
+{
+  static int tab[32] = {
+    0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85,
+    90, 95, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 1600
+  };
+
+  if (v < 0 || v > 31) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  return tab[v];
+}
+
+int decode_t_status_prohibit(int v)
+{
+  static int tab[62] = {
+    0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90,
+    95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150, 155, 160, 165,
+    170, 175, 180, 185, 190, 195, 200, 205, 210, 215, 220, 225, 230, 235, 240,
+    245, 250, 300, 350, 400, 450, 500, 800, 1000, 1200, 1600, 2000, 2400
+  };
+
+  if (v < 0 || v > 61) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  return tab[v];
+}
+
+int decode_t_poll_retransmit(int v)
+{
+  static int tab[59] = {
+    5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95,
+    100, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150, 155, 160, 165, 170,
+    175, 180, 185, 190, 195, 200, 205, 210, 215, 220, 225, 230, 235, 240, 245,
+    250, 300, 350, 400, 450, 500, 800, 1000, 2000, 4000
+  };
+
+  if (v < 0 || v > 58) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  return tab[v];
+}
+
+int decode_poll_pdu(int v)
+{
+  static int tab[8] = {
+    4, 8, 16, 32, 64, 128, 256, -1 /* -1 means infinity */
+  };
+
+  if (v < 0 || v > 7) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  return tab[v];
+}
+
+int decode_poll_byte(int v)
+{
+  static int tab[15] = {
+    25, 50, 75, 100, 125, 250, 375, 500, 750, 1000, 1250, 1500, 2000, 3000,
+    -1 /* -1 means infinity */
+  };
+
+  if (v < 0 || v > 14) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  if (tab[v] == -1) return -1;
+  return tab[v] * 1024;
+}
+
+int decode_max_retx_threshold(int v)
+{
+  static int tab[8] = {
+    1, 2, 3, 4, 6, 8, 16, 32
+  };
+
+  if (v < 0 || v > 7) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  return tab[v];
+}
+
+int decode_sn_field_length(int v)
+{
+  static int tab[2] = {
+    5, 10
+  };
+
+  if (v < 0 || v > 1) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  return tab[v];
+}
diff --git a/openair2/LAYER2/nr_rlc/asn1_utils.h b/openair2/LAYER2/nr_rlc/asn1_utils.h
new file mode 100644
index 0000000000..61394c9c69
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/asn1_utils.h
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _ASN1_UTILS_H_
+#define _ASN1_UTILS_H_
+
+int decode_t_reordering(int v);
+int decode_t_status_prohibit(int v);
+int decode_t_poll_retransmit(int v);
+int decode_poll_pdu(int v);
+int decode_poll_byte(int v);
+int decode_max_retx_threshold(int v);
+int decode_sn_field_length(int v);
+
+#endif /* _ASN1_UTILS_H_ */
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity.c b/openair2/LAYER2/nr_rlc/nr_rlc_entity.c
new file mode 100644
index 0000000000..fefe2fc77d
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity.c
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "nr_rlc_entity.h"
+
+#include <stdlib.h>
+
+#include "nr_rlc_entity_am.h"
+#include "nr_rlc_entity_um.h"
+#include "nr_rlc_entity_tm.h"
+
+#include "LOG/log.h"
+
+nr_rlc_entity_t *new_nr_rlc_entity_am(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct nr_rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    void (*sdu_successful_delivery)(void *sdu_successful_delivery_data,
+                                    struct nr_rlc_entity_t *entity,
+                                    int sdu_id),
+    void *sdu_successful_delivery_data,
+    void (*max_retx_reached)(void *max_retx_reached_data,
+                             struct nr_rlc_entity_t *entity),
+    void *max_retx_reached_data,
+    int t_poll_retransmit,
+    int t_reassembly,
+    int t_status_prohibit,
+    int poll_pdu,
+    int poll_byte,
+    int max_retx_threshold,
+    int sn_field_length)
+{
+  nr_rlc_entity_am_t *ret;
+
+  ret = calloc(1, sizeof(nr_rlc_entity_am_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  ret->tx_maxsize = tx_maxsize;
+  ret->rx_maxsize = rx_maxsize;
+
+  ret->t_poll_retransmit  = t_poll_retransmit;
+  ret->t_reassembly       = t_reassembly;
+  ret->t_status_prohibit  = t_status_prohibit;
+  ret->poll_pdu           = poll_pdu;
+  ret->poll_byte          = poll_byte;
+  ret->max_retx_threshold = max_retx_threshold;
+  ret->sn_field_length    = sn_field_length;
+
+  if (!(sn_field_length == 12 || sn_field_length == 18)) {
+    LOG_E(RLC, "%s:%d:%s: wrong SN field_lenght (%d), must be 12 or 18\n",
+          __FILE__, __LINE__, __FUNCTION__, sn_field_length);
+    exit(1);
+  }
+  ret->sn_modulus = 1 << ret->sn_field_length;
+  ret->window_size = ret->sn_modulus / 2;
+
+  ret->common.recv_pdu        = nr_rlc_entity_am_recv_pdu;
+  ret->common.buffer_status   = nr_rlc_entity_am_buffer_status;
+  ret->common.generate_pdu    = nr_rlc_entity_am_generate_pdu;
+  ret->common.recv_sdu        = nr_rlc_entity_am_recv_sdu;
+  ret->common.set_time        = nr_rlc_entity_am_set_time;
+  ret->common.discard_sdu     = nr_rlc_entity_am_discard_sdu;
+  ret->common.reestablishment = nr_rlc_entity_am_reestablishment;
+  ret->common.delete          = nr_rlc_entity_am_delete;
+
+  ret->common.deliver_sdu                  = deliver_sdu;
+  ret->common.deliver_sdu_data             = deliver_sdu_data;
+  ret->common.sdu_successful_delivery      = sdu_successful_delivery;
+  ret->common.sdu_successful_delivery_data = sdu_successful_delivery_data;
+  ret->common.max_retx_reached             = max_retx_reached;
+  ret->common.max_retx_reached_data        = max_retx_reached_data;
+
+  return (nr_rlc_entity_t *)ret;
+}
+
+nr_rlc_entity_t *new_nr_rlc_entity_um(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct nr_rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    int t_reassembly,
+    int sn_field_length)
+{
+  nr_rlc_entity_um_t *ret;
+
+  ret = calloc(1, sizeof(nr_rlc_entity_um_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  ret->tx_maxsize = tx_maxsize;
+  ret->rx_maxsize = rx_maxsize;
+
+  ret->t_reassembly    = t_reassembly;
+  ret->sn_field_length = sn_field_length;
+
+  if (!(sn_field_length == 6 || sn_field_length == 12)) {
+    LOG_E(RLC, "%s:%d:%s: wrong SN field_lenght (%d), must be 6 or 12\n",
+          __FILE__, __LINE__, __FUNCTION__, sn_field_length);
+    exit(1);
+  }
+  ret->sn_modulus = 1 << ret->sn_field_length;
+  ret->window_size = ret->sn_modulus / 2;
+
+  ret->common.recv_pdu        = nr_rlc_entity_um_recv_pdu;
+  ret->common.buffer_status   = nr_rlc_entity_um_buffer_status;
+  ret->common.generate_pdu    = nr_rlc_entity_um_generate_pdu;
+  ret->common.recv_sdu        = nr_rlc_entity_um_recv_sdu;
+  ret->common.set_time        = nr_rlc_entity_um_set_time;
+  ret->common.discard_sdu     = nr_rlc_entity_um_discard_sdu;
+  ret->common.reestablishment = nr_rlc_entity_um_reestablishment;
+  ret->common.delete          = nr_rlc_entity_um_delete;
+
+  ret->common.deliver_sdu                  = deliver_sdu;
+  ret->common.deliver_sdu_data             = deliver_sdu_data;
+
+  return (nr_rlc_entity_t *)ret;
+}
+
+nr_rlc_entity_t *new_nr_rlc_entity_tm(
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct nr_rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data)
+{
+  nr_rlc_entity_tm_t *ret;
+
+  ret = calloc(1, sizeof(nr_rlc_entity_tm_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  ret->tx_maxsize = tx_maxsize;
+
+  ret->common.recv_pdu        = nr_rlc_entity_tm_recv_pdu;
+  ret->common.buffer_status   = nr_rlc_entity_tm_buffer_status;
+  ret->common.generate_pdu    = nr_rlc_entity_tm_generate_pdu;
+  ret->common.recv_sdu        = nr_rlc_entity_tm_recv_sdu;
+  ret->common.set_time        = nr_rlc_entity_tm_set_time;
+  ret->common.discard_sdu     = nr_rlc_entity_tm_discard_sdu;
+  ret->common.reestablishment = nr_rlc_entity_tm_reestablishment;
+  ret->common.delete          = nr_rlc_entity_tm_delete;
+
+  ret->common.deliver_sdu                  = deliver_sdu;
+  ret->common.deliver_sdu_data             = deliver_sdu_data;
+
+  return (nr_rlc_entity_t *)ret;
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity.h b/openair2/LAYER2/nr_rlc/nr_rlc_entity.h
new file mode 100644
index 0000000000..5b12a90cfd
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity.h
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _NR_RLC_ENTITY_H_
+#define _NR_RLC_ENTITY_H_
+
+#include <stdint.h>
+
+#define NR_SDU_MAX 16000   /* max NR PDCP SDU size is 9000, let's take more */
+
+typedef struct {
+  int status_size;
+  int tx_size;
+  int retx_size;
+} nr_rlc_entity_buffer_status_t;
+
+typedef struct nr_rlc_entity_t {
+  /* functions provided by the RLC module */
+  void (*recv_pdu)(struct nr_rlc_entity_t *entity, char *buffer, int size);
+  nr_rlc_entity_buffer_status_t (*buffer_status)(
+      struct nr_rlc_entity_t *entity, int maxsize);
+  int (*generate_pdu)(struct nr_rlc_entity_t *entity, char *buffer, int size);
+
+  void (*recv_sdu)(struct nr_rlc_entity_t *entity, char *buffer, int size,
+                   int sdu_id);
+
+  void (*set_time)(struct nr_rlc_entity_t *entity, uint64_t now);
+
+  void (*discard_sdu)(struct nr_rlc_entity_t *entity, int sdu_id);
+
+  void (*reestablishment)(struct nr_rlc_entity_t *entity);
+
+  void (*delete)(struct nr_rlc_entity_t *entity);
+
+  /* callbacks provided to the RLC module */
+  void (*deliver_sdu)(void *deliver_sdu_data, struct nr_rlc_entity_t *entity,
+                      char *buf, int size);
+  void *deliver_sdu_data;
+
+  void (*sdu_successful_delivery)(void *sdu_successful_delivery_data,
+                                  struct nr_rlc_entity_t *entity,
+                                  int sdu_id);
+  void *sdu_successful_delivery_data;
+
+  void (*max_retx_reached)(void *max_retx_reached_data,
+                           struct nr_rlc_entity_t *entity);
+  void *max_retx_reached_data;
+} nr_rlc_entity_t;
+
+nr_rlc_entity_t *new_nr_rlc_entity_am(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct nr_rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    void (*sdu_successful_delivery)(void *sdu_successful_delivery_data,
+                                    struct nr_rlc_entity_t *entity,
+                                    int sdu_id),
+    void *sdu_successful_delivery_data,
+    void (*max_retx_reached)(void *max_retx_reached_data,
+                             struct nr_rlc_entity_t *entity),
+    void *max_retx_reached_data,
+    int t_poll_retransmit,
+    int t_reassembly,
+    int t_status_prohibit,
+    int poll_pdu,
+    int poll_byte,
+    int max_retx_threshold,
+    int sn_field_length);
+
+nr_rlc_entity_t *new_nr_rlc_entity_um(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct nr_rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    int t_reassembly,
+    int sn_field_length);
+
+nr_rlc_entity_t *new_nr_rlc_entity_tm(
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct nr_rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data);
+
+#endif /* _NR_RLC_ENTITY_H_ */
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c b/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c
new file mode 100644
index 0000000000..8f3d49d8c2
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c
@@ -0,0 +1,1844 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "nr_rlc_entity_am.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "nr_rlc_pdu.h"
+
+#include "LOG/log.h"
+
+/*************************************************************************/
+/* PDU RX functions                                                      */
+/*************************************************************************/
+
+static int modulus_rx(nr_rlc_entity_am_t *entity, int a)
+{
+  /* as per 38.322 7.1, modulus base is rx_next */
+  int r = a - entity->rx_next;
+  if (r < 0) r += entity->sn_modulus;
+  return r;
+}
+
+static int modulus_tx(nr_rlc_entity_am_t *entity, int a)
+{
+  int r = a - entity->tx_next_ack;
+  if (r < 0) r += entity->sn_modulus;
+  return r;
+}
+
+static int sn_in_recv_window(void *_entity, int sn)
+{
+  nr_rlc_entity_am_t *entity = _entity;
+  int mod_sn = modulus_rx(entity, sn);
+  /* we simplify rx_next <= sn < rx_next + am_window_size */
+  return mod_sn < entity->window_size;
+}
+
+static int sn_compare_rx(void *_entity, int a, int b)
+{
+  nr_rlc_entity_am_t *entity = _entity;
+  return modulus_rx(entity, a) - modulus_rx(entity, b);
+}
+
+static int sn_compare_tx(void *_entity, int a, int b)
+{
+  nr_rlc_entity_am_t *entity = _entity;
+  return modulus_tx(entity, a) - modulus_tx(entity, b);
+}
+
+static int segment_already_received(nr_rlc_entity_am_t *entity,
+    int sn, int so, int size)
+{
+  nr_rlc_pdu_t *l = entity->rx_list;
+  int covered;
+
+  while (l != NULL && size > 0) {
+    if (l->sn == sn) {
+      if (l->so <= so && so < l->so + l->size) {
+        covered = l->size - (so - l->so);
+        size -= covered;
+        so += covered;
+      } else if (l->so <= so+size-1 && so+size-1 < l->so + l->size) {
+        covered = size - (l->so - so);
+        size -= covered;
+      }
+    }
+    l = l->next;
+  }
+
+  return size <= 0;
+}
+
+static void consider_retransmission(nr_rlc_entity_am_t *entity,
+    nr_rlc_sdu_segment_t *cur, int update_retx)
+{
+  if (update_retx)
+    cur->sdu->retx_count++;
+
+  /* let's report max RETX reached for all retx_count >= max_retx_threshold
+   * (specs say to report if retx_count == max_retx_threshold).
+   * Upper layers should react (radio link failure), so no big deal actually.
+   */
+  if (update_retx && cur->sdu->retx_count >= entity->max_retx_threshold) {
+    entity->common.max_retx_reached(entity->common.max_retx_reached_data,
+                                    (nr_rlc_entity_t *)entity);
+  }
+
+  /* let's put in retransmit list even if we are over max_retx_threshold.
+   * upper layers should deal with this condition, internally it's better
+   * for the RLC code to keep going with this segment (we only remove
+   * a segment that was ACKed)
+   */
+  nr_rlc_sdu_segment_list_append(&entity->retransmit_list,
+                                 &entity->retransmit_end,
+                                 cur);
+}
+
+/* checks that all the bytes of the SDU sn have been received (but SDU
+ * has not been already processed)
+ */
+static int sdu_full(nr_rlc_entity_am_t *entity, int sn)
+{
+  nr_rlc_pdu_t *l = entity->rx_list;
+  int last_byte;
+  int new_last_byte;
+
+  last_byte = -1;
+  while (l != NULL) {
+    if (l->sn == sn)
+      break;
+    l = l->next;
+  }
+
+  /* check if the data has already been processed */
+  if (l != NULL && l->data == NULL)
+    return 0;
+
+  while (l != NULL && l->sn == sn) {
+    if (l->so > last_byte + 1)
+      return 0;
+    if (l->is_last)
+      return 1;
+    new_last_byte = l->so + l->size - 1;
+    if (new_last_byte > last_byte)
+      last_byte = new_last_byte;
+    l = l->next;
+  }
+
+  return 0;
+}
+
+/* checks that an SDU has already been delivered */
+static int sdu_delivered(nr_rlc_entity_am_t *entity, int sn)
+{
+  nr_rlc_pdu_t *l = entity->rx_list;
+
+  while (l != NULL) {
+    if (l->sn == sn)
+      break;
+    l = l->next;
+  }
+
+  return l != NULL && l->data == NULL;
+}
+
+/* check if there is some missing bytes before the last received of SDU sn */
+/* todo: be sure that when no byte was received or the SDU has already been
+ *       processed then the SDU has no missing byte
+ */
+static int sdu_has_missing_bytes(nr_rlc_entity_am_t *entity, int sn)
+{
+  nr_rlc_pdu_t *l = entity->rx_list;
+  int last_byte;
+  int new_last_byte;
+
+  last_byte = -1;
+  while (l != NULL) {
+    if (l->sn == sn)
+      break;
+    l = l->next;
+  }
+
+  /* check if the data has already been processed */
+  if (l != NULL && l->data == NULL)
+    return 0;                    /* data already processed: no missing byte */
+
+  while (l != NULL && l->sn == sn) {
+    if (l->so > last_byte + 1)
+      return 1;
+    new_last_byte = l->so + l->size - 1;
+    if (new_last_byte > last_byte)
+      last_byte = new_last_byte;
+    l = l->next;
+  }
+
+  return 0;
+}
+
+static void reassemble_and_deliver(nr_rlc_entity_am_t *entity, int sn)
+{
+  nr_rlc_pdu_t *pdu;
+  char sdu[NR_SDU_MAX];
+  int so = 0;
+  int bad_sdu = 0;
+
+  /* go to first segment of sn */
+  pdu = entity->rx_list;
+  while (pdu->sn != sn)
+    pdu = pdu->next;
+
+  /* reassemble - free 'data' of each segment after processing */
+  while (pdu != NULL && pdu->sn == sn) {
+    int len = pdu->size - (so - pdu->so);
+    if (so + len > NR_SDU_MAX && !bad_sdu) {
+      LOG_E(RLC, "%s:%d:%s: bad SDU, too big, discarding\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      bad_sdu = 1;
+    }
+    if (!bad_sdu && len > 0) {
+      memcpy(sdu + so, pdu->data, len);
+      so += len;
+    }
+    free(pdu->data);
+    pdu->data = NULL;
+    entity->rx_size -= pdu->size;
+    pdu = pdu->next;
+  }
+
+  if (bad_sdu)
+    return;
+
+  /* deliver */
+  entity->common.deliver_sdu(entity->common.deliver_sdu_data,
+                             (nr_rlc_entity_t *)entity,
+                             sdu, so);
+}
+
+static void reception_actions(nr_rlc_entity_am_t *entity, nr_rlc_pdu_t *pdu)
+{
+  int x = pdu->sn;
+
+  if (sn_compare_rx(entity, x, entity->rx_next_highest) >= 0)
+    entity->rx_next_highest = (x + 1) % entity->sn_modulus;
+
+  /* todo: room for optimization: we can run through rx_list only once */
+  if (sdu_full(entity, x)) {
+    reassemble_and_deliver(entity, x);
+
+    if (x == entity->rx_highest_status) {
+      int rx_highest_status = entity->rx_highest_status;
+      while (sdu_delivered(entity, rx_highest_status))
+        rx_highest_status = (rx_highest_status + 1) % entity->sn_modulus;
+      entity->rx_highest_status = rx_highest_status;
+    }
+
+    if (x == entity->rx_next) {
+      /* update rx_next and free all delivered SDUs at the head of the
+       * rx_list
+       */
+      int rx_next = entity->rx_next;
+      while (entity->rx_list != NULL && entity->rx_list->data == NULL &&
+             entity->rx_list->sn == rx_next) {
+        /* free all segments of this SDU */
+        do {
+          nr_rlc_pdu_t *p = entity->rx_list;
+          entity->rx_list = p->next;
+          free(p);
+        } while (entity->rx_list != NULL &&
+                 entity->rx_list->sn == rx_next);
+        rx_next = (rx_next + 1) % entity->sn_modulus;
+      }
+      entity->rx_next = rx_next;
+    }
+  }
+
+  if (entity->t_reassembly_start) {
+    if (entity->rx_next_status_trigger == entity->rx_next ||
+        (entity->rx_next_status_trigger == (entity->rx_next + 1)
+                                             % entity->sn_modulus &&
+         !sdu_has_missing_bytes(entity, entity->rx_next)) ||
+        (!sn_in_recv_window(entity, entity->rx_next_status_trigger) &&
+         entity->rx_next_status_trigger !=
+           (entity->rx_next + entity->window_size) % entity->sn_modulus)) {
+      entity->t_reassembly_start = 0;
+    }
+  }
+
+  if (entity->t_reassembly_start == 0) {
+    if (sn_compare_rx(entity, entity->rx_next_highest,
+                      (entity->rx_next + 1) % entity->sn_modulus) > 0 ||
+        (entity->rx_next_highest == (entity->rx_next + 1)
+                                      % entity->sn_modulus &&
+         sdu_has_missing_bytes(entity, entity->rx_next))) {
+      entity->t_reassembly_start = entity->t_current;
+      entity->rx_next_status_trigger = entity->rx_next_highest;
+    }
+  }
+}
+
+static void process_received_ack(nr_rlc_entity_am_t *entity, int ack_sn)
+{
+  nr_rlc_sdu_segment_t head;
+  nr_rlc_sdu_segment_t *cur;
+  nr_rlc_sdu_segment_t *prev;
+  unsigned char sn_set[32768];  /* used to dec retx_count only once per sdu */
+
+  memset(sn_set, 0, 32768);
+
+#define IS_SN_SET(b) (sn_set[(b)/8] & (1 << ((b) % 8)))
+#define SET_SN(b) do { sn_set[(b)/8] |= (1 << ((b) % 8)); } while (0)
+
+  /* put SDUs from wait and retransmit lists with SN < 'ack_sn' to ack_list */
+
+  /* process wait list */
+  head.next = entity->wait_list;
+  prev = &head;
+  cur = entity->wait_list;
+  while (cur != NULL) {
+    if (sn_compare_tx(entity, cur->sdu->sn, ack_sn) < 0) {
+      /* remove from wait list */
+      prev->next = cur->next;
+      /* put the PDU in the ack list */
+      entity->ack_list = nr_rlc_sdu_segment_list_add(sn_compare_tx, entity,
+                                                     entity->ack_list, cur);
+      entity->wait_end = prev;
+      cur = prev->next;
+    } else {
+      entity->wait_end = cur;
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->wait_list = head.next;
+  if (entity->wait_list == NULL)
+    entity->wait_end = NULL;
+
+  /* process retransmit list */
+  head.next = entity->retransmit_list;
+  prev = &head;
+  cur = entity->retransmit_list;
+  while (cur != NULL) {
+    if (sn_compare_tx(entity, cur->sdu->sn, ack_sn) < 0) {
+      /* dec. retx_count in case we put this segment back in retransmit list
+       * in 'process_received_nack'
+       * do it only once per SDU
+       */
+      if (!IS_SN_SET(cur->sdu->sn)) {
+        cur->sdu->retx_count--;
+        SET_SN(cur->sdu->sn);
+      }
+      /* remove from retransmit list */
+      prev->next = cur->next;
+      /* put the PDU in the ack list */
+      entity->ack_list = nr_rlc_sdu_segment_list_add(sn_compare_tx, entity,
+                                                     entity->ack_list, cur);
+      entity->retransmit_end = prev;
+      cur = prev->next;
+    } else {
+      entity->retransmit_end = cur;
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->retransmit_list = head.next;
+  if (entity->retransmit_list == NULL)
+    entity->retransmit_end = NULL;
+
+#undef IS_BIT_SET
+#undef SET_BIT
+}
+
+static int so_overlap(int s1, int e1, int s2, int e2)
+{
+  if (s1 < s2) {
+    if (e1 == -1 || e1 >= s2)
+      return 1;
+    return 0;
+  }
+  if (e2 == -1 || s1 <= e2)
+    return 1;
+  return 0;
+}
+
+static void process_nack_sn(nr_rlc_entity_am_t *entity, int nack_sn,
+                            int so_start, int so_end, unsigned char *sn_set)
+{
+  /* put all SDU segments with SN == 'sn' and with an overlapping so start/end
+   * to the retransmit list
+   * source lists are ack list and wait list.
+   * Not sure if we should consider wait list, isn't the other end supposed
+   * to only NACK SNs lower than the ACK SN sent in the status PDU, in which
+   * case all potential SDU segments should all be in ack list when calling
+   * the current function? in doubt let's accept anything and thus process
+   * also wait list.
+   */
+  nr_rlc_sdu_segment_t head;
+  nr_rlc_sdu_segment_t *cur;
+  nr_rlc_sdu_segment_t *prev;
+
+#define IS_SN_SET(b) (sn_set[(b)/8] & (1 << ((b) % 8)))
+#define SET_SN(b) do { sn_set[(b)/8] |= (1 << ((b) % 8)); } while (0)
+
+  /* check that tx_next_ack <= sn < tx_next */
+  if (!(sn_compare_tx(entity, entity->tx_next_ack, nack_sn) <= 0 &&
+        sn_compare_tx(entity, nack_sn, entity->tx_next) < 0))
+    return;
+
+  /* process wait list */
+  head.next = entity->wait_list;
+  prev = &head;
+  cur = entity->wait_list;
+  while (cur != NULL) {
+    if (cur->sdu->sn == nack_sn &&
+        so_overlap(so_start, so_end, cur->so, cur->so + cur->size - 1)) {
+      /* remove from wait list */
+      prev->next = cur->next;
+      cur->next = NULL;
+      /* consider the SDU segment for retransmission */
+      consider_retransmission(entity, cur, !IS_SN_SET(cur->sdu->sn));
+      SET_SN(cur->sdu->sn);
+      entity->wait_end = prev;
+      cur = prev->next;
+    } else {
+      entity->wait_end = cur;
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->wait_list = head.next;
+  if (entity->wait_list == NULL)
+    entity->wait_end = NULL;
+
+  /* process ack list */
+  head.next = entity->ack_list;
+  prev = &head;
+  cur = entity->ack_list;
+  while (cur != NULL) {
+    if (cur->sdu->sn == nack_sn &&
+        so_overlap(so_start, so_end, cur->so, cur->so + cur->size - 1)) {
+      /* remove from ack list */
+      prev->next = cur->next;
+      cur->next = NULL;
+      /* consider the SDU segment for retransmission */
+      consider_retransmission(entity, cur, !IS_SN_SET(cur->sdu->sn));
+      SET_SN(cur->sdu->sn);
+      cur = prev->next;
+    } else {
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->ack_list = head.next;
+
+#undef IS_BIT_SET
+#undef SET_BIT
+}
+
+static void process_received_nack(nr_rlc_entity_am_t *entity, int nack_sn,
+                                  int so_start, int so_end, int range,
+                                  unsigned char *sn_set)
+{
+  int i;
+
+  for (i = 0; i < range; i++)
+    process_nack_sn(entity, (nack_sn + i) % entity->sn_modulus,
+                    i == 0 ?         so_start : 0,
+                    i == range - 1 ? so_end : -1,
+                    sn_set);
+}
+
+static int sdu_segment_in_ack_list_full(nr_rlc_sdu_segment_t *sdu)
+{
+  int target_count = sdu->sdu->ref_count;
+  int actual_count = 0;
+  int sn = sdu->sdu->sn;
+
+  while (sdu != NULL && sdu->sdu->sn == sn) {
+    actual_count++;
+    sdu = sdu->next;
+  }
+
+  return actual_count == target_count;
+}
+
+static void finalize_ack_nack_processing(nr_rlc_entity_am_t *entity)
+{
+  nr_rlc_sdu_segment_t *cur = entity->ack_list;
+  int sn;
+
+  /* - send indication of successful delivery for all consecutive acked SDUs
+   *   starting from tx_next_ack. Also free them.
+   * - update tx_next_ack to the next SN not acked yet
+   */
+  /* todo: send indication of successful delivery as soon as possible as
+   *       the specs say (38.322 5.2.3.1.1). As the code is, if we receive
+   *       ack for SN+2 we won't indicate successful delivery before
+   *       SN+1 has been indicated.
+   */
+  while (cur != NULL && cur->sdu->sn == entity->tx_next_ack &&
+         sdu_segment_in_ack_list_full(cur)) {
+    entity->tx_size -= cur->sdu->size;
+    sn = cur->sdu->sn;
+    entity->common.sdu_successful_delivery(
+        entity->common.sdu_successful_delivery_data,
+        (nr_rlc_entity_t *)entity, cur->sdu->upper_layer_id);
+    while (cur != NULL && cur->sdu->sn == sn) {
+      nr_rlc_sdu_segment_t *s = cur;
+      cur = cur->next;
+      nr_rlc_free_sdu_segment(s);
+    }
+    entity->ack_list = cur;
+    entity->tx_next_ack = (entity->tx_next_ack + 1) % entity->sn_modulus;
+  }
+}
+
+void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *_entity,
+                               char *buffer, int size)
+{
+#define R(d) do { if (nr_rlc_pdu_decoder_in_error(&d)) goto err; } while (0)
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  nr_rlc_pdu_decoder_t decoder;
+  nr_rlc_pdu_decoder_t control_decoder;
+  nr_rlc_pdu_t *pdu;
+  int dc;
+  int p = 0;
+  int si;
+  int sn;
+  int so = 0;
+  int data_size;
+  int is_first;
+  int is_last;
+
+  int cpt;
+  int e1;
+  int e2;
+  int e3;
+  int ack_sn;
+  int nack_sn;
+  int so_start;
+  int so_end;
+  int range;
+  int control_e1;
+  int control_e2;
+  int control_e3;
+  unsigned char sn_set[32768];  /* used to dec retx_count only once per sdu */
+
+  nr_rlc_pdu_decoder_init(&decoder, buffer, size);
+  dc = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  if (dc == 0) goto control;
+
+  /* data PDU */
+  p  = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  si = nr_rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
+
+  is_first = (si & 0x2) == 0;
+  is_last = (si & 0x1) == 0;
+
+  if (entity->sn_field_length == 18) {
+    nr_rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
+  }
+
+  sn = nr_rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length);
+  R(decoder);
+
+  if (!is_first) {
+    so = nr_rlc_pdu_decoder_get_bits(&decoder, 16); R(decoder);
+    if (so == 0) {
+      LOG_E(RLC, "%s:%d:%s: warning: discard PDU, bad so\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      goto discard;
+    }
+  }
+
+  data_size = size - decoder.byte;
+
+  /* dicard PDU if no data */
+  if (data_size <= 0) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, no data\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    goto discard;
+  }
+
+  /* dicard PDU if rx buffer is full */
+  if (entity->rx_size + data_size > entity->rx_maxsize) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, RX buffer full\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    goto discard;
+  }
+
+  if (!sn_in_recv_window(entity, sn)) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, sn out of window (sn %d rx_next %d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+           sn, entity->rx_next);
+    goto discard;
+  }
+
+  /* discard segment if all the bytes of the segment are already there */
+  if (segment_already_received(entity, sn, so, data_size)) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, already received\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    goto discard;
+  }
+
+  /* put in pdu reception list */
+  entity->rx_size += data_size;
+  pdu = nr_rlc_new_pdu(sn, so, is_first, is_last,
+                       buffer + size - data_size, data_size);
+  entity->rx_list = nr_rlc_pdu_list_add(sn_compare_rx, entity,
+                                        entity->rx_list, pdu);
+
+  /* do reception actions (38.322 5.2.3.2.3) */
+  reception_actions(entity, pdu);
+
+  if (p) {
+    /* 38.322 5.3.4 says status triggering should be delayed
+     * until x < rx_highest_status or x >= rx_next + am_window_size.
+     * This is not clear (what is x then? we keep the same?). So let's
+     * trigger no matter what.
+     * todo: delay status triggering properly
+     */
+    int v = (entity->rx_next + entity->window_size) % entity->sn_modulus;
+    entity->status_triggered = 1;
+    if (!(sn_compare_rx(entity, sn, entity->rx_highest_status) < 0 ||
+          sn_compare_rx(entity, sn, v) >= 0)) {
+      LOG_D(RLC, "%s:%d:%s: warning: STATUS trigger should be delayed, according to specs\n",
+            __FILE__, __LINE__, __FUNCTION__);
+    }
+  }
+
+  return;
+
+control:
+  cpt = nr_rlc_pdu_decoder_get_bits(&decoder, 3); R(decoder);
+  if (cpt != 0) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, CPT not 0 (%d)\n",
+          __FILE__, __LINE__, __FUNCTION__, cpt);
+    goto discard;
+  }
+  ack_sn = nr_rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length); R(decoder);
+  e1 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  /* r bits */
+  if (entity->sn_field_length == 18) {
+    nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  } else {
+    nr_rlc_pdu_decoder_get_bits(&decoder, 7); R(decoder);
+  }
+
+  /* let's try to parse the control PDU once to check consistency */
+  control_decoder = decoder;
+  control_e1 = e1;
+  while (control_e1) {
+    nr_rlc_pdu_decoder_get_bits(&control_decoder, entity->sn_field_length); R(control_decoder); /* NACK_SN */
+    control_e1 = nr_rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
+    control_e2 = nr_rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
+    control_e3 = nr_rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
+    /* r bits */
+    if (entity->sn_field_length == 18) {
+      nr_rlc_pdu_decoder_get_bits(&control_decoder, 3); R(control_decoder);
+    } else {
+      nr_rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
+    }
+    if (control_e2) {
+      nr_rlc_pdu_decoder_get_bits(&control_decoder, 16); R(control_decoder); /* SOstart */
+      nr_rlc_pdu_decoder_get_bits(&control_decoder, 16); R(control_decoder); /* SOend */
+    }
+    if (control_e3) {
+      nr_rlc_pdu_decoder_get_bits(&control_decoder, 8); R(control_decoder); /* NACK range */
+    }
+  }
+
+  /* 38.322 5.3.3.3 says to stop t_poll_retransmit if a ACK or NACK is
+   * received for the SN 'poll_sn'
+   */
+  if (sn_compare_tx(entity, entity->poll_sn, ack_sn) < 0)
+    entity->t_poll_retransmit_start = 0;
+
+  /* at this point, accept the PDU even if the actual values
+   * may be incorrect (eg. if so_start > so_end)
+   */
+  process_received_ack(entity, ack_sn);
+
+  if (e1)
+    memset(sn_set, 0, 32768);
+
+  while (e1) {
+    nack_sn = nr_rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length); R(decoder);
+    e1 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+    e2 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+    e3 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+    /* r bits */
+    if (entity->sn_field_length == 18) {
+      nr_rlc_pdu_decoder_get_bits(&decoder, 3); R(decoder);
+    } else {
+      nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+    }
+    if (e2) {
+      so_start = nr_rlc_pdu_decoder_get_bits(&decoder, 16); R(decoder);
+      so_end = nr_rlc_pdu_decoder_get_bits(&decoder, 16); R(decoder);
+      if (so_end < so_start) {
+        LOG_W(RLC, "%s:%d:%s: warning, bad so start/end, NACK the whole PDU (sn %d)\n",
+              __FILE__, __LINE__, __FUNCTION__, nack_sn);
+        so_start = 0;
+        so_end = -1;
+      }
+      /* special value 0xffff indicates 'all bytes to the end' */
+      if (so_end == 0xffff)
+        so_end = -1;
+    } else {
+      so_start = 0;
+      so_end = -1;
+    }
+    if (e3) {
+      range = nr_rlc_pdu_decoder_get_bits(&decoder, 8); R(decoder);
+    } else {
+      range = 1;
+    }
+    process_received_nack(entity, nack_sn, so_start, so_end, range, sn_set);
+
+    /* 38.322 5.3.3.3 says to stop t_poll_retransmit if a ACK or NACK is
+     * received for the SN 'poll_sn'
+     */
+    if (sn_compare_tx(entity, nack_sn, entity->poll_sn) <= 0 &&
+        sn_compare_tx(entity, entity->poll_sn, (nack_sn + range) % entity->sn_modulus) < 0)
+      entity->t_poll_retransmit_start = 0;
+  }
+
+  finalize_ack_nack_processing(entity);
+
+  return;
+
+err:
+  LOG_W(RLC, "%s:%d:%s: error decoding PDU, discarding\n", __FILE__, __LINE__, __FUNCTION__);
+  goto discard;
+
+discard:
+  if (p)
+    entity->status_triggered = 1;
+
+#undef R
+}
+
+/*************************************************************************/
+/* TX functions                                                          */
+/*************************************************************************/
+
+static int is_window_stalling(nr_rlc_entity_am_t *entity)
+{
+  /* we are stalling if tx_next is not:
+   *   tx_next_ack <= tx_next < tx_next_ack + window_size
+   */
+  return !(sn_compare_tx(entity, entity->tx_next_ack, entity->tx_next) <= 0 &&
+           sn_compare_tx(entity, entity->tx_next,
+                         (entity->tx_next_ack + entity->window_size) %
+                           entity->sn_modulus) < 0);
+}
+
+static void include_poll(nr_rlc_entity_am_t *entity, char *buffer)
+{
+  /* set the P bit to 1 */
+  buffer[0] |= 0x40;
+
+  entity->pdu_without_poll = 0;
+  entity->byte_without_poll = 0;
+
+  /* set POLL_SN to highest SN submitted to lower layer
+   * (this is: entity->tx_next - 1) (todo: be sure of this)
+   */
+  entity->poll_sn = (entity->tx_next - 1 + entity->sn_modulus)
+                      % entity->sn_modulus;
+
+  /* start/restart t_poll_retransmit */
+  entity->t_poll_retransmit_start = entity->t_current;
+}
+
+static int check_poll_after_pdu_assembly(nr_rlc_entity_am_t *entity)
+{
+  int retransmission_buffer_empty;
+  int transmission_buffer_empty;
+
+  /* is transmission buffer empty? */
+  if (entity->tx_list == NULL)
+    transmission_buffer_empty = 1;
+  else
+    transmission_buffer_empty = 0;
+
+  /* is retransmission buffer empty? */
+  if (entity->retransmit_list == NULL)
+    retransmission_buffer_empty = 1;
+  else
+    retransmission_buffer_empty = 0;
+
+  return (transmission_buffer_empty && retransmission_buffer_empty) ||
+         is_window_stalling(entity);
+}
+
+static int serialize_sdu(nr_rlc_entity_am_t *entity,
+                         nr_rlc_sdu_segment_t *sdu, char *buffer, int bufsize,
+                         int p)
+{
+  nr_rlc_pdu_encoder_t encoder;
+
+  /* generate header */
+  nr_rlc_pdu_encoder_init(&encoder, buffer, bufsize);
+
+  nr_rlc_pdu_encoder_put_bits(&encoder, 1, 1);             /* D/C: 1 = data */
+  nr_rlc_pdu_encoder_put_bits(&encoder, 0, 1);     /* P: reserve, set later */
+
+  nr_rlc_pdu_encoder_put_bits(&encoder, 1-sdu->is_first,1);/* 1st bit of SI */
+  nr_rlc_pdu_encoder_put_bits(&encoder, 1-sdu->is_last,1); /* 2nd bit of SI */
+
+  if (entity->sn_field_length == 18)
+    nr_rlc_pdu_encoder_put_bits(&encoder, 0, 2);                       /* R */
+
+  nr_rlc_pdu_encoder_put_bits(&encoder, sdu->sdu->sn,
+                                        entity->sn_field_length);     /* SN */
+
+  if (!sdu->is_first)
+    nr_rlc_pdu_encoder_put_bits(&encoder, sdu->so, 16);               /* SO */
+
+  /* data */
+  memcpy(buffer + encoder.byte, sdu->sdu->data + sdu->so, sdu->size);
+
+  if (p)
+    include_poll(entity, buffer);
+
+  return encoder.byte + sdu->size;
+}
+
+/* for a given SDU/SDU segment, computes the corresponding PDU header size */
+static int compute_pdu_header_size(nr_rlc_entity_am_t *entity,
+                                   nr_rlc_sdu_segment_t *sdu)
+{
+  int header_size = 2;
+  /* one more byte if SN field length is 18 */
+  if (entity->sn_field_length == 18)
+    header_size++;
+  /* two more bytes for SO if SDU segment is not the first */
+  if (!sdu->is_first) header_size += 2;
+  return header_size;
+}
+
+/* resize SDU/SDU segment for the corresponding PDU to fit into 'pdu_size'
+ * bytes
+ * - modifies SDU/SDU segment to become an SDU segment
+ * - returns a new SDU segment covering the remaining data bytes
+ */
+static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu,
+                                       nr_rlc_entity_am_t *entity,
+                                       int pdu_size)
+{
+  nr_rlc_sdu_segment_t *next;
+  int pdu_header_size;
+  int over_size;
+
+  sdu->sdu->ref_count++;
+
+  pdu_header_size = compute_pdu_header_size(entity, sdu);
+
+  next = calloc(1, sizeof(nr_rlc_sdu_segment_t));
+  if (next == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__,  __FUNCTION__);
+    exit(1);
+  }
+  *next = *sdu;
+
+  over_size = pdu_header_size + sdu->size - pdu_size;
+
+  /* update SDU */
+  sdu->size -= over_size;
+  sdu->is_last = 0;
+
+  /* create new segment */
+  next->size = over_size;
+  next->so = sdu->so + sdu->size;
+  next->is_first = 0;
+
+  return next;
+}
+
+/*************************************************************************/
+/* TX functions - status reporting [begin]                               */
+/*************************************************************************/
+
+typedef struct {
+  /* data for missing bytes */
+  int sn_start;    /* set to -1 when no more missing part to report */
+  int so_start;
+  int sn_end;
+  int so_end;
+  /* data for maximum ack */
+  int ack_sn;                               /* -1 if not to be used */
+  /* pdu to use for next call to 'next_missing' */
+  nr_rlc_pdu_t *next;
+} missing_data_t;
+
+/* todo: rewrite this function, too messy */
+static missing_data_t next_missing(nr_rlc_entity_am_t *entity,
+                                        nr_rlc_pdu_t *cur, int check_head)
+{
+  missing_data_t ret;
+  int cur_max_so;
+  int sn;
+  int max_so       = 0;
+  int last_reached = 0;
+
+  ret.ack_sn = -1;
+
+  /* special case: missing part before the head of RX list */
+  if (check_head) {
+    if (cur->sn != entity->rx_next || !cur->is_first) {
+      /* don't report if out of reporting window */
+      if (sn_compare_rx(entity, entity->rx_highest_status, cur->sn) <= 0) {
+        ret.sn_start = -1;
+        return ret;
+      }
+      /* the missing part is starting from rx_next(0)
+       * going to min of:
+       *     - cur->sn(cur->so-1) [if cur->sn is not first]
+       *       or (cur->sn-1)(0xffff) [if cur->sn is first]
+       *     - (entity->rx_highest_status-1)(0xffff)
+       */
+      ret.sn_start = entity->rx_next;
+      ret.so_start = 0;
+      goto set_end_different_sdu;
+    }
+  }
+
+next_pdu:
+  sn = cur->sn;
+  cur_max_so = cur->so + cur->size - 1;
+  if (cur_max_so > max_so)
+    max_so = cur_max_so;
+  last_reached = last_reached | cur->is_last;
+
+  /* if cur already processed, it can be the acked SDU */
+  if (cur->data == NULL)
+    ret.ack_sn = (cur->sn + 1) % entity->sn_modulus;
+
+  /* no next? */
+  if (cur->next == NULL) {
+    /* inform the caller that work is over */
+    ret.next = NULL;
+
+    /* already processed => next SDU to rx_highest_status - 1 to be nacked */
+    if (cur->data == NULL) {
+      ret.sn_start = (cur->sn + 1) % entity->sn_modulus;
+      /* don't report if out of reporting window */
+      if (sn_compare_rx(entity, entity->rx_highest_status,
+                        ret.sn_start) <= 0) {
+        ret.sn_start = -1;
+        return ret;
+      }
+      ret.so_start = 0;
+      ret.sn_end   = (entity->rx_highest_status - 1 + entity->sn_modulus) %
+                        entity->sn_modulus;
+      ret.so_end   = 0xffff;
+      return ret;
+    }
+    /* not already processed => all bytes after max_so (if any) then all SDU
+     * to rx_highest_status-1 to be nacked
+     */
+    if (last_reached) {
+      ret.sn_start = (cur->sn + 1) % entity->sn_modulus;
+      ret.so_start = 0;
+    } else {
+      ret.sn_start = cur->sn;
+      ret.so_start = max_so + 1;
+    }
+    /* don't report if out of reporting window */
+    if (sn_compare_rx(entity, entity->rx_highest_status,
+                      ret.sn_start) <= 0) {
+      ret.sn_start = -1;
+      return ret;
+    }
+    ret.sn_end   = (entity->rx_highest_status - 1 + entity->sn_modulus) %
+                      entity->sn_modulus;
+    ret.so_end   = 0xffff;
+    return ret;
+  }
+
+  cur = cur->next;
+
+  /* no discontinuity in data => process to next PDU */
+  if (cur->sn == sn && max_so >= cur->so - 1)
+    goto next_pdu;
+  if (cur->sn == (sn + 1) % entity->sn_modulus && last_reached &&
+      cur->is_first) {
+    last_reached = 0;
+    max_so       = 0;
+    goto next_pdu;
+  }
+
+  /* discontinuity in data */
+
+  /* remember where to start from for the next call */
+  ret.next = cur;
+
+  /* discontinuity in same SDU */
+  if (cur->sn == sn) {
+    ret.sn_start = sn;
+    /* don't report if out of reporting window */
+    if (sn_compare_rx(entity, entity->rx_highest_status,
+                      ret.sn_start) <= 0) {
+      ret.sn_start = -1;
+      return ret;
+    }
+    ret.so_start = max_so + 1;
+    ret.sn_end = sn;
+    ret.so_end = cur->so - 1;
+    return ret;
+  }
+
+  /* discontinuity between different SDUs */
+  ret.sn_start = sn;
+  /* don't report if out of reporting window */
+  if (sn_compare_rx(entity, entity->rx_highest_status, ret.sn_start) <= 0) {
+    ret.sn_start = -1;
+    return ret;
+  }
+  ret.so_start = max_so + 1;
+
+set_end_different_sdu:
+  /* don't go more than rx_highest_status - 1 */
+  if (sn_compare_rx(entity, entity->rx_highest_status, cur->sn) <= 0) {
+    ret.so_end = (entity->rx_highest_status - 1 + entity->sn_modulus) %
+                      entity->sn_modulus;
+    ret.so_end   = 0xffff;
+    return ret;
+  }
+
+  /* if cur is the head of a SDU, then use cur-1 */
+  if (cur->is_first) {
+    ret.sn_end = (cur->sn - 1 + entity->sn_modulus) % entity->sn_modulus;
+    ret.so_end = 0xffff;
+    return ret;
+  }
+
+  ret.sn_end = cur->sn;
+  ret.so_end = cur->so - 1;
+  return ret;
+}
+
+static int nack_size(nr_rlc_entity_am_t *entity, missing_data_t *m)
+{
+  int nack_length = 2 + (entity->sn_field_length == 18);
+
+  if (m->sn_start == m->sn_end) {
+    /* only nack_sn, no so_start/end, no nack range */
+    if (m->so_start == 0 && m->so_end == 0xffff)
+      return nack_length;
+    /* nack_sn + so_start/end */
+    return nack_length + 4;
+  }
+
+  /* nack_sn + nack range, no so_start/end */
+  if (m->so_start == 0 && m->so_end == 0xffff)
+    return nack_length + 1;
+
+  /* nack_sn + so_start/end + nack range */
+  return nack_length + 5;
+}
+
+/* returns the e1 byte/bit position supposing the encoder points at
+ * the beginning of a nack_sn block
+ */
+static void get_e1_position(nr_rlc_entity_am_t *entity,
+                            nr_rlc_pdu_encoder_t *encoder,
+                            int *e1_byte, int *e1_bit)
+{
+  if (entity->sn_field_length == 18) {
+    *e1_byte = encoder->byte + 2;
+    *e1_bit = 5;
+  } else {
+    *e1_byte = encoder->byte + 1;
+    *e1_bit = 3;
+  }
+}
+
+/* returns the number of nacks serialized.
+ * In most cases it is 1, it can be more if the
+ * missing data consists of a range that is more
+ * than 255 SNs in which case it has to be cut in
+ * smaller ranges.
+ * If there is no more room in the status buffer,
+ * will set m->next = NULL (and may serialize
+ * less nacks than required by 'm').
+ */
+static int generate_missing(nr_rlc_entity_am_t *entity,
+                            nr_rlc_pdu_encoder_t *encoder,
+                            missing_data_t *m, int *e1_byte, int *e1_bit)
+{
+  int r_bits = entity->sn_field_length == 18 ? 3 : 1;
+  int range_count = 0;
+  int sn_start;
+  int so_start;
+  int sn_end;
+  int so_end;
+  int sn_count;
+  missing_data_t m_nack;
+  int e2;
+  int e3;
+
+  /* be careful to limit a range to 255 SNs, that is: cut if needed */
+  sn_count = (m->sn_end - m->sn_start + entity->sn_modulus)
+              % entity->sn_modulus + 1;
+
+  sn_start = m->sn_start;
+
+  while (sn_count) {
+    int cur_sn_count = sn_count;
+    if (cur_sn_count > 255)
+      cur_sn_count = 255;
+
+    /* for first range, so_start is the one of the initial range
+     * for the following ones, it is 0
+     */
+    if (sn_start == m->sn_start) {
+      /* first range */
+      so_start = m->so_start;
+    } else {
+      /* following ranges */
+      so_start = 0;
+    }
+
+    /* for the last range, sn_end/so_end are the ones of the initial range
+     * for the previous ones, it is sn_start+254/0xffff
+     */
+    if (cur_sn_count == sn_count) {
+      /* last range */
+      sn_end = m->sn_end;
+      so_end = m->so_end;
+    } else {
+      /* previous ranges */
+      sn_end = (sn_start + 254) % entity->sn_modulus;
+      so_end = 0xffff;
+    }
+
+    /* check that there is room for a nack */
+    m_nack.sn_start = sn_start;
+    m_nack.so_start = so_start;
+    m_nack.sn_end = sn_end;
+    m_nack.so_end = so_end;
+    if (encoder->byte + nack_size(entity, &m_nack) > encoder->size) {
+      m->next = NULL;
+      break;
+    }
+
+    /* set the previous e1 bit to 1 */
+    encoder->buffer[*e1_byte] |= 1 << *e1_bit;
+
+    get_e1_position(entity, encoder, e1_byte, e1_bit);
+
+    if (sn_start == sn_end) {
+      if (so_start == 0 && so_end == 0xffff) {
+        /* only nack_sn, no so_start/end, no nack range */
+        e2 = 0;
+        e3 = 0;
+      } else {
+        /* nack_sn + so_start/end, no nack range */
+        e2 = 1;
+        e3 = 0;
+      }
+    } else {
+      if (so_start == 0 && so_end == 0xffff) {
+        /* nack_sn + nack range, no so_start/end */
+        e2 = 0;
+        e3 = 1;
+      } else {
+        /* nack_sn + so_start/end + nack range */
+        e2 = 1;
+        e3 = 1;
+      }
+    }
+
+    /* nack_sn */
+    nr_rlc_pdu_encoder_put_bits(encoder, sn_start,
+                                entity->sn_field_length);
+    /* e1 = 0 (set later if needed) */
+    nr_rlc_pdu_encoder_put_bits(encoder, 0, 1);
+    /* e2 */
+    nr_rlc_pdu_encoder_put_bits(encoder, e2, 1);
+    /* e3 */
+    nr_rlc_pdu_encoder_put_bits(encoder, e3, 1);
+    /* r */
+    nr_rlc_pdu_encoder_put_bits(encoder, 0, r_bits);
+    /* so_start/so_end */
+    if (e2) {
+      nr_rlc_pdu_encoder_put_bits(encoder, so_start, 16);
+      nr_rlc_pdu_encoder_put_bits(encoder, so_end, 16);
+    }
+    /* nack range */
+    if (e3)
+      nr_rlc_pdu_encoder_put_bits(encoder, cur_sn_count, 8);
+
+    sn_count -= cur_sn_count;
+    sn_start = (sn_start + cur_sn_count) % entity->sn_modulus;
+    range_count++;
+  }
+
+  return range_count;
+}
+
+static int generate_status(nr_rlc_entity_am_t *entity, char *buffer, int size)
+{
+  int                  ack_sn = entity->rx_next;
+  missing_data_t  m;
+  nr_rlc_pdu_t         *cur;
+  int                  nack_count = 0;
+  nr_rlc_pdu_encoder_t encoder;
+  int                  e1_byte;
+  int                  e1_bit;
+
+  /* if not enough room, do nothing */
+  if (size < 3)
+    return 0;
+
+  nr_rlc_pdu_encoder_init(&encoder, buffer, size);
+
+  /* first 3 bytes, ack_sn and e1 will be set later */
+  nr_rlc_pdu_encoder_put_bits(&encoder, 0, 8*3);
+
+  cur = entity->rx_list;
+
+  /* store the position of the e1 bit to be set if
+   * there is a nack following
+   */
+  e1_byte = 2;
+  e1_bit = entity->sn_field_length == 18 ? 1 : 7;
+
+  while (cur != NULL) {
+    m = next_missing(entity, cur, nack_count == 0);
+
+    /* update ack_sn if the returned value is valid */
+    if (m.ack_sn != -1)
+      ack_sn = m.ack_sn;
+
+    /* stop here if no more nack to report */
+    if (m.sn_start == -1)
+      break;
+
+    nack_count += generate_missing(entity, &encoder, &m, &e1_byte, &e1_bit);
+
+    cur = m.next;
+  }
+
+  /* put ack_sn */
+  if (entity->sn_field_length == 12) {
+    buffer[0] = ack_sn >> 8;
+    buffer[1] = ack_sn & 255;
+  } else {
+    buffer[0] = ack_sn >> 14;
+    buffer[1] = (ack_sn >> 6) & 255;
+    buffer[2] |= (ack_sn & 0x3f) << 2;
+  }
+
+  /* reset the trigger */
+  entity->status_triggered = 0;
+
+  /* start t_status_prohibit */
+  entity->t_status_prohibit_start = entity->t_current;
+
+  return encoder.byte;
+}
+
+static int status_to_report(nr_rlc_entity_am_t *entity)
+{
+  return entity->status_triggered &&
+         (entity->t_status_prohibit_start == 0 ||
+          entity->t_current - entity->t_status_prohibit_start >
+              entity->t_status_prohibit);
+}
+
+static int missing_size(nr_rlc_entity_am_t *entity, missing_data_t *m,
+                        int *size, int maxsize)
+{
+  int r_bits = entity->sn_field_length == 18 ? 3 : 1;
+  int range_count = 0;
+  int sn_start;
+  int so_start;
+  int sn_end;
+  int so_end;
+  int sn_count;
+  missing_data_t m_nack;
+
+  /* be careful to limit a range to 255 SNs, that is: cut if needed */
+  sn_count = m->sn_end - m->sn_start;
+  if (sn_count < 0)
+    sn_count += entity->sn_modulus;
+
+  sn_start = m->sn_start;
+
+  while (sn_count) {
+    int cur_sn_count = sn_count;
+    if (cur_sn_count > 255)
+      cur_sn_count = 255;
+
+    /* for first range, so_start is the one of the initial range
+     * for the following ones, it is 0
+     */
+    if (sn_start == m->sn_start) {
+      /* first range */
+      so_start = m->so_start;
+    } else {
+      /* following ranges */
+      so_start = 0;
+    }
+
+    /* for the last range, sn_end/so_end are the ones of the initial range
+     * for the previous ones, it is sn_start+254/0xffff
+     */
+    if (cur_sn_count == sn_count) {
+      /* last range */
+      sn_end = m->sn_end;
+      so_end = m->so_end;
+    } else {
+      /* previous ranges */
+      sn_end = (sn_start + 254) % entity->sn_modulus;
+      so_end = 0xffff;
+    }
+
+    /* check that there is room for a nack */
+    m_nack.sn_start = sn_start;
+    m_nack.so_start = so_start;
+    m_nack.sn_end = sn_end;
+    m_nack.so_end = so_end;
+    if (*size + nack_size(entity, &m_nack) > maxsize) {
+      m->next = NULL;
+      break;
+    }
+
+    if (sn_start == sn_end) {
+      if (so_start == 0 && so_end == 0xffff) {
+        /* only nack_sn, no so_start/end, no nack range */
+        *size += (entity->sn_field_length + 3 + r_bits) / 8;
+      } else {
+        /* nack_sn + so_start/end, no nack range */
+        *size += (entity->sn_field_length + 3 + r_bits + 16*2) / 8;
+      }
+    } else {
+      if (so_start == 0 && so_end == 0xffff) {
+        /* nack_sn + nack range, no so_start/end */
+        *size += (entity->sn_field_length + 3 + r_bits + 8) / 8;
+      } else {
+        /* nack_sn + so_start/end + nack range */
+        *size += (entity->sn_field_length + 3 + r_bits + 16*2 + 8) / 8;
+      }
+    }
+
+    sn_count -= cur_sn_count;
+    sn_start = (sn_start + cur_sn_count) % entity->sn_modulus;
+    range_count++;
+  }
+
+  return range_count;
+}
+
+static int status_size(nr_rlc_entity_am_t *entity, int maxsize)
+{
+  missing_data_t  m;
+  nr_rlc_pdu_t    *cur;
+  int             nack_count = 0;
+  int             size;
+
+  /* if not enough room, do nothing */
+  if (maxsize < 3)
+    return 0;
+
+  /* minimum 3 bytes */
+  size = 3;
+
+  cur = entity->rx_list;
+
+  while (cur != NULL) {
+    m = next_missing(entity, cur, nack_count == 0);
+
+    /* stop here if no more nack to report */
+    if (m.sn_start == -1)
+      break;
+
+    nack_count += missing_size(entity, &m, &size, maxsize);
+
+    cur = m.next;
+  }
+
+  return size;
+}
+
+/*************************************************************************/
+/* TX functions - status reporting [end]                                 */
+/*************************************************************************/
+
+static int generate_retx_pdu(nr_rlc_entity_am_t *entity, char *buffer,
+                             int size)
+{
+  nr_rlc_sdu_segment_t *sdu;
+  int pdu_header_size;
+  int pdu_size;
+  int p;
+
+  sdu = entity->retransmit_list;
+
+  pdu_header_size = compute_pdu_header_size(entity, sdu);
+
+  /* not enough room for at least one byte of data? do nothing */
+  if (pdu_header_size + 1 > size)
+    return 0;
+
+  entity->retransmit_list = entity->retransmit_list->next;
+  if (entity->retransmit_list == NULL)
+    entity->retransmit_end = NULL;
+
+  sdu->next = NULL;
+
+  /* segment if necessary */
+  pdu_size = pdu_header_size + sdu->size;
+  if (pdu_size > size) {
+    nr_rlc_sdu_segment_t *next_sdu;
+    next_sdu = resegment(sdu, entity, size);
+    /* put the second SDU back at the head of the retransmit list */
+    next_sdu->next = entity->retransmit_list;
+    entity->retransmit_list = next_sdu;
+    if (entity->retransmit_end == NULL)
+      entity->retransmit_end = entity->retransmit_list;
+  }
+
+  /* put SDU/SDU segment in the wait list */
+  nr_rlc_sdu_segment_list_append(&entity->wait_list, &entity->wait_end, sdu);
+
+  p = check_poll_after_pdu_assembly(entity);
+
+  if (entity->force_poll) {
+    p = 1;
+    entity->force_poll = 0;
+  }
+
+  return serialize_sdu(entity, sdu, buffer, size, p);
+}
+
+static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size)
+{
+  nr_rlc_sdu_segment_t *sdu;
+  int pdu_header_size;
+  int pdu_size;
+  int p;
+
+  /* sn out of window (that is: we have window stalling)? do nothing */
+  if (is_window_stalling(entity))
+    return 0;
+
+  if (entity->tx_list == NULL)
+    return 0;
+
+  sdu = entity->tx_list;
+
+  pdu_header_size = compute_pdu_header_size(entity, sdu);
+
+  /* not enough room for at least one byte of data? do nothing */
+  if (pdu_header_size + 1 > size)
+    return 0;
+
+  entity->tx_list = entity->tx_list->next;
+  if (entity->tx_list == NULL)
+    entity->tx_end = NULL;
+
+  sdu->next = NULL;
+
+  /* assign SN to SDU */
+  sdu->sdu->sn = entity->tx_next;
+
+  /* segment if necessary */
+  pdu_size = pdu_header_size + sdu->size;
+  if (pdu_size > size) {
+    nr_rlc_sdu_segment_t *next_sdu;
+    next_sdu = resegment(sdu, entity, size);
+    /* put the second SDU back at the head of the TX list */
+    next_sdu->next = entity->tx_list;
+    entity->tx_list = next_sdu;
+    if (entity->tx_end == NULL)
+      entity->tx_end = entity->tx_list;
+  }
+
+  /* update tx_next if the SDU segment is the last */
+  if (sdu->is_last)
+    entity->tx_next = (entity->tx_next + 1) % entity->sn_modulus;
+
+  /* put SDU/SDU segment in the wait list */
+  nr_rlc_sdu_segment_list_append(&entity->wait_list, &entity->wait_end, sdu);
+
+  /* polling actions for a new PDU */
+  entity->pdu_without_poll++;
+  entity->byte_without_poll += sdu->size;
+  if ((entity->poll_pdu != -1 &&
+       entity->pdu_without_poll >= entity->poll_pdu) ||
+      (entity->poll_byte != -1 &&
+       entity->byte_without_poll >= entity->poll_byte))
+    p = 1;
+  else
+    p = check_poll_after_pdu_assembly(entity);
+
+  if (entity->force_poll) {
+    p = 1;
+    entity->force_poll = 0;
+  }
+
+  return serialize_sdu(entity, sdu, buffer, size, p);
+}
+
+/* Pretend to serialize all the SDUs in a list and return the size
+ * of all the PDUs it would produce, limited to 'maxsize'.
+ * Used for buffer status reporting.
+ */
+static int tx_list_size(nr_rlc_entity_am_t *entity,
+                        nr_rlc_sdu_segment_t *l, int maxsize)
+{
+  int ret = 0;
+
+  while (l != NULL) {
+    ret += compute_pdu_header_size(entity, l) + l->size;
+    l = l->next;
+  }
+
+  if (ret > maxsize) ret = maxsize;
+  return ret;
+}
+
+nr_rlc_entity_buffer_status_t nr_rlc_entity_am_buffer_status(
+    nr_rlc_entity_t *_entity, int maxsize)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  nr_rlc_entity_buffer_status_t ret;
+
+  if (status_to_report(entity))
+    ret.status_size = status_size(entity, maxsize);
+  else
+    ret.status_size = 0;
+
+  ret.tx_size = tx_list_size(entity, entity->tx_list, maxsize);
+  ret.retx_size = tx_list_size(entity, entity->retransmit_list, maxsize);
+
+  return ret;
+}
+
+int nr_rlc_entity_am_generate_pdu(nr_rlc_entity_t *_entity,
+                                  char *buffer, int size)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  int ret;
+
+  if (status_to_report(entity)) {
+    ret = generate_status(entity, buffer, size);
+    if (ret != 0)
+      return ret;
+  }
+
+  if (entity->retransmit_list != NULL) {
+    ret = generate_retx_pdu(entity, buffer, size);
+    if (ret != 0)
+      return ret;
+  }
+
+  return generate_tx_pdu(entity, buffer, size);
+}
+
+/*************************************************************************/
+/* SDU RX functions                                                      */
+/*************************************************************************/
+
+void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *_entity,
+                               char *buffer, int size,
+                               int sdu_id)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  nr_rlc_sdu_segment_t *sdu;
+
+  if (size > NR_SDU_MAX) {
+    LOG_E(RLC, "%s:%d:%s: fatal: SDU size too big (%d bytes)\n",
+          __FILE__, __LINE__, __FUNCTION__, size);
+    exit(1);
+  }
+
+  if (entity->tx_size + size > entity->tx_maxsize) {
+    LOG_D(RLC, "%s:%d:%s: warning: SDU rejected, SDU buffer full\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    return;
+  }
+
+  entity->tx_size += size;
+
+  sdu = nr_rlc_new_sdu(buffer, size, sdu_id);
+
+  nr_rlc_sdu_segment_list_append(&entity->tx_list, &entity->tx_end, sdu);
+}
+
+/*************************************************************************/
+/* time/timers                                                           */
+/*************************************************************************/
+
+static void check_t_poll_retransmit(nr_rlc_entity_am_t *entity)
+{
+  nr_rlc_sdu_segment_t head;
+  nr_rlc_sdu_segment_t *cur;
+  nr_rlc_sdu_segment_t *prev;
+  int sn;
+  int old_retx_count;
+
+  /* 38.322 5.3.3.4 */
+  /* did t_poll_retransmit expire? */
+  if (entity->t_poll_retransmit_start == 0 ||
+      entity->t_current <= entity->t_poll_retransmit_start +
+                               entity->t_poll_retransmit)
+    return;
+
+  /* stop timer */
+  entity->t_poll_retransmit_start = 0;
+
+  /* 38.322 5.3.3.4 says:
+   *
+   *     - include a poll in a RLC data PDU as described in section 5.3.3.2
+   *
+   * That does not seem to be conditional. So we forcefully will send
+   * a poll as soon as we generate a PDU.
+   * Hopefully this interpretation is correct. In the worst case we generate
+   * more polling than necessary, but it's not a big deal. When
+   * 't_poll_retransmit' expires it means we didn't receive a status report,
+   * meaning a bad radio link, so things are quite bad at this point and
+   * asking again for a poll won't hurt much more.
+   */
+  entity->force_poll = 1;
+
+  LOG_D(RLC, "%s:%d:%s: warning: t_poll_retransmit expired\n",
+        __FILE__, __LINE__, __FUNCTION__);
+
+  /* do we meet conditions of 38.322 5.3.3.4? */
+  if (!check_poll_after_pdu_assembly(entity))
+    return;
+
+  /* search wait list for SDU with highest SN */
+  /* this code may be incorrect: in LTE we had to look for PDU
+   * with SN = VT(S) - 1, but for NR the specs say "highest SN among the
+   * ones submitted to lower layers" not 'tx_next - 1'. So we should look
+   * for the highest SN in the wait list. But that's no big deal. If the
+   * program runs this code, then the connection is in a bad state and we
+   * can retransmit whatever we want. At some point we will receive a status
+   * report and retransmit what we really have to. Actually we could just
+   * retransmit the head of wait list (the specs have this 'or').
+   * (Actually, maybe this interpretation is not correct and what the code
+   * does is correct. The specs are confusing.)
+   */
+  sn = (entity->tx_next - 1 + entity->sn_modulus) % entity->sn_modulus;
+
+  head.next = entity->wait_list;
+  cur = entity->wait_list;
+  prev = &head;
+
+  while (cur != NULL) {
+    if (cur->sdu->sn == sn)
+      break;
+    prev = cur;
+    cur = cur->next;
+  }
+
+  /* SDU with highest SN not found? take the head of wait list */
+  if (cur == NULL) {
+    cur = entity->wait_list;
+    prev = &head;
+    sn = cur->sdu->sn;
+  }
+
+  /* todo: do we need to for check cur == NULL?
+   * It seems that no, the wait list should not be empty here, but not sure.
+   */
+
+  old_retx_count = cur->sdu->retx_count;
+
+  /* 38.322 says "SDU", not "SDU segment", so let's retransmit all
+   * SDU segments with this SN
+   */
+  /* todo: maybe we could simply retransmit the current SDU segment,
+   * so that we don't have to run through the full wait list.
+   */
+  while (cur != NULL) {
+    if (cur->sdu->sn == sn) {
+      prev->next = cur->next;
+      cur->next = NULL;
+      /* put in retransmit list */
+      consider_retransmission(entity, cur,
+                              old_retx_count == cur->sdu->retx_count);
+    } else {
+      prev = cur;
+    }
+    cur = prev->next;
+  }
+  entity->wait_list = head.next;
+  /* reset wait_end (todo: optimize?) */
+  entity->wait_end = entity->wait_list;
+  while (entity->wait_end != NULL && entity->wait_end->next != NULL)
+    entity->wait_end = entity->wait_end->next;
+}
+
+static void check_t_reassembly(nr_rlc_entity_am_t *entity)
+{
+  int sn;
+
+  /* is t_reassembly running and if yes has it expired? */
+  if (entity->t_reassembly_start == 0 ||
+      entity->t_current <= entity->t_reassembly_start + entity->t_reassembly)
+    return;
+
+  /* stop timer */
+  entity->t_reassembly_start = 0;
+
+  LOG_D(RLC, "%s:%d:%s: t_reassembly expired\n",
+        __FILE__, __LINE__, __FUNCTION__);
+
+  /* update RX_Highest_Status */
+  sn = entity->rx_next_status_trigger;
+  while (sdu_delivered(entity, sn))
+    sn = (sn + 1) % entity->sn_modulus;
+  entity->rx_highest_status = sn;
+
+  if (sn_compare_rx(entity, entity->rx_next_highest,
+                    (entity->rx_highest_status+1) % entity->sn_modulus) > 0 ||
+      (entity->rx_next_highest ==
+         (entity->rx_highest_status+1) % entity->sn_modulus &&
+       sdu_has_missing_bytes(entity, entity->rx_highest_status))) {
+    entity->t_reassembly_start = entity->t_current;
+    entity->rx_next_status_trigger = entity->rx_next_highest;
+  }
+}
+
+void nr_rlc_entity_am_set_time(nr_rlc_entity_t *_entity, uint64_t now)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+
+  entity->t_current = now;
+
+  check_t_poll_retransmit(entity);
+
+  check_t_reassembly(entity);
+}
+
+/*************************************************************************/
+/* discard/re-establishment/delete                                       */
+/*************************************************************************/
+
+void nr_rlc_entity_am_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id)
+{
+  /* implements 38.322 5.4 */
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  nr_rlc_sdu_segment_t head;
+  nr_rlc_sdu_segment_t *cur;
+  nr_rlc_sdu_segment_t *prev;
+
+  head.next = entity->tx_list;
+  cur = entity->tx_list;
+  prev = &head;
+
+  while (cur != NULL && cur->sdu->upper_layer_id != sdu_id) {
+    prev = cur;
+    cur = cur->next;
+  }
+
+  /* if sdu_id not found or some bytes have already been 'PDU-ized'
+   * then do nothing
+   */
+  if (cur == NULL || !cur->is_first || !cur->is_last)
+    return;
+
+  /* remove SDU from tx_list */
+  prev->next = cur->next;
+  entity->tx_list = head.next;
+  if (entity->tx_end == cur) {
+    if (prev != &head)
+      entity->tx_end = prev;
+    else
+      entity->tx_end = NULL;
+  }
+
+  nr_rlc_free_sdu_segment(cur);
+}
+
+static void clear_entity(nr_rlc_entity_am_t *entity)
+{
+  nr_rlc_pdu_t *cur_rx;
+
+  entity->rx_next                = 0;
+  entity->rx_next_status_trigger = 0;
+  entity->rx_highest_status      = 0;
+  entity->rx_next_highest        = 0;
+
+  entity->status_triggered = 0;
+
+  entity->tx_next           = 0;
+  entity->tx_next_ack       = 0;
+  entity->poll_sn           = 0;
+  entity->pdu_without_poll  = 0;
+  entity->byte_without_poll = 0;
+  entity->force_poll        = 0;
+
+  entity->t_current = 0;
+
+  entity->t_poll_retransmit_start = 0;
+  entity->t_reassembly_start      = 0;
+  entity->t_status_prohibit_start = 0;
+
+  cur_rx = entity->rx_list;
+  while (cur_rx != NULL) {
+    nr_rlc_pdu_t *p = cur_rx;
+    cur_rx = cur_rx->next;
+    nr_rlc_free_pdu(p);
+  }
+  entity->rx_list = NULL;
+  entity->rx_size = 0;
+
+  nr_rlc_free_sdu_segment_list(entity->tx_list);
+  nr_rlc_free_sdu_segment_list(entity->wait_list);
+  nr_rlc_free_sdu_segment_list(entity->retransmit_list);
+  nr_rlc_free_sdu_segment_list(entity->ack_list);
+
+  entity->tx_list         = NULL;
+  entity->tx_end          = NULL;
+  entity->tx_size         = 0;
+
+  entity->wait_list       = NULL;
+  entity->wait_end        = NULL;
+
+  entity->retransmit_list = NULL;
+  entity->retransmit_end  = NULL;
+
+  entity->ack_list        = NULL;
+}
+
+void nr_rlc_entity_am_reestablishment(nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  clear_entity(entity);
+}
+
+void nr_rlc_entity_am_delete(nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  clear_entity(entity);
+  free(entity);
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h b/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h
new file mode 100644
index 0000000000..7a553a22a9
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _NR_RLC_ENTITY_AM_H_
+#define _NR_RLC_ENTITY_AM_H_
+
+#include "nr_rlc_entity.h"
+#include "nr_rlc_sdu.h"
+#include "nr_rlc_pdu.h"
+
+typedef struct {
+  nr_rlc_entity_t common;
+
+  /* configuration */
+  int t_poll_retransmit;
+  int t_reassembly;
+  int t_status_prohibit;
+  int poll_pdu;              /* -1 means infinity */
+  int poll_byte;             /* -1 means infinity */
+  int max_retx_threshold;
+  int sn_field_length;
+
+  int sn_modulus;
+  int window_size;
+
+  /* runtime rx */
+  int rx_next;
+  int rx_next_status_trigger;
+  int rx_highest_status;
+  int rx_next_highest;
+
+  int status_triggered;
+
+  /* runtime tx */
+  int tx_next;
+  int tx_next_ack;
+  int poll_sn;
+  int pdu_without_poll;
+  int byte_without_poll;
+  int force_poll;
+
+  /* set to the latest know time by the user of the module. Unit: ms */
+  uint64_t t_current;
+
+  /* timers (stores the TTI of activation, 0 means not active) */
+  uint64_t t_poll_retransmit_start;
+  uint64_t t_reassembly_start;
+  uint64_t t_status_prohibit_start;
+
+  /* rx management */
+  nr_rlc_pdu_t *rx_list;
+  int          rx_size;
+  int          rx_maxsize;
+
+  /* tx management */
+  nr_rlc_sdu_segment_t *tx_list;
+  nr_rlc_sdu_segment_t *tx_end;
+  int                  tx_size;
+  int                  tx_maxsize;
+
+  nr_rlc_sdu_segment_t *wait_list;
+  nr_rlc_sdu_segment_t *wait_end;
+
+  nr_rlc_sdu_segment_t *retransmit_list;
+  nr_rlc_sdu_segment_t *retransmit_end;
+
+  nr_rlc_sdu_segment_t *ack_list;
+} nr_rlc_entity_am_t;
+
+void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *entity,
+                               char *buffer, int size,
+                               int sdu_id);
+void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *entity,
+                               char *buffer, int size);
+nr_rlc_entity_buffer_status_t nr_rlc_entity_am_buffer_status(
+    nr_rlc_entity_t *entity, int maxsize);
+int nr_rlc_entity_am_generate_pdu(nr_rlc_entity_t *entity,
+                                  char *buffer, int size);
+void nr_rlc_entity_am_set_time(nr_rlc_entity_t *entity, uint64_t now);
+void nr_rlc_entity_am_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id);
+void nr_rlc_entity_am_reestablishment(nr_rlc_entity_t *_entity);
+void nr_rlc_entity_am_delete(nr_rlc_entity_t *entity);
+
+#endif /* _NR_RLC_ENTITY_AM_H_ */
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.c b/openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.c
new file mode 100644
index 0000000000..86a0697624
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.c
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "nr_rlc_entity_tm.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "nr_rlc_pdu.h"
+
+#include "LOG/log.h"
+
+/*************************************************************************/
+/* PDU RX functions                                                      */
+/*************************************************************************/
+
+void nr_rlc_entity_tm_recv_pdu(nr_rlc_entity_t *_entity,
+                               char *buffer, int size)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+  entity->common.deliver_sdu(entity->common.deliver_sdu_data,
+                             (nr_rlc_entity_t *)entity,
+                             buffer, size);
+}
+
+/*************************************************************************/
+/* TX functions                                                          */
+/*************************************************************************/
+
+static int generate_tx_pdu(nr_rlc_entity_tm_t *entity, char *buffer, int size)
+{
+  nr_rlc_sdu_segment_t *sdu;
+  int ret;
+
+  if (entity->tx_list == NULL)
+    return 0;
+
+  sdu = entity->tx_list;
+
+  /* not enough room? do nothing */
+  if (sdu->size > size)
+    return 0;
+
+  entity->tx_list = entity->tx_list->next;
+  if (entity->tx_list == NULL)
+    entity->tx_end = NULL;
+
+  ret = sdu->size;
+
+  memcpy(buffer, sdu->sdu->data, sdu->size);
+
+  entity->tx_size -= sdu->size;
+  nr_rlc_free_sdu_segment(sdu);
+
+  return ret;
+}
+
+static int tx_list_size(nr_rlc_entity_tm_t *entity,
+                        nr_rlc_sdu_segment_t *l, int maxsize)
+{
+  int ret = 0;
+
+  while (l != NULL) {
+    ret += l->size;
+    l = l->next;
+  }
+
+  if (ret > maxsize) ret = maxsize;
+  return ret;
+}
+
+nr_rlc_entity_buffer_status_t nr_rlc_entity_tm_buffer_status(
+    nr_rlc_entity_t *_entity, int maxsize)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+  nr_rlc_entity_buffer_status_t ret;
+
+  ret.status_size = 0;
+  ret.tx_size = tx_list_size(entity, entity->tx_list, maxsize);
+  ret.retx_size = 0;
+
+  return ret;
+}
+
+int nr_rlc_entity_tm_generate_pdu(nr_rlc_entity_t *_entity,
+                                  char *buffer, int size)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+
+  return generate_tx_pdu(entity, buffer, size);
+}
+
+/*************************************************************************/
+/* SDU RX functions                                                      */
+/*************************************************************************/
+
+void nr_rlc_entity_tm_recv_sdu(nr_rlc_entity_t *_entity,
+                               char *buffer, int size,
+                               int sdu_id)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+  nr_rlc_sdu_segment_t *sdu;
+
+  if (size > NR_SDU_MAX) {
+    LOG_E(RLC, "%s:%d:%s: fatal: SDU size too big (%d bytes)\n",
+          __FILE__, __LINE__, __FUNCTION__, size);
+    exit(1);
+  }
+
+  if (entity->tx_size + size > entity->tx_maxsize) {
+    LOG_D(RLC, "%s:%d:%s: warning: SDU rejected, SDU buffer full\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    return;
+  }
+
+  entity->tx_size += size;
+
+  sdu = nr_rlc_new_sdu(buffer, size, sdu_id);
+
+  nr_rlc_sdu_segment_list_append(&entity->tx_list, &entity->tx_end, sdu);
+}
+
+/*************************************************************************/
+/* time/timers                                                           */
+/*************************************************************************/
+
+void nr_rlc_entity_tm_set_time(nr_rlc_entity_t *_entity, uint64_t now)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+
+  entity->t_current = now;
+}
+
+/*************************************************************************/
+/* discard/re-establishment/delete                                       */
+/*************************************************************************/
+
+void nr_rlc_entity_tm_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id)
+{
+  /* nothing to do */
+}
+
+static void clear_entity(nr_rlc_entity_tm_t *entity)
+{
+  nr_rlc_free_sdu_segment_list(entity->tx_list);
+
+  entity->tx_list         = NULL;
+  entity->tx_end          = NULL;
+  entity->tx_size         = 0;
+}
+
+void nr_rlc_entity_tm_reestablishment(nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+  clear_entity(entity);
+}
+
+void nr_rlc_entity_tm_delete(nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+  clear_entity(entity);
+  free(entity);
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.h b/openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.h
new file mode 100644
index 0000000000..7c8c7e40a0
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity_tm.h
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _NR_RLC_ENTITY_TM_H_
+#define _NR_RLC_ENTITY_TM_H_
+
+#include "nr_rlc_entity.h"
+#include "nr_rlc_sdu.h"
+
+typedef struct {
+  nr_rlc_entity_t common;
+
+  /* set to the latest know time by the user of the module. Unit: ms */
+  uint64_t t_current;
+
+  /* tx management */
+  nr_rlc_sdu_segment_t *tx_list;
+  nr_rlc_sdu_segment_t *tx_end;
+  int                  tx_size;
+  int                  tx_maxsize;
+} nr_rlc_entity_tm_t;
+
+void nr_rlc_entity_tm_recv_sdu(nr_rlc_entity_t *entity,
+                               char *buffer, int size,
+                               int sdu_id);
+void nr_rlc_entity_tm_recv_pdu(nr_rlc_entity_t *entity,
+                               char *buffer, int size);
+nr_rlc_entity_buffer_status_t nr_rlc_entity_tm_buffer_status(
+    nr_rlc_entity_t *entity, int maxsize);
+int nr_rlc_entity_tm_generate_pdu(nr_rlc_entity_t *entity,
+                                  char *buffer, int size);
+void nr_rlc_entity_tm_set_time(nr_rlc_entity_t *entity, uint64_t now);
+void nr_rlc_entity_tm_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id);
+void nr_rlc_entity_tm_reestablishment(nr_rlc_entity_t *_entity);
+void nr_rlc_entity_tm_delete(nr_rlc_entity_t *entity);
+
+#endif /* _NR_RLC_ENTITY_TM_H_ */
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity_um.c b/openair2/LAYER2/nr_rlc/nr_rlc_entity_um.c
new file mode 100644
index 0000000000..09f29ebe67
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity_um.c
@@ -0,0 +1,697 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "nr_rlc_entity_um.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "nr_rlc_pdu.h"
+
+#include "LOG/log.h"
+
+/*************************************************************************/
+/* PDU RX functions                                                      */
+/*************************************************************************/
+
+static int modulus_rx(nr_rlc_entity_um_t *entity, int a)
+{
+  /* as per 38.322 7.1, modulus base is rx_next_highest - window_size */
+  int r = a - (entity->rx_next_highest - entity->window_size);
+  if (r < 0) r += entity->sn_modulus;
+  return r % entity->sn_modulus;
+}
+
+static int sn_compare_rx(void *_entity, int a, int b)
+{
+  nr_rlc_entity_um_t *entity = _entity;
+  return modulus_rx(entity, a) - modulus_rx(entity, b);
+}
+
+/* checks that all the bytes of the SDU sn have been received (but SDU
+ * has not been already processed)
+ */
+static int sdu_full(nr_rlc_entity_um_t *entity, int sn)
+{
+  nr_rlc_pdu_t *l = entity->rx_list;
+  int last_byte;
+  int new_last_byte;
+
+  last_byte = -1;
+  while (l != NULL) {
+    if (l->sn == sn)
+      break;
+    l = l->next;
+  }
+
+  /* check if the data has already been processed */
+  if (l != NULL && l->data == NULL)
+    return 0;
+
+  while (l != NULL && l->sn == sn) {
+    if (l->so > last_byte + 1)
+      return 0;
+    if (l->is_last)
+      return 1;
+    new_last_byte = l->so + l->size - 1;
+    if (new_last_byte > last_byte)
+      last_byte = new_last_byte;
+    l = l->next;
+  }
+
+  return 0;
+}
+
+/* checks that an SDU has already been delivered */
+static int sdu_delivered(nr_rlc_entity_um_t *entity, int sn)
+{
+  nr_rlc_pdu_t *l = entity->rx_list;
+
+  while (l != NULL) {
+    if (l->sn == sn)
+      break;
+    l = l->next;
+  }
+
+  return l != NULL && l->data == NULL;
+}
+
+/* check if there is some missing bytes before the last received of SDU sn */
+/* todo: be sure that when no byte was received or the SDU has already been
+ *       processed then the SDU has no missing byte
+ */
+static int sdu_has_missing_bytes(nr_rlc_entity_um_t *entity, int sn)
+{
+  nr_rlc_pdu_t *l = entity->rx_list;
+  int last_byte;
+  int new_last_byte;
+
+  last_byte = -1;
+  while (l != NULL) {
+    if (l->sn == sn)
+      break;
+    l = l->next;
+  }
+
+  /* check if the data has already been processed */
+  if (l != NULL && l->data == NULL)
+    return 0;                    /* data already processed: no missing byte */
+
+  while (l != NULL && l->sn == sn) {
+    if (l->so > last_byte + 1)
+      return 1;
+    new_last_byte = l->so + l->size - 1;
+    if (new_last_byte > last_byte)
+      last_byte = new_last_byte;
+    l = l->next;
+  }
+
+  return 0;
+}
+
+static void reassemble_and_deliver(nr_rlc_entity_um_t *entity, int sn)
+{
+  nr_rlc_pdu_t *pdu;
+  char sdu[NR_SDU_MAX];
+  int so = 0;
+  int bad_sdu = 0;
+
+  /* go to first segment of sn */
+  pdu = entity->rx_list;
+  while (pdu->sn != sn)
+    pdu = pdu->next;
+
+  /* reassemble - free 'data' of each segment after processing */
+  while (pdu != NULL && pdu->sn == sn) {
+    int len = pdu->size - (so - pdu->so);
+    if (so + len > NR_SDU_MAX && !bad_sdu) {
+      LOG_E(RLC, "%s:%d:%s: bad SDU, too big, discarding\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      bad_sdu = 1;
+    }
+    if (!bad_sdu && len > 0) {
+      memcpy(sdu + so, pdu->data, len);
+      so += len;
+    }
+    free(pdu->data);
+    pdu->data = NULL;
+    entity->rx_size -= pdu->size;
+    pdu->size = 0;
+    pdu = pdu->next;
+  }
+
+  if (bad_sdu)
+    return;
+
+  /* deliver */
+  entity->common.deliver_sdu(entity->common.deliver_sdu_data,
+                             (nr_rlc_entity_t *)entity,
+                             sdu, so);
+}
+
+static void reception_actions(nr_rlc_entity_um_t *entity, nr_rlc_pdu_t *pdu)
+{
+  int x = pdu->sn;
+
+  if (sdu_full(entity, x)) {
+    /* SDU full */
+    reassemble_and_deliver(entity, x);
+
+    if (x == entity->rx_next_reassembly) {
+      int rx_next_reassembly = entity->rx_next_reassembly;
+      while (sdu_delivered(entity, rx_next_reassembly))
+        rx_next_reassembly = (rx_next_reassembly + 1) % entity->sn_modulus;
+      entity->rx_next_reassembly = rx_next_reassembly;
+    }
+  } else {
+    /* SDU not full */
+    /* test if x is not in reassembly window, that is x >= rx_next_highest */
+    if (sn_compare_rx(entity, x, entity->rx_next_highest) >= 0) {
+      entity->rx_next_highest = (x + 1) % entity->sn_modulus;
+
+      /* discard PDUs not in reassembly window */
+      while (entity->rx_list != NULL &&
+             sn_compare_rx(entity, entity->rx_list->sn,
+                           entity->rx_next_highest) >= 0) {
+        nr_rlc_pdu_t *p = entity->rx_list;
+        entity->rx_size -= p->size;
+        entity->rx_list = p->next;
+        nr_rlc_free_pdu(p);
+      }
+
+      /* if rx_next_reassembly not in reassembly window */
+      if (sn_compare_rx(entity, entity->rx_next_reassembly,
+                        entity->rx_next_highest) >= 0) {
+        int rx_next_reassembly;
+        /* set rx_next_reassembly to first SN >= rx_next_highest - window_size
+         * not delivered yet
+         */
+        rx_next_reassembly = (entity->rx_next_highest - entity->window_size
+                                 + entity->sn_modulus) % entity->sn_modulus;
+        while (sdu_delivered(entity, rx_next_reassembly))
+          rx_next_reassembly = (rx_next_reassembly + 1) % entity->sn_modulus;
+        entity->rx_next_reassembly = rx_next_reassembly;
+      }
+    }
+  }
+
+  if (entity->t_reassembly_start) {
+    if (/* rx_timer_trigger <= rx_next_reassembly */
+        sn_compare_rx(entity, entity->rx_timer_trigger,
+                      entity->rx_next_reassembly) <= 0 ||
+        /* or rx_timer_trigger outside of reassembly window and not equal
+         * to rx_next_highest, that is is > rx_next_highest
+         */
+        sn_compare_rx(entity, entity->rx_timer_trigger,
+                      entity->rx_next_highest) > 0 ||
+        /* or rx_next_highest == rx_next_reassembly + 1 and no missing byte
+         * for rx_next_reassembly
+         */
+       (entity->rx_next_highest == (entity->rx_next_reassembly + 1) %
+           entity->sn_modulus &&
+        !sdu_has_missing_bytes(entity, entity->rx_next_reassembly)))
+      entity->t_reassembly_start = 0;
+  }
+
+  if (entity->t_reassembly_start == 0) {
+    if (sn_compare_rx(entity, entity->rx_next_highest,
+                      (entity->rx_next_reassembly + 1)
+                          % entity->sn_modulus) > 0 ||
+        (entity->rx_next_highest == (entity->rx_next_reassembly + 1)
+                                        % entity->sn_modulus &&
+         sdu_has_missing_bytes(entity, entity->rx_next_reassembly))) {
+      entity->t_reassembly_start = entity->t_current;
+      entity->rx_timer_trigger = entity->rx_next_highest;
+    }
+  }
+}
+
+void nr_rlc_entity_um_recv_pdu(nr_rlc_entity_t *_entity,
+                               char *buffer, int size)
+{
+#define R(d) do { if (nr_rlc_pdu_decoder_in_error(&d)) goto err; } while (0)
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  nr_rlc_pdu_decoder_t decoder;
+  nr_rlc_pdu_t *pdu;
+  int si;
+  int sn;
+  int so = 0;
+  int data_size;
+  int is_first;
+  int is_last;
+
+  nr_rlc_pdu_decoder_init(&decoder, buffer, size);
+
+  si = nr_rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
+
+  is_first = (si & 0x2) == 0;
+  is_last = (si & 0x1) == 0;
+
+  /* if full, deliver SDU */
+  if (is_first && is_last) {
+    if (size < 2) {
+      LOG_E(RLC, "%s:%d:%s: warning: discard PDU, no data\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      goto discard;
+    }
+    /* deliver */
+    entity->common.deliver_sdu(entity->common.deliver_sdu_data,
+                               (nr_rlc_entity_t *)entity,
+                               buffer + 1, size - 1);
+    return;
+  }
+
+  if (entity->sn_field_length == 12) {
+    nr_rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
+  }
+
+  sn = nr_rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length);
+  R(decoder);
+
+  if (!is_first) {
+    so = nr_rlc_pdu_decoder_get_bits(&decoder, 16); R(decoder);
+    if (so == 0) {
+      LOG_E(RLC, "%s:%d:%s: warning: discard PDU, bad so\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      goto discard;
+    }
+  }
+
+  data_size = size - decoder.byte;
+
+  /* dicard PDU if no data */
+  if (data_size <= 0) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, no data\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    goto discard;
+  }
+
+  /* dicard PDU if rx buffer is full */
+  if (entity->rx_size + data_size > entity->rx_maxsize) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, RX buffer full\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    goto discard;
+  }
+
+  /* discard PDU if sn < rx_next_reassembly */
+  if (sn_compare_rx(entity, sn, entity->rx_next_reassembly) < 0) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, SN (%d) < rx_next_reassembly (%d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+          sn, entity->rx_next_reassembly);
+    goto discard;
+  }
+
+  /* put in pdu reception list */
+  entity->rx_size += data_size;
+  pdu = nr_rlc_new_pdu(sn, so, is_first, is_last,
+                       buffer + size - data_size, data_size);
+  entity->rx_list = nr_rlc_pdu_list_add(sn_compare_rx, entity,
+                                        entity->rx_list, pdu);
+
+  /* do reception actions (38.322 5.2.2.2.3) */
+  reception_actions(entity, pdu);
+
+  return;
+
+err:
+  LOG_W(RLC, "%s:%d:%s: error decoding PDU, discarding\n", __FILE__, __LINE__, __FUNCTION__);
+  goto discard;
+
+discard:
+  return;
+
+#undef R
+}
+
+/*************************************************************************/
+/* TX functions                                                          */
+/*************************************************************************/
+
+static int serialize_sdu(nr_rlc_entity_um_t *entity,
+                         nr_rlc_sdu_segment_t *sdu, char *buffer, int bufsize)
+{
+  nr_rlc_pdu_encoder_t encoder;
+
+  /* generate header */
+  nr_rlc_pdu_encoder_init(&encoder, buffer, bufsize);
+
+  nr_rlc_pdu_encoder_put_bits(&encoder, 1-sdu->is_first,1);/* 1st bit of SI */
+  nr_rlc_pdu_encoder_put_bits(&encoder, 1-sdu->is_last,1); /* 2nd bit of SI */
+
+  /* SN, if required */
+  if (sdu->is_first == 1 && sdu->is_last == 1) {
+    nr_rlc_pdu_encoder_put_bits(&encoder, 0, 6);                       /* R */
+  } else {
+    if (entity->sn_field_length == 12)
+      nr_rlc_pdu_encoder_put_bits(&encoder, 0, 2);                     /* R */
+    nr_rlc_pdu_encoder_put_bits(&encoder, sdu->sdu->sn,
+                                entity->sn_field_length);             /* SN */
+  }
+
+  if (!sdu->is_first)
+    nr_rlc_pdu_encoder_put_bits(&encoder, sdu->so, 16);               /* SO */
+
+  /* data */
+  memcpy(buffer + encoder.byte, sdu->sdu->data + sdu->so, sdu->size);
+
+  return encoder.byte + sdu->size;
+}
+
+/* for a given SDU/SDU segment, computes the corresponding PDU header size */
+static int compute_pdu_header_size(nr_rlc_entity_um_t *entity,
+                                   nr_rlc_sdu_segment_t *sdu)
+{
+  int header_size = 1;
+
+  /* if SN to be included then one more byte if SN field length is 12 */
+  if (!(sdu->is_first && sdu->is_last) && entity->sn_field_length == 12)
+    header_size++;
+  /* two more bytes for SO if SDU segment is not the first */
+  if (!sdu->is_first) header_size += 2;
+  return header_size;
+}
+
+/* resize SDU/SDU segment for the corresponding PDU to fit into 'pdu_size'
+ * bytes
+ * - modifies SDU/SDU segment to become an SDU segment
+ * - returns a new SDU segment covering the remaining data bytes
+ * returns NULL if pdu_size is too small to contain the new segment
+ */
+static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu,
+                                       nr_rlc_entity_um_t *entity,
+                                       int pdu_size)
+{
+  nr_rlc_sdu_segment_t *next;
+  int pdu_header_size;
+  int over_size;
+  int old_is_last;
+
+  sdu->sdu->ref_count++;
+
+  /* clear is_last to compute header size */
+  old_is_last = sdu->is_last;
+  sdu->is_last = 0;
+  pdu_header_size = compute_pdu_header_size(entity, sdu);
+  sdu->is_last = old_is_last;
+
+  /* if no room for at least 1 data byte, do nothing */
+  if (pdu_header_size + 1 > pdu_size)
+    return NULL;
+
+  next = calloc(1, sizeof(nr_rlc_sdu_segment_t));
+  if (next == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__,  __FUNCTION__);
+    exit(1);
+  }
+  *next = *sdu;
+
+  over_size = pdu_header_size + sdu->size - pdu_size;
+
+  /* update SDU */
+  sdu->size -= over_size;
+  sdu->is_last = 0;
+
+  /* create new segment */
+  next->size = over_size;
+  next->so = sdu->so + sdu->size;
+  next->is_first = 0;
+
+  return next;
+}
+
+static int generate_tx_pdu(nr_rlc_entity_um_t *entity, char *buffer, int size)
+{
+  nr_rlc_sdu_segment_t *sdu;
+  int pdu_header_size;
+  int pdu_size;
+  int ret;
+
+  if (entity->tx_list == NULL)
+    return 0;
+
+  sdu = entity->tx_list;
+
+  pdu_header_size = compute_pdu_header_size(entity, sdu);
+
+  /* not enough room for at least one byte of data? do nothing */
+  if (pdu_header_size + 1 > size)
+    return 0;
+
+  entity->tx_list = entity->tx_list->next;
+  if (entity->tx_list == NULL)
+    entity->tx_end = NULL;
+
+  /* assign SN to SDU */
+  sdu->sdu->sn = entity->tx_next;
+
+  /* segment if necessary */
+  pdu_size = pdu_header_size + sdu->size;
+  if (pdu_size > size) {
+    nr_rlc_sdu_segment_t *next_sdu;
+    next_sdu = resegment(sdu, entity, size);
+    if (next_sdu == NULL)
+      return 0;
+    /* put the second SDU back at the head of the TX list */
+    next_sdu->next = entity->tx_list;
+    entity->tx_list = next_sdu;
+    if (entity->tx_end == NULL)
+      entity->tx_end = entity->tx_list;
+  }
+
+  /* update tx_next if the SDU is an SDU segment and is the last */
+  if (!sdu->is_first && sdu->is_last)
+    entity->tx_next = (entity->tx_next + 1) % entity->sn_modulus;
+
+  ret = serialize_sdu(entity, sdu, buffer, size);
+
+  entity->tx_size -= sdu->size;
+  nr_rlc_free_sdu_segment(sdu);
+
+  return ret;
+}
+
+/* Pretend to serialize all the SDUs in a list and return the size
+ * of all the PDUs it would produce, limited to 'maxsize'.
+ * Used for buffer status reporting.
+ */
+static int tx_list_size(nr_rlc_entity_um_t *entity,
+                        nr_rlc_sdu_segment_t *l, int maxsize)
+{
+  int ret = 0;
+
+  while (l != NULL) {
+    ret += compute_pdu_header_size(entity, l) + l->size;
+    l = l->next;
+  }
+
+  if (ret > maxsize) ret = maxsize;
+  return ret;
+}
+
+nr_rlc_entity_buffer_status_t nr_rlc_entity_um_buffer_status(
+    nr_rlc_entity_t *_entity, int maxsize)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  nr_rlc_entity_buffer_status_t ret;
+
+  ret.status_size = 0;
+  ret.tx_size = tx_list_size(entity, entity->tx_list, maxsize);
+  ret.retx_size = 0;
+
+  return ret;
+}
+
+int nr_rlc_entity_um_generate_pdu(nr_rlc_entity_t *_entity,
+                                  char *buffer, int size)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+
+  return generate_tx_pdu(entity, buffer, size);
+}
+
+/*************************************************************************/
+/* SDU RX functions                                                      */
+/*************************************************************************/
+
+void nr_rlc_entity_um_recv_sdu(nr_rlc_entity_t *_entity,
+                               char *buffer, int size,
+                               int sdu_id)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  nr_rlc_sdu_segment_t *sdu;
+
+  if (size > NR_SDU_MAX) {
+    LOG_E(RLC, "%s:%d:%s: fatal: SDU size too big (%d bytes)\n",
+          __FILE__, __LINE__, __FUNCTION__, size);
+    exit(1);
+  }
+
+  if (entity->tx_size + size > entity->tx_maxsize) {
+    LOG_D(RLC, "%s:%d:%s: warning: SDU rejected, SDU buffer full\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    return;
+  }
+
+  entity->tx_size += size;
+
+  sdu = nr_rlc_new_sdu(buffer, size, sdu_id);
+
+  nr_rlc_sdu_segment_list_append(&entity->tx_list, &entity->tx_end, sdu);
+}
+
+/*************************************************************************/
+/* time/timers                                                           */
+/*************************************************************************/
+
+static void check_t_reassembly(nr_rlc_entity_um_t *entity)
+{
+  nr_rlc_pdu_t *cur;
+
+  /* is t_reassembly running and if yes has it expired? */
+  if (entity->t_reassembly_start == 0 ||
+      entity->t_current <= entity->t_reassembly_start + entity->t_reassembly)
+    return;
+
+  /* stop timer */
+  entity->t_reassembly_start = 0;
+
+  LOG_D(RLC, "%s:%d:%s: t_reassembly expired\n",
+        __FILE__, __LINE__, __FUNCTION__);
+
+  /* update rx_next_reassembly to first SN >= rx_timer_trigger not reassembled
+   * (ie. not delivered yet)
+   */
+  entity->rx_next_reassembly = entity->rx_timer_trigger;
+  while (sdu_delivered(entity, entity->rx_next_reassembly))
+    entity->rx_next_reassembly = (entity->rx_next_reassembly + 1)
+                                     % entity->sn_modulus;
+
+  /* discard all segments < entity->rx_next_reassembly */
+  cur = entity->rx_list;
+  while (cur != NULL &&
+         sn_compare_rx(entity, cur->sn, entity->rx_next_reassembly) < 0) {
+    nr_rlc_free_pdu(cur);
+    cur = cur->next;
+    entity->rx_list = cur;
+  }
+
+  if (sn_compare_rx(entity, entity->rx_next_highest,
+                    (entity->rx_next_reassembly + 1)
+                        % entity->sn_modulus) > 0 ||
+      (entity->rx_next_highest == entity->rx_next_reassembly + 1 &&
+       sdu_has_missing_bytes(entity, entity->rx_next_reassembly))) {
+    entity->t_reassembly_start = entity->t_current;
+    entity->rx_timer_trigger = entity->rx_next_highest;
+  }
+}
+
+void nr_rlc_entity_um_set_time(nr_rlc_entity_t *_entity, uint64_t now)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+
+  entity->t_current = now;
+
+  check_t_reassembly(entity);
+}
+
+/*************************************************************************/
+/* discard/re-establishment/delete                                       */
+/*************************************************************************/
+
+void nr_rlc_entity_um_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id)
+{
+  /* implements 38.322 5.4 */
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  nr_rlc_sdu_segment_t head;
+  nr_rlc_sdu_segment_t *cur;
+  nr_rlc_sdu_segment_t *prev;
+
+  head.next = entity->tx_list;
+  cur = entity->tx_list;
+  prev = &head;
+
+  while (cur != NULL && cur->sdu->upper_layer_id != sdu_id) {
+    prev = cur;
+    cur = cur->next;
+  }
+
+  /* if sdu_id not found or some bytes have already been 'PDU-ized'
+   * then do nothing
+   */
+  if (cur == NULL || !cur->is_first || !cur->is_last)
+    return;
+
+  /* remove SDU from tx_list */
+  prev->next = cur->next;
+  entity->tx_list = head.next;
+  if (entity->tx_end == cur) {
+    if (prev != &head)
+      entity->tx_end = prev;
+    else
+      entity->tx_end = NULL;
+  }
+
+  nr_rlc_free_sdu_segment(cur);
+}
+
+static void clear_entity(nr_rlc_entity_um_t *entity)
+{
+  nr_rlc_pdu_t *cur_rx;
+
+  entity->rx_next_highest    = 0;
+  entity->rx_next_reassembly = 0;
+  entity->rx_timer_trigger   = 0;
+
+
+  entity->tx_next           = 0;
+
+  entity->t_current = 0;
+
+  entity->t_reassembly_start      = 0;
+
+  cur_rx = entity->rx_list;
+  while (cur_rx != NULL) {
+    nr_rlc_pdu_t *p = cur_rx;
+    cur_rx = cur_rx->next;
+    nr_rlc_free_pdu(p);
+  }
+  entity->rx_list = NULL;
+  entity->rx_size = 0;
+
+  nr_rlc_free_sdu_segment_list(entity->tx_list);
+
+  entity->tx_list         = NULL;
+  entity->tx_end          = NULL;
+  entity->tx_size         = 0;
+}
+
+void nr_rlc_entity_um_reestablishment(nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  clear_entity(entity);
+}
+
+void nr_rlc_entity_um_delete(nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  clear_entity(entity);
+  free(entity);
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity_um.h b/openair2/LAYER2/nr_rlc/nr_rlc_entity_um.h
new file mode 100644
index 0000000000..21063a1ea1
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity_um.h
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _NR_RLC_ENTITY_UM_H_
+#define _NR_RLC_ENTITY_UM_H_
+
+#include "nr_rlc_entity.h"
+#include "nr_rlc_sdu.h"
+#include "nr_rlc_pdu.h"
+
+typedef struct {
+  nr_rlc_entity_t common;
+
+  /* configuration */
+  int t_reassembly;
+  int sn_field_length;
+
+  int sn_modulus;
+  int window_size;
+
+  /* runtime rx */
+  int rx_next_highest;
+  int rx_next_reassembly;
+  int rx_timer_trigger;
+
+  /* runtime tx */
+  int tx_next;
+
+  /* set to the latest know time by the user of the module. Unit: ms */
+  uint64_t t_current;
+
+  /* timers (stores the TTI of activation, 0 means not active) */
+  uint64_t t_reassembly_start;
+
+  /* rx management */
+  nr_rlc_pdu_t *rx_list;
+  int          rx_size;
+  int          rx_maxsize;
+
+  /* tx management */
+  nr_rlc_sdu_segment_t *tx_list;
+  nr_rlc_sdu_segment_t *tx_end;
+  int                  tx_size;
+  int                  tx_maxsize;
+} nr_rlc_entity_um_t;
+
+void nr_rlc_entity_um_recv_sdu(nr_rlc_entity_t *entity,
+                               char *buffer, int size,
+                               int sdu_id);
+void nr_rlc_entity_um_recv_pdu(nr_rlc_entity_t *entity,
+                               char *buffer, int size);
+nr_rlc_entity_buffer_status_t nr_rlc_entity_um_buffer_status(
+    nr_rlc_entity_t *entity, int maxsize);
+int nr_rlc_entity_um_generate_pdu(nr_rlc_entity_t *entity,
+                                  char *buffer, int size);
+void nr_rlc_entity_um_set_time(nr_rlc_entity_t *entity, uint64_t now);
+void nr_rlc_entity_um_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id);
+void nr_rlc_entity_um_reestablishment(nr_rlc_entity_t *_entity);
+void nr_rlc_entity_um_delete(nr_rlc_entity_t *entity);
+
+#endif /* _NR_RLC_ENTITY_UM_H_ */
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c b/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c
new file mode 100644
index 0000000000..b56e8b11e9
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c
@@ -0,0 +1,874 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+/* from openair */
+#include "rlc.h"
+#include "pdcp.h"
+
+/* from nr rlc module */
+#include "asn1_utils.h"
+#include "nr_rlc_ue_manager.h"
+#include "nr_rlc_entity.h"
+
+#include <stdint.h>
+
+static nr_rlc_ue_manager_t *nr_rlc_ue_manager;
+
+/* TODO: handle time a bit more properly */
+static uint64_t nr_rlc_current_time;
+static int      nr_rlc_current_time_last_frame;
+static int      nr_rlc_current_time_last_subframe;
+
+void mac_rlc_data_ind     (
+  const module_id_t         module_idP,
+  const rnti_t              rntiP,
+  const eNB_index_t         eNB_index,
+  const frame_t             frameP,
+  const eNB_flag_t          enb_flagP,
+  const MBMS_flag_t         MBMS_flagP,
+  const logical_chan_id_t   channel_idP,
+  char                     *buffer_pP,
+  const tb_size_t           tb_sizeP,
+  num_tb_t                  num_tbP,
+  crc_t                    *crcs_pP)
+{
+  nr_rlc_ue_t *ue;
+  nr_rlc_entity_t *rb;
+
+  if (module_idP != 0 || eNB_index != 0 || /*enb_flagP != 1 ||*/ MBMS_flagP != 0) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  if (enb_flagP)
+    T(T_ENB_RLC_MAC_UL, T_INT(module_idP), T_INT(rntiP),
+      T_INT(channel_idP), T_INT(tb_sizeP));
+
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, rntiP);
+
+  switch (channel_idP) {
+  case 1 ... 2: rb = ue->srb[channel_idP - 1]; break;
+  case 3 ... 7: rb = ue->drb[channel_idP - 3]; break;
+  default:      rb = NULL;                     break;
+  }
+
+  if (rb != NULL) {
+    rb->set_time(rb, nr_rlc_current_time);
+    rb->recv_pdu(rb, buffer_pP, tb_sizeP);
+  } else {
+    LOG_E(RLC, "%s:%d:%s: fatal: no RB found (channel ID %d)\n",
+          __FILE__, __LINE__, __FUNCTION__, channel_idP);
+    exit(1);
+  }
+
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+}
+
+tbs_size_t mac_rlc_data_req(
+  const module_id_t       module_idP,
+  const rnti_t            rntiP,
+  const eNB_index_t       eNB_index,
+  const frame_t           frameP,
+  const eNB_flag_t        enb_flagP,
+  const MBMS_flag_t       MBMS_flagP,
+  const logical_chan_id_t channel_idP,
+  const tb_size_t         tb_sizeP,
+  char             *buffer_pP
+#if (LTE_RRC_VERSION >= MAKE_VERSION(14, 0, 0))
+  ,const uint32_t sourceL2Id
+  ,const uint32_t destinationL2Id
+#endif
+   )
+{
+  int ret;
+  nr_rlc_ue_t *ue;
+  nr_rlc_entity_t *rb;
+  int is_enb;
+  int maxsize;
+
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, rntiP);
+
+  switch (channel_idP) {
+  case 1 ... 2: rb = ue->srb[channel_idP - 1]; break;
+  case 3 ... 7: rb = ue->drb[channel_idP - 3]; break;
+  default:      rb = NULL;                     break;
+  }
+
+  if (rb != NULL) {
+    rb->set_time(rb, nr_rlc_current_time);
+    /* UE does not seem to use saved_status_ind_tb_size */
+    is_enb = nr_rlc_manager_get_enb_flag(nr_rlc_ue_manager);
+    if (is_enb)
+      maxsize = ue->saved_status_ind_tb_size[channel_idP - 1];
+    else
+      maxsize = tb_sizeP;
+    ret = rb->generate_pdu(rb, buffer_pP, maxsize);
+  } else {
+    LOG_E(RLC, "%s:%d:%s: fatal: data req for unknown RB\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+    ret = 0;
+  }
+
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+
+  if (enb_flagP)
+    T(T_ENB_RLC_MAC_DL, T_INT(module_idP), T_INT(rntiP),
+      T_INT(channel_idP), T_INT(ret));
+
+  return ret;
+}
+
+mac_rlc_status_resp_t mac_rlc_status_ind(
+  const module_id_t       module_idP,
+  const rnti_t            rntiP,
+  const eNB_index_t       eNB_index,
+  const frame_t           frameP,
+  const sub_frame_t       subframeP,
+  const eNB_flag_t        enb_flagP,
+  const MBMS_flag_t       MBMS_flagP,
+  const logical_chan_id_t channel_idP,
+  const tb_size_t         tb_sizeP
+#if (LTE_RRC_VERSION >= MAKE_VERSION(14, 0, 0))
+  ,const uint32_t sourceL2Id
+  ,const uint32_t destinationL2Id
+#endif
+  )
+{
+  nr_rlc_ue_t *ue;
+  mac_rlc_status_resp_t ret;
+  nr_rlc_entity_t *rb;
+
+  /* TODO: handle time a bit more properly */
+  if (nr_rlc_current_time_last_frame != frameP ||
+      nr_rlc_current_time_last_subframe != subframeP) {
+    nr_rlc_current_time++;
+    nr_rlc_current_time_last_frame = frameP;
+    nr_rlc_current_time_last_subframe = subframeP;
+  }
+
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, rntiP);
+
+  switch (channel_idP) {
+  case 1 ... 2: rb = ue->srb[channel_idP - 1]; break;
+  case 3 ... 7: rb = ue->drb[channel_idP - 3]; break;
+  default:      rb = NULL;                     break;
+  }
+
+  if (rb != NULL) {
+    nr_rlc_entity_buffer_status_t buf_stat;
+    rb->set_time(rb, nr_rlc_current_time);
+    /* 36.321 deals with BSR values up to 3000000 bytes, after what it
+     * reports '> 3000000' (table 6.1.3.1-2). Passing 4000000 is thus
+     * more than enough.
+     */
+    buf_stat = rb->buffer_status(rb, 4000000);
+    ret.bytes_in_buffer = buf_stat.status_size
+                        + buf_stat.retx_size
+                        + buf_stat.tx_size;
+    ue->saved_status_ind_tb_size[channel_idP - 1] = tb_sizeP;
+  } else {
+    ret.bytes_in_buffer = 0;
+  }
+
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+
+  ret.pdus_in_buffer = 0;
+  /* TODO: creation time may be important (unit: frame, as it seems) */
+  ret.head_sdu_creation_time = 0;
+  ret.head_sdu_remaining_size_to_send = 0;
+  ret.head_sdu_is_segmented = 0;
+  return ret;
+}
+
+int oai_emulation;
+
+rlc_op_status_t rlc_data_req     (const protocol_ctxt_t *const ctxt_pP,
+                                  const srb_flag_t   srb_flagP,
+                                  const MBMS_flag_t  MBMS_flagP,
+                                  const rb_id_t      rb_idP,
+                                  const mui_t        muiP,
+                                  confirm_t    confirmP,
+                                  sdu_size_t   sdu_sizeP,
+                                  mem_block_t *sdu_pP
+#if (LTE_RRC_VERSION >= MAKE_VERSION(14, 0, 0))
+  ,const uint32_t *const sourceL2Id
+  ,const uint32_t *const destinationL2Id
+#endif
+                                 )
+{
+  int rnti = ctxt_pP->rnti;
+  nr_rlc_ue_t *ue;
+  nr_rlc_entity_t *rb;
+
+  LOG_D(RLC, "%s rnti %d srb_flag %d rb_id %d mui %d confirm %d sdu_size %d MBMS_flag %d\n",
+        __FUNCTION__, rnti, srb_flagP, rb_idP, muiP, confirmP, sdu_sizeP,
+        MBMS_flagP);
+
+  if (ctxt_pP->enb_flag)
+    T(T_ENB_RLC_DL, T_INT(ctxt_pP->module_id),
+      T_INT(ctxt_pP->rnti), T_INT(rb_idP), T_INT(sdu_sizeP));
+
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, rnti);
+
+  rb = NULL;
+
+  if (srb_flagP) {
+    if (rb_idP >= 1 && rb_idP <= 2)
+      rb = ue->srb[rb_idP - 1];
+  } else {
+    if (rb_idP >= 1 && rb_idP <= 5)
+      rb = ue->drb[rb_idP - 1];
+  }
+
+  if (rb != NULL) {
+    rb->set_time(rb, nr_rlc_current_time);
+    rb->recv_sdu(rb, (char *)sdu_pP->data, sdu_sizeP, muiP);
+  } else {
+    LOG_E(RLC, "%s:%d:%s: fatal: SDU sent to unknown RB\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+
+  free_mem_block(sdu_pP, __func__);
+
+  return RLC_OP_STATUS_OK;
+}
+
+int rlc_module_init(int enb_flag)
+{
+  static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+  static int inited = 0;
+
+  if (pthread_mutex_lock(&lock)) abort();
+
+  if (inited) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  inited = 1;
+
+  nr_rlc_ue_manager = new_nr_rlc_ue_manager(enb_flag);
+
+  if (pthread_mutex_unlock(&lock)) abort();
+
+  return 0;
+}
+
+void rlc_util_print_hex_octets(comp_name_t componentP, unsigned char *dataP, const signed long sizeP)
+{
+}
+
+static void deliver_sdu(void *_ue, nr_rlc_entity_t *entity, char *buf, int size)
+{
+  nr_rlc_ue_t *ue = _ue;
+  int is_srb;
+  int rb_id;
+  protocol_ctxt_t ctx;
+  mem_block_t *memblock;
+  int i;
+  int is_enb;
+
+  /* is it SRB? */
+  for (i = 0; i < 2; i++) {
+    if (entity == ue->srb[i]) {
+      is_srb = 1;
+      rb_id = i+1;
+      goto rb_found;
+    }
+  }
+
+  /* maybe DRB? */
+  for (i = 0; i < 5; i++) {
+    if (entity == ue->drb[i]) {
+      is_srb = 0;
+      rb_id = i+1;
+      goto rb_found;
+    }
+  }
+
+  LOG_E(RLC, "%s:%d:%s: fatal, no RB found for ue %d\n",
+        __FILE__, __LINE__, __FUNCTION__, ue->rnti);
+  exit(1);
+
+rb_found:
+  LOG_D(RLC, "%s:%d:%s: delivering SDU (rnti %d is_srb %d rb_id %d) size %d",
+        __FILE__, __LINE__, __FUNCTION__, ue->rnti, is_srb, rb_id, size);
+
+  memblock = get_free_mem_block(size, __func__);
+  if (memblock == NULL) {
+    LOG_E(RLC, "%s:%d:%s: ERROR: get_free_mem_block failed\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  memcpy(memblock->data, buf, size);
+
+  /* unused fields? */
+  ctx.instance = 0;
+  ctx.frame = 0;
+  ctx.subframe = 0;
+  ctx.eNB_index = 0;
+  ctx.configured = 1;
+  ctx.brOption = 0;
+
+  /* used fields? */
+  ctx.module_id = 0;
+  ctx.rnti = ue->rnti;
+
+  is_enb = nr_rlc_manager_get_enb_flag(nr_rlc_ue_manager);
+  ctx.enb_flag = is_enb;
+
+  if (is_enb) {
+    T(T_ENB_RLC_UL,
+      T_INT(0 /*ctxt_pP->module_id*/),
+      T_INT(ue->rnti), T_INT(rb_id), T_INT(size));
+  }
+
+  if (!pdcp_data_ind(&ctx, is_srb, 0, rb_id, size, memblock)) {
+    LOG_E(RLC, "%s:%d:%s: ERROR: pdcp_data_ind failed\n", __FILE__, __LINE__, __FUNCTION__);
+    /* what to do in case of failure? for the moment: nothing */
+  }
+}
+
+static void successful_delivery(void *_ue, nr_rlc_entity_t *entity, int sdu_id)
+{
+  nr_rlc_ue_t *ue = _ue;
+  int i;
+  int is_srb;
+  int rb_id;
+#if 0
+  MessageDef *msg;
+#endif
+  int is_enb;
+
+  /* is it SRB? */
+  for (i = 0; i < 2; i++) {
+    if (entity == ue->srb[i]) {
+      is_srb = 1;
+      rb_id = i+1;
+      goto rb_found;
+    }
+  }
+
+  /* maybe DRB? */
+  for (i = 0; i < 5; i++) {
+    if (entity == ue->drb[i]) {
+      is_srb = 0;
+      rb_id = i+1;
+      goto rb_found;
+    }
+  }
+
+  LOG_E(RLC, "%s:%d:%s: fatal, no RB found for ue %d\n",
+        __FILE__, __LINE__, __FUNCTION__, ue->rnti);
+  exit(1);
+
+rb_found:
+  LOG_D(RLC, "sdu %d was successfully delivered on %s %d\n",
+        sdu_id,
+        is_srb ? "SRB" : "DRB",
+        rb_id);
+
+  /* TODO: do something for DRBs? */
+  if (is_srb == 0)
+    return;
+
+  is_enb = nr_rlc_manager_get_enb_flag(nr_rlc_ue_manager);
+  if (!is_enb)
+    return;
+
+#if 0
+  msg = itti_alloc_new_message(TASK_RLC_ENB, RLC_SDU_INDICATION);
+  RLC_SDU_INDICATION(msg).rnti          = ue->rnti;
+  RLC_SDU_INDICATION(msg).is_successful = 1;
+  RLC_SDU_INDICATION(msg).srb_id        = rb_id;
+  RLC_SDU_INDICATION(msg).message_id    = sdu_id;
+  /* TODO: accept more than 1 instance? here we send to instance id 0 */
+  itti_send_msg_to_task(TASK_RRC_ENB, 0, msg);
+#endif
+}
+
+static void max_retx_reached(void *_ue, nr_rlc_entity_t *entity)
+{
+  nr_rlc_ue_t *ue = _ue;
+  int i;
+  int is_srb;
+  int rb_id;
+#if 0
+  MessageDef *msg;
+#endif
+  int is_enb;
+
+  /* is it SRB? */
+  for (i = 0; i < 2; i++) {
+    if (entity == ue->srb[i]) {
+      is_srb = 1;
+      rb_id = i+1;
+      goto rb_found;
+    }
+  }
+
+  /* maybe DRB? */
+  for (i = 0; i < 5; i++) {
+    if (entity == ue->drb[i]) {
+      is_srb = 0;
+      rb_id = i+1;
+      goto rb_found;
+    }
+  }
+
+  LOG_E(RLC, "%s:%d:%s: fatal, no RB found for ue %d\n",
+        __FILE__, __LINE__, __FUNCTION__, ue->rnti);
+  exit(1);
+
+rb_found:
+  LOG_D(RLC, "max RETX reached on %s %d\n",
+        is_srb ? "SRB" : "DRB",
+        rb_id);
+
+  /* TODO: do something for DRBs? */
+  if (is_srb == 0)
+    return;
+
+  is_enb = nr_rlc_manager_get_enb_flag(nr_rlc_ue_manager);
+  if (!is_enb)
+    return;
+
+#if 0
+  msg = itti_alloc_new_message(TASK_RLC_ENB, RLC_SDU_INDICATION);
+  RLC_SDU_INDICATION(msg).rnti          = ue->rnti;
+  RLC_SDU_INDICATION(msg).is_successful = 0;
+  RLC_SDU_INDICATION(msg).srb_id        = rb_id;
+  RLC_SDU_INDICATION(msg).message_id    = -1;
+  /* TODO: accept more than 1 instance? here we send to instance id 0 */
+  itti_send_msg_to_task(TASK_RRC_ENB, 0, msg);
+#endif
+}
+
+static void add_srb(int rnti, struct LTE_SRB_ToAddMod *s)
+{
+  nr_rlc_entity_t            *nr_rlc_am;
+  nr_rlc_ue_t                *ue;
+
+  struct LTE_SRB_ToAddMod__rlc_Config *r = s->rlc_Config;
+  struct LTE_SRB_ToAddMod__logicalChannelConfig *l = s->logicalChannelConfig;
+  int srb_id = s->srb_Identity;
+  int logical_channel_group;
+
+  //int t_reordering;
+  int t_status_prohibit;
+  int t_poll_retransmit;
+  int poll_pdu;
+  int poll_byte;
+  int max_retx_threshold;
+  int t_reassembly;
+  int sn_field_length;
+
+  if (srb_id != 1 && srb_id != 2) {
+    LOG_E(RLC, "%s:%d:%s: fatal, bad srb id %d\n",
+          __FILE__, __LINE__, __FUNCTION__, srb_id);
+    exit(1);
+  }
+
+  switch (l->present) {
+  case LTE_SRB_ToAddMod__logicalChannelConfig_PR_explicitValue:
+    logical_channel_group = *l->choice.explicitValue.ul_SpecificParameters->logicalChannelGroup;
+    break;
+  case LTE_SRB_ToAddMod__logicalChannelConfig_PR_defaultValue:
+    /* default value from 36.331 9.2.1 */
+    logical_channel_group = 0;
+    break;
+  default:
+    LOG_E(RLC, "%s:%d:%s: fatal error\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  /* TODO: accept other values? */
+  if (logical_channel_group != 0) {
+    LOG_E(RLC, "%s:%d:%s: fatal error\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  switch (r->present) {
+  case LTE_SRB_ToAddMod__rlc_Config_PR_explicitValue: {
+    struct LTE_RLC_Config__am *am;
+    if (r->choice.explicitValue.present != LTE_RLC_Config_PR_am) {
+      LOG_E(RLC, "%s:%d:%s: fatal error, must be RLC AM\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      exit(1);
+    }
+    am = &r->choice.explicitValue.choice.am;
+    //t_reordering       = decode_t_reordering(am->dl_AM_RLC.t_Reordering);
+    t_status_prohibit  = decode_t_status_prohibit(am->dl_AM_RLC.t_StatusProhibit);
+    t_poll_retransmit  = decode_t_poll_retransmit(am->ul_AM_RLC.t_PollRetransmit);
+    poll_pdu           = decode_poll_pdu(am->ul_AM_RLC.pollPDU);
+    poll_byte          = decode_poll_byte(am->ul_AM_RLC.pollByte);
+    max_retx_threshold = decode_max_retx_threshold(am->ul_AM_RLC.maxRetxThreshold);
+    break;
+  }
+  case LTE_SRB_ToAddMod__rlc_Config_PR_defaultValue:
+    /* default values from 36.331 9.2.1 */
+    //t_reordering       = 35;
+    t_status_prohibit  = 0;
+    t_poll_retransmit  = 45;
+    poll_pdu           = -1;
+    poll_byte          = -1;
+    max_retx_threshold = 4;
+    break;
+  default:
+    LOG_E(RLC, "%s:%d:%s: fatal error\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, rnti);
+  if (ue->srb[srb_id-1] != NULL) {
+    LOG_D(RLC, "%s:%d:%s: warning SRB %d already exist for ue %d, do nothing\n",
+          __FILE__, __LINE__, __FUNCTION__, srb_id, rnti);
+  } else {
+    /* hack: hardcode values for NR */
+    t_poll_retransmit = 45;
+    t_reassembly = 35;
+    t_status_prohibit = 0;
+    poll_pdu = -1;
+    poll_byte = -1;
+    max_retx_threshold = 8;
+    sn_field_length = 12;
+    nr_rlc_am = new_nr_rlc_entity_am(100000,
+                                     100000,
+                                     deliver_sdu, ue,
+                                     successful_delivery, ue,
+                                     max_retx_reached, ue,
+                                     t_poll_retransmit,
+                                     t_reassembly, t_status_prohibit,
+                                     poll_pdu, poll_byte, max_retx_threshold,
+                                     sn_field_length);
+    nr_rlc_ue_add_srb_rlc_entity(ue, srb_id, nr_rlc_am);
+
+    LOG_D(RLC, "%s:%d:%s: added srb %d to ue %d\n",
+          __FILE__, __LINE__, __FUNCTION__, srb_id, rnti);
+  }
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+}
+
+static void add_drb_am(int rnti, struct LTE_DRB_ToAddMod *s)
+{
+  nr_rlc_entity_t            *nr_rlc_am;
+  nr_rlc_ue_t                *ue;
+
+  struct LTE_RLC_Config *r = s->rlc_Config;
+  struct LTE_LogicalChannelConfig *l = s->logicalChannelConfig;
+  int drb_id = s->drb_Identity;
+  int channel_id = *s->logicalChannelIdentity;
+  int logical_channel_group;
+
+  //int t_reordering;
+  int t_status_prohibit;
+  int t_poll_retransmit;
+  int poll_pdu;
+  int poll_byte;
+  int max_retx_threshold;
+  int t_reassembly;
+  int sn_field_length;
+
+  if (!(drb_id >= 1 && drb_id <= 5)) {
+    LOG_E(RLC, "%s:%d:%s: fatal, bad srb id %d\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id);
+    exit(1);
+  }
+
+  if (channel_id != drb_id + 2) {
+    LOG_E(RLC, "%s:%d:%s: todo, remove this limitation\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  logical_channel_group = *l->ul_SpecificParameters->logicalChannelGroup;
+
+  /* TODO: accept other values? */
+  if (logical_channel_group != 1) {
+    LOG_E(RLC, "%s:%d:%s: fatal error\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  switch (r->present) {
+  case LTE_RLC_Config_PR_am: {
+    struct LTE_RLC_Config__am *am;
+    am = &r->choice.am;
+    //t_reordering       = decode_t_reordering(am->dl_AM_RLC.t_Reordering);
+    t_status_prohibit  = decode_t_status_prohibit(am->dl_AM_RLC.t_StatusProhibit);
+    t_poll_retransmit  = decode_t_poll_retransmit(am->ul_AM_RLC.t_PollRetransmit);
+    poll_pdu           = decode_poll_pdu(am->ul_AM_RLC.pollPDU);
+    poll_byte          = decode_poll_byte(am->ul_AM_RLC.pollByte);
+    max_retx_threshold = decode_max_retx_threshold(am->ul_AM_RLC.maxRetxThreshold);
+    break;
+  }
+  default:
+    LOG_E(RLC, "%s:%d:%s: fatal error\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, rnti);
+  if (ue->drb[drb_id-1] != NULL) {
+    LOG_D(RLC, "%s:%d:%s: warning DRB %d already exist for ue %d, do nothing\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id, rnti);
+  } else {
+    /* hack: hardcode values for NR */
+    t_poll_retransmit = 45;
+    t_reassembly = 35;
+    t_status_prohibit = 0;
+    poll_pdu = -1;
+    poll_byte = -1;
+    max_retx_threshold = 8;
+    sn_field_length = 12;
+    nr_rlc_am = new_nr_rlc_entity_am(100000,
+                                     100000,
+                                     deliver_sdu, ue,
+                                     successful_delivery, ue,
+                                     max_retx_reached, ue,
+                                     t_poll_retransmit,
+                                     t_reassembly, t_status_prohibit,
+                                     poll_pdu, poll_byte, max_retx_threshold,
+                                     sn_field_length);
+    nr_rlc_ue_add_drb_rlc_entity(ue, drb_id, nr_rlc_am);
+
+    LOG_D(RLC, "%s:%d:%s: added drb %d to ue %d\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id, rnti);
+  }
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+}
+
+static void add_drb_um(int rnti, struct LTE_DRB_ToAddMod *s)
+{
+  nr_rlc_entity_t            *nr_rlc_um;
+  nr_rlc_ue_t                *ue;
+
+  struct LTE_RLC_Config *r = s->rlc_Config;
+  struct LTE_LogicalChannelConfig *l = s->logicalChannelConfig;
+  int drb_id = s->drb_Identity;
+  int channel_id = *s->logicalChannelIdentity;
+  int logical_channel_group;
+
+  //int t_reordering;
+  int sn_field_length;
+  int t_reassembly;
+
+  if (!(drb_id >= 1 && drb_id <= 5)) {
+    LOG_E(RLC, "%s:%d:%s: fatal, bad srb id %d\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id);
+    exit(1);
+  }
+
+  if (channel_id != drb_id + 2) {
+    LOG_E(RLC, "%s:%d:%s: todo, remove this limitation\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  logical_channel_group = *l->ul_SpecificParameters->logicalChannelGroup;
+
+  /* TODO: accept other values? */
+  if (logical_channel_group != 1) {
+    LOG_E(RLC, "%s:%d:%s: fatal error\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  switch (r->present) {
+  case LTE_RLC_Config_PR_um_Bi_Directional: {
+    struct LTE_RLC_Config__um_Bi_Directional *um;
+    um = &r->choice.um_Bi_Directional;
+    //t_reordering    = decode_t_reordering(um->dl_UM_RLC.t_Reordering);
+    if (um->dl_UM_RLC.sn_FieldLength != um->ul_UM_RLC.sn_FieldLength) {
+      LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+      exit(1);
+    }
+    sn_field_length = decode_sn_field_length(um->dl_UM_RLC.sn_FieldLength);
+    break;
+  }
+  default:
+    LOG_E(RLC, "%s:%d:%s: fatal error\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, rnti);
+  if (ue->drb[drb_id-1] != NULL) {
+    LOG_D(RLC, "%s:%d:%s: warning DRB %d already exist for ue %d, do nothing\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id, rnti);
+  } else {
+    /* hack: hardcode values for NR */
+    t_reassembly = 35;
+    sn_field_length = 6;
+    nr_rlc_um = new_nr_rlc_entity_um(1000000,
+                                     1000000,
+                                     deliver_sdu, ue,
+                                     t_reassembly,
+                                     sn_field_length);
+    nr_rlc_ue_add_drb_rlc_entity(ue, drb_id, nr_rlc_um);
+
+    LOG_D(RLC, "%s:%d:%s: added drb %d to ue %d\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id, rnti);
+  }
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+}
+
+static void add_drb(int rnti, struct LTE_DRB_ToAddMod *s)
+{
+  switch (s->rlc_Config->present) {
+  case LTE_RLC_Config_PR_am:
+    add_drb_am(rnti, s);
+    break;
+  case LTE_RLC_Config_PR_um_Bi_Directional:
+    add_drb_um(rnti, s);
+    break;
+  default:
+    LOG_E(RLC, "%s:%d:%s: fatal: unhandled DRB type\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+}
+
+rlc_op_status_t rrc_rlc_config_asn1_req (const protocol_ctxt_t   * const ctxt_pP,
+    const LTE_SRB_ToAddModList_t   * const srb2add_listP,
+    const LTE_DRB_ToAddModList_t   * const drb2add_listP,
+    const LTE_DRB_ToReleaseList_t  * const drb2release_listP
+#if (LTE_RRC_VERSION >= MAKE_VERSION(9, 0, 0))
+    ,const LTE_PMCH_InfoList_r9_t * const pmch_InfoList_r9_pP
+    ,const uint32_t sourceL2Id
+    ,const uint32_t destinationL2Id
+#endif
+                                        )
+{
+  int rnti = ctxt_pP->rnti;
+  int i;
+
+  if (/*ctxt_pP->enb_flag != 1 ||*/ ctxt_pP->module_id != 0 /*||
+      ctxt_pP->instance != 0 || ctxt_pP->eNB_index != 0 ||
+      ctxt_pP->configured != 1 || ctxt_pP->brOption != 0 */) {
+    LOG_E(RLC, "%s: ctxt_pP not handled (%d %d %d %d %d %d)\n", __FUNCTION__,
+          ctxt_pP->enb_flag , ctxt_pP->module_id, ctxt_pP->instance,
+          ctxt_pP->eNB_index, ctxt_pP->configured, ctxt_pP->brOption);
+    exit(1);
+  }
+
+  if (pmch_InfoList_r9_pP != NULL) {
+    LOG_E(RLC, "%s: pmch_InfoList_r9_pP not handled\n", __FUNCTION__);
+    exit(1);
+  }
+
+  if (drb2release_listP != NULL) {
+    LOG_E(RLC, "%s:%d:%s: TODO\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  if (srb2add_listP != NULL) {
+    for (i = 0; i < srb2add_listP->list.count; i++) {
+      add_srb(rnti, srb2add_listP->list.array[i]);
+    }
+  }
+
+  if (drb2add_listP != NULL) {
+    for (i = 0; i < drb2add_listP->list.count; i++) {
+      add_drb(rnti, drb2add_listP->list.array[i]);
+    }
+  }
+
+  return RLC_OP_STATUS_OK;
+}
+
+rlc_op_status_t rrc_rlc_config_req   (
+  const protocol_ctxt_t* const ctxt_pP,
+  const srb_flag_t      srb_flagP,
+  const MBMS_flag_t     mbms_flagP,
+  const config_action_t actionP,
+  const rb_id_t         rb_idP,
+  const rlc_info_t      rlc_infoP)
+{
+  nr_rlc_ue_t *ue;
+  int      i;
+
+  if (mbms_flagP) {
+    LOG_E(RLC, "%s:%d:%s: todo (MBMS NOT supported)\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  if (actionP != CONFIG_ACTION_REMOVE) {
+    LOG_E(RLC, "%s:%d:%s: todo (only CONFIG_ACTION_REMOVE supported)\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  if (ctxt_pP->module_id) {
+    LOG_E(RLC, "%s:%d:%s: todo (only module_id 0 supported)\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  if ((srb_flagP && !(rb_idP >= 1 && rb_idP <= 2)) ||
+      (!srb_flagP && !(rb_idP >= 1 && rb_idP <= 5))) {
+    LOG_E(RLC, "%s:%d:%s: bad rb_id (%d) (is_srb %d)\n", __FILE__, __LINE__, __FUNCTION__, rb_idP, srb_flagP);
+    exit(1);
+  }
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  LOG_D(RLC, "%s:%d:%s: remove rb %d (is_srb %d) for UE %d\n", __FILE__, __LINE__, __FUNCTION__, rb_idP, srb_flagP, ctxt_pP->rnti);
+  ue = nr_rlc_manager_get_ue(nr_rlc_ue_manager, ctxt_pP->rnti);
+  if (srb_flagP) {
+    if (ue->srb[rb_idP-1] != NULL) {
+      ue->srb[rb_idP-1]->delete(ue->srb[rb_idP-1]);
+      ue->srb[rb_idP-1] = NULL;
+    } else
+      LOG_W(RLC, "removing non allocated SRB %d, do nothing\n", rb_idP);
+  } else {
+    if (ue->drb[rb_idP-1] != NULL) {
+      ue->drb[rb_idP-1]->delete(ue->drb[rb_idP-1]);
+      ue->drb[rb_idP-1] = NULL;
+    } else
+      LOG_W(RLC, "removing non allocated DRB %d, do nothing\n", rb_idP);
+  }
+  /* remove UE if it has no more RB configured */
+  for (i = 0; i < 2; i++)
+    if (ue->srb[i] != NULL)
+      break;
+  if (i == 2) {
+    for (i = 0; i < 5; i++)
+      if (ue->drb[i] != NULL)
+        break;
+    if (i == 5)
+      nr_rlc_manager_remove_ue(nr_rlc_ue_manager, ctxt_pP->rnti);
+  }
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+  return RLC_OP_STATUS_OK;
+}
+
+void rrc_rlc_register_rrc (rrc_data_ind_cb_t rrc_data_indP, rrc_data_conf_cb_t rrc_data_confP)
+{
+  /* nothing to do */
+}
+
+rlc_op_status_t rrc_rlc_remove_ue (const protocol_ctxt_t* const x)
+{
+  LOG_D(RLC, "%s:%d:%s: remove UE %d\n", __FILE__, __LINE__, __FUNCTION__, x->rnti);
+  nr_rlc_manager_lock(nr_rlc_ue_manager);
+  nr_rlc_manager_remove_ue(nr_rlc_ue_manager, x->rnti);
+  nr_rlc_manager_unlock(nr_rlc_ue_manager);
+
+  return RLC_OP_STATUS_OK;
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_pdu.c b/openair2/LAYER2/nr_rlc/nr_rlc_pdu.c
new file mode 100644
index 0000000000..b42410ab72
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_pdu.c
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "nr_rlc_pdu.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+/**************************************************************************/
+/* RX PDU management                                                      */
+/**************************************************************************/
+
+nr_rlc_pdu_t *nr_rlc_new_pdu(int sn, int so, int is_first,
+    int is_last, char *data, int size)
+{
+  nr_rlc_pdu_t *ret = malloc(sizeof(nr_rlc_pdu_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  ret->sn = sn;
+  ret->so = so;
+  ret->size = size;
+  ret->is_first = is_first;
+  ret->is_last = is_last;
+  ret->next = NULL;
+
+  ret->data = malloc(size);
+  if (ret->data == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  memcpy(ret->data, data, size);
+
+  return ret;
+}
+
+void nr_rlc_free_pdu(nr_rlc_pdu_t *pdu)
+{
+  free(pdu->data);
+  free(pdu);
+}
+
+nr_rlc_pdu_t *nr_rlc_pdu_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    nr_rlc_pdu_t *list, nr_rlc_pdu_t *pdu)
+{
+  nr_rlc_pdu_t head;
+  nr_rlc_pdu_t *cur;
+  nr_rlc_pdu_t *prev;
+
+  head.next = list;
+  cur = list;
+  prev = &head;
+
+  /* order is by 'sn', if 'sn' is the same then order is by 'so' */
+  while (cur != NULL) {
+    /* check if 'pdu' is before 'cur' in the list */
+    if (sn_compare(sn_compare_data, cur->sn, pdu->sn) > 0 ||
+        (cur->sn == pdu->sn && cur->so > pdu->so)) {
+      break;
+    }
+    prev = cur;
+    cur = cur->next;
+  }
+  prev->next = pdu;
+  pdu->next = cur;
+  return head.next;
+}
+
+/**************************************************************************/
+/* PDU decoder                                                            */
+/**************************************************************************/
+
+void nr_rlc_pdu_decoder_init(nr_rlc_pdu_decoder_t *decoder,
+                             char *buffer, int size)
+{
+  decoder->error = 0;
+  decoder->byte = 0;
+  decoder->bit = 0;
+  decoder->buffer = buffer;
+  decoder->size = size;
+}
+
+static int get_bit(nr_rlc_pdu_decoder_t *decoder)
+{
+  int ret;
+
+  if (decoder->byte >= decoder->size) {
+    decoder->error = 1;
+    return 0;
+  }
+
+  ret = (decoder->buffer[decoder->byte] >> (7 - decoder->bit)) & 1;
+
+  decoder->bit++;
+  if (decoder->bit == 8) {
+    decoder->bit = 0;
+    decoder->byte++;
+  }
+
+  return ret;
+}
+
+int nr_rlc_pdu_decoder_get_bits(nr_rlc_pdu_decoder_t *decoder, int count)
+{
+  int ret = 0;
+  int i;
+
+  if (count > 31) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  for (i = 0; i < count; i++) {
+    ret <<= 1;
+    ret |= get_bit(decoder);
+    if (decoder->error) return -1;
+  }
+
+  return ret;
+}
+
+/**************************************************************************/
+/* PDU encoder                                                            */
+/**************************************************************************/
+
+void nr_rlc_pdu_encoder_init(nr_rlc_pdu_encoder_t *encoder,
+                             char *buffer, int size)
+{
+  encoder->byte = 0;
+  encoder->bit = 0;
+  encoder->buffer = buffer;
+  encoder->size = size;
+}
+
+static void put_bit(nr_rlc_pdu_encoder_t *encoder, int bit)
+{
+  if (encoder->byte == encoder->size) {
+    LOG_E(RLC, "%s:%d:%s: fatal, buffer full\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  encoder->buffer[encoder->byte] <<= 1;
+  if (bit)
+    encoder->buffer[encoder->byte] |= 1;
+
+  encoder->bit++;
+  if (encoder->bit == 8) {
+    encoder->bit = 0;
+    encoder->byte++;
+  }
+}
+
+void nr_rlc_pdu_encoder_put_bits(nr_rlc_pdu_encoder_t *encoder,
+                                 int value, int count)
+{
+  int i;
+  int x;
+
+  if (count > 31) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  x = 1 << (count - 1);
+  for (i = 0; i < count; i++, x >>= 1)
+    put_bit(encoder, value & x);
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_pdu.h b/openair2/LAYER2/nr_rlc/nr_rlc_pdu.h
new file mode 100644
index 0000000000..8e9872522f
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_pdu.h
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _NR_RLC_PDU_H_
+#define _NR_RLC_PDU_H_
+
+/**************************************************************************/
+/* RX PDU management                                                      */
+/**************************************************************************/
+
+typedef struct nr_rlc_pdu_t {
+  int sn;
+  char *data;       /* contains only SDU bytes, no PDU header */
+  int size;         /* size of SDU data, no PDU header bytes counted */
+  int so;
+  int is_first;
+  int is_last;
+  struct nr_rlc_pdu_t *next;
+} nr_rlc_pdu_t;
+
+nr_rlc_pdu_t *nr_rlc_new_pdu(int sn, int so, int is_first,
+    int is_last, char *data, int size);
+
+void nr_rlc_free_pdu(nr_rlc_pdu_t *pdu);
+
+nr_rlc_pdu_t *nr_rlc_pdu_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    nr_rlc_pdu_t *list, nr_rlc_pdu_t *pdu);
+
+/**************************************************************************/
+/* PDU decoder                                                            */
+/**************************************************************************/
+
+typedef struct {
+  int error;
+  int byte;           /* next byte to decode */
+  int bit;            /* next bit in next byte to decode */
+  char *buffer;
+  int size;
+} nr_rlc_pdu_decoder_t;
+
+void nr_rlc_pdu_decoder_init(nr_rlc_pdu_decoder_t *decoder,
+                             char *buffer, int size);
+
+#define nr_rlc_pdu_decoder_in_error(d) ((d)->error == 1)
+
+int nr_rlc_pdu_decoder_get_bits(nr_rlc_pdu_decoder_t *decoder, int count);
+
+/**************************************************************************/
+/* PDU encoder                                                            */
+/**************************************************************************/
+
+typedef struct {
+  int byte;           /* next byte to encode */
+  int bit;            /* next bit in next byte to encode */
+  char *buffer;
+  int size;
+} nr_rlc_pdu_encoder_t;
+
+void nr_rlc_pdu_encoder_init(nr_rlc_pdu_encoder_t *encoder,
+                             char *buffer, int size);
+
+void nr_rlc_pdu_encoder_put_bits(nr_rlc_pdu_encoder_t *encoder,
+                                 int value, int count);
+
+#endif /* _NR_RLC_PDU_H_ */
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_sdu.c b/openair2/LAYER2/nr_rlc/nr_rlc_sdu.c
new file mode 100644
index 0000000000..ef0d52558a
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_sdu.c
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "nr_rlc_sdu.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+nr_rlc_sdu_segment_t *nr_rlc_new_sdu(
+    char *buffer, int size,
+    int upper_layer_id)
+{
+  nr_rlc_sdu_t *sdu         = calloc(1, sizeof(nr_rlc_sdu_t));
+  nr_rlc_sdu_segment_t *ret = calloc(1, sizeof(nr_rlc_sdu_segment_t));
+  if (sdu == NULL || ret == NULL)
+    goto oom;
+
+  sdu->ref_count      = 1;
+  sdu->sn             = -1;                 /* set later */
+  sdu->upper_layer_id = upper_layer_id;
+  sdu->data           = malloc(size);
+  if (sdu->data == NULL)
+    goto oom;
+  memcpy(sdu->data, buffer, size);
+  sdu->size           = size;
+  sdu->retx_count     = -1;
+
+  ret->sdu      = sdu;
+  ret->size     = size;
+  ret->so       = 0;
+  ret->is_first = 1;
+  ret->is_last  = 1;
+
+  return ret;
+
+oom:
+  LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__,  __FUNCTION__);
+  exit(1);
+}
+
+void nr_rlc_free_sdu_segment(nr_rlc_sdu_segment_t *sdu)
+{
+  sdu->sdu->ref_count--;
+  if (sdu->sdu->ref_count == 0) {
+    free(sdu->sdu->data);
+    free(sdu->sdu);
+  }
+  free(sdu);
+}
+
+void nr_rlc_sdu_segment_list_append(nr_rlc_sdu_segment_t **list,
+                                    nr_rlc_sdu_segment_t **end,
+                                    nr_rlc_sdu_segment_t *sdu)
+{
+  if (*list == NULL) {
+    *list = sdu;
+    *end = sdu;
+    return;
+  }
+
+  (*end)->next = sdu;
+  *end = sdu;
+}
+
+nr_rlc_sdu_segment_t *nr_rlc_sdu_segment_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    nr_rlc_sdu_segment_t *list, nr_rlc_sdu_segment_t *sdu_segment)
+{
+  nr_rlc_sdu_segment_t head;
+  nr_rlc_sdu_segment_t *cur;
+  nr_rlc_sdu_segment_t *prev;
+
+  head.next = list;
+  cur = list;
+  prev = &head;
+
+  /* order is by 'sn', if 'sn' is the same then order is by 'so' */
+  while (cur != NULL) {
+    /* check if 'sdu_segment' is before 'cur' in the list */
+    if (sn_compare(sn_compare_data, cur->sdu->sn, sdu_segment->sdu->sn) > 0 ||
+        (cur->sdu->sn == sdu_segment->sdu->sn && cur->so > sdu_segment->so)) {
+      break;
+    }
+    prev = cur;
+    cur = cur->next;
+  }
+  prev->next = sdu_segment;
+  sdu_segment->next = cur;
+  return head.next;
+}
+
+void nr_rlc_free_sdu_segment_list(nr_rlc_sdu_segment_t *l)
+{
+  nr_rlc_sdu_segment_t *cur;
+
+  while (l != NULL) {
+    cur = l;
+    l = l->next;
+    nr_rlc_free_sdu_segment(cur);
+  }
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_sdu.h b/openair2/LAYER2/nr_rlc/nr_rlc_sdu.h
new file mode 100644
index 0000000000..f4585fb7d6
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_sdu.h
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _NR_RLC_SDU_H_
+#define _NR_RLC_SDU_H_
+
+typedef struct nr_rlc_sdu_t {
+  int sn;
+  int upper_layer_id;
+  char *data;
+  int size;
+  int retx_count;
+
+  int ref_count;      /* incremented each time the SDU is segmented */
+} nr_rlc_sdu_t;
+
+typedef struct nr_rlc_sdu_segment_t {
+  nr_rlc_sdu_t *sdu;
+  int size;
+  int so;
+  int is_first;
+  int is_last;
+  struct nr_rlc_sdu_segment_t *next;
+} nr_rlc_sdu_segment_t;
+
+nr_rlc_sdu_segment_t *nr_rlc_new_sdu(
+    char *buffer, int size,
+    int upper_layer_id);
+void nr_rlc_free_sdu_segment(nr_rlc_sdu_segment_t *sdu);
+void nr_rlc_sdu_segment_list_append(nr_rlc_sdu_segment_t **list,
+                                    nr_rlc_sdu_segment_t **end,
+                                    nr_rlc_sdu_segment_t *sdu);
+nr_rlc_sdu_segment_t *nr_rlc_sdu_segment_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    nr_rlc_sdu_segment_t *list, nr_rlc_sdu_segment_t *sdu_segment);
+void nr_rlc_free_sdu_segment_list(nr_rlc_sdu_segment_t *l);
+
+#endif /* _NR_RLC_SDU_H_ */
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.c b/openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.c
new file mode 100644
index 0000000000..09f1e4f599
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.c
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#include "nr_rlc_ue_manager.h"
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+typedef struct {
+  pthread_mutex_t lock;
+  nr_rlc_ue_t     **ue_list;
+  int             ue_count;
+  int             enb_flag;
+} nr_rlc_ue_manager_internal_t;
+
+nr_rlc_ue_manager_t *new_nr_rlc_ue_manager(int enb_flag)
+{
+  nr_rlc_ue_manager_internal_t *ret;
+
+  ret = calloc(1, sizeof(nr_rlc_ue_manager_internal_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  if (pthread_mutex_init(&ret->lock, NULL)) abort();
+  ret->enb_flag = enb_flag;
+
+  return ret;
+}
+
+int nr_rlc_manager_get_enb_flag(nr_rlc_ue_manager_t *_m)
+{
+  nr_rlc_ue_manager_internal_t *m = _m;
+  return m->enb_flag;
+}
+
+void nr_rlc_manager_lock(nr_rlc_ue_manager_t *_m)
+{
+  nr_rlc_ue_manager_internal_t *m = _m;
+  if (pthread_mutex_lock(&m->lock)) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+}
+
+void nr_rlc_manager_unlock(nr_rlc_ue_manager_t *_m)
+{
+  nr_rlc_ue_manager_internal_t *m = _m;
+  if (pthread_mutex_unlock(&m->lock)) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+}
+
+/* must be called with lock acquired */
+nr_rlc_ue_t *nr_rlc_manager_get_ue(nr_rlc_ue_manager_t *_m, int rnti)
+{
+  /* TODO: optimze */
+  nr_rlc_ue_manager_internal_t *m = _m;
+  int i;
+
+  for (i = 0; i < m->ue_count; i++)
+    if (m->ue_list[i]->rnti == rnti)
+      return m->ue_list[i];
+
+  LOG_D(RLC, "%s:%d:%s: new UE %d\n", __FILE__, __LINE__, __FUNCTION__, rnti);
+
+  m->ue_count++;
+  m->ue_list = realloc(m->ue_list, sizeof(nr_rlc_ue_t *) * m->ue_count);
+  if (m->ue_list == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  m->ue_list[m->ue_count-1] = calloc(1, sizeof(nr_rlc_ue_t));
+  if (m->ue_list[m->ue_count-1] == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  m->ue_list[m->ue_count-1]->rnti = rnti;
+
+  return m->ue_list[m->ue_count-1];
+}
+
+/* must be called with lock acquired */
+void nr_rlc_manager_remove_ue(nr_rlc_ue_manager_t *_m, int rnti)
+{
+  nr_rlc_ue_manager_internal_t *m = _m;
+  nr_rlc_ue_t *ue;
+  int i;
+  int j;
+
+  for (i = 0; i < m->ue_count; i++)
+    if (m->ue_list[i]->rnti == rnti)
+      break;
+
+  if (i == m->ue_count) {
+    LOG_D(RLC, "%s:%d:%s: warning: ue %d not found\n",
+          __FILE__, __LINE__, __FUNCTION__,
+          rnti);
+    return;
+  }
+
+  ue = m->ue_list[i];
+
+  for (j = 0; j < 2; j++)
+    if (ue->srb[j] != NULL)
+      ue->srb[j]->delete(ue->srb[j]);
+
+  for (j = 0; j < 5; j++)
+    if (ue->drb[j] != NULL)
+      ue->drb[j]->delete(ue->drb[j]);
+
+  free(ue);
+
+  m->ue_count--;
+  if (m->ue_count == 0) {
+    free(m->ue_list);
+    m->ue_list = NULL;
+    return;
+  }
+
+  memmove(&m->ue_list[i], &m->ue_list[i+1],
+          (m->ue_count - i) * sizeof(nr_rlc_ue_t *));
+  m->ue_list = realloc(m->ue_list, m->ue_count * sizeof(nr_rlc_ue_t *));
+  if (m->ue_list == NULL) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+}
+
+/* must be called with lock acquired */
+void nr_rlc_ue_add_srb_rlc_entity(nr_rlc_ue_t *ue, int srb_id, nr_rlc_entity_t *entity)
+{
+  if (srb_id < 1 || srb_id > 2) {
+    LOG_E(RLC, "%s:%d:%s: fatal, bad srb id\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  srb_id--;
+
+  if (ue->srb[srb_id] != NULL) {
+    LOG_E(RLC, "%s:%d:%s: fatal, srb already present\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  ue->srb[srb_id] = entity;
+}
+
+/* must be called with lock acquired */
+void nr_rlc_ue_add_drb_rlc_entity(nr_rlc_ue_t *ue, int drb_id, nr_rlc_entity_t *entity)
+{
+  if (drb_id < 1 || drb_id > 5) {
+    LOG_E(RLC, "%s:%d:%s: fatal, bad drb id\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  drb_id--;
+
+  if (ue->drb[drb_id] != NULL) {
+    LOG_E(RLC, "%s:%d:%s: fatal, drb already present\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  ue->drb[drb_id] = entity;
+}
diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h b/openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h
new file mode 100644
index 0000000000..97da28cdce
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+#ifndef _NR_RLC_UE_MANAGER_H_
+#define _NR_RLC_UE_MANAGER_H_
+
+#include "nr_rlc_entity.h"
+
+typedef void nr_rlc_ue_manager_t;
+
+typedef struct nr_rlc_ue_t {
+  int rnti;
+  /* due to openair calling status_ind/data_req, we need to keep this.
+   * To be considered 'hackish'.
+   */
+  int saved_status_ind_tb_size[2+5];
+  nr_rlc_entity_t *srb[2];
+  nr_rlc_entity_t *drb[5];
+} nr_rlc_ue_t;
+
+/***********************************************************************/
+/* manager functions                                                   */
+/***********************************************************************/
+
+nr_rlc_ue_manager_t *new_nr_rlc_ue_manager(int enb_flag);
+
+int nr_rlc_manager_get_enb_flag(nr_rlc_ue_manager_t *m);
+
+void nr_rlc_manager_lock(nr_rlc_ue_manager_t *m);
+void nr_rlc_manager_unlock(nr_rlc_ue_manager_t *m);
+
+nr_rlc_ue_t *nr_rlc_manager_get_ue(nr_rlc_ue_manager_t *m, int rnti);
+void nr_rlc_manager_remove_ue(nr_rlc_ue_manager_t *m, int rnti);
+
+/***********************************************************************/
+/* ue functions                                                        */
+/***********************************************************************/
+
+void nr_rlc_ue_add_srb_rlc_entity(nr_rlc_ue_t *ue, int srb_id, nr_rlc_entity_t *entity);
+void nr_rlc_ue_add_drb_rlc_entity(nr_rlc_ue_t *ue, int drb_id, nr_rlc_entity_t *entity);
+
+#endif /* _NR_RLC_UE_MANAGER_H_ */
diff --git a/openair2/LAYER2/nr_rlc/test.c b/openair2/LAYER2/nr_rlc/test.c
new file mode 100644
index 0000000000..52b0891bc3
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/test.c
@@ -0,0 +1,32 @@
+#include "nr_rlc_entity.h"
+
+#include <stdio.h>
+
+int main(void)
+{
+  char out[32768];
+  nr_rlc_entity_t *am;
+  int sdu_id = 0;
+  int size;
+  int i;
+
+  am = new_nr_rlc_entity_am(100000, 100000,
+                            0, 0,
+                            0, 0,
+                            0, 0,
+                            45, 35, 0,
+                            -1, -1, 8,
+                            12);
+
+  char data[8] = { 1, 2, 3, 4, 8, 7, 6, 5 };
+
+  am->recv_sdu(am, data, sizeof(data), sdu_id++);
+
+  size = am->generate_pdu(am, out, 32768);
+
+  printf("generate_pdu[%d]:", size);
+  for (i = 0; i < size; i++) printf(" %2.2x", (unsigned char)out[i]);
+  printf("\n");
+
+  return 0;
+}
diff --git a/openair2/LAYER2/nr_rlc/tests/LOG/log.h b/openair2/LAYER2/nr_rlc/tests/LOG/log.h
new file mode 100644
index 0000000000..5c9fcd643c
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/LOG/log.h
@@ -0,0 +1,10 @@
+#ifndef _LOG_H_
+#define _LOG_H_
+
+#include <stdio.h>
+
+#define LOG_E(x, ...) printf(__VA_ARGS__)
+#define LOG_D(x, ...) printf(__VA_ARGS__)
+#define LOG_W(x, ...) printf(__VA_ARGS__)
+
+#endif /* _LOG_H_ */
diff --git a/openair2/LAYER2/nr_rlc/tests/Makefile b/openair2/LAYER2/nr_rlc/tests/Makefile
new file mode 100644
index 0000000000..746d61483b
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/Makefile
@@ -0,0 +1,33 @@
+CC=gcc
+CFLAGS=-Wall -g --coverage -I.
+
+LIB=nr_rlc_entity.o nr_rlc_entity_am.o nr_rlc_entity_um.o nr_rlc_entity_tm.o \
+    nr_rlc_pdu.o nr_rlc_sdu.o
+
+tests:
+	@./run_tests.sh
+
+all: clean_run $(TEST).run
+
+%.run: $(TEST).bin
+	#valgrind ./$(TEST).bin > $(TEST).run_pre 2> $(TEST).valgrind
+	./$(TEST).bin > $(TEST).run_pre
+	grep ^TEST $(TEST).run_pre > $(TEST).run
+	gunzip -c $(TEST).txt.gz > $(TEST).txt
+	diff -q $(TEST).txt $(TEST).run
+
+$(TEST).bin: $(TEST).o $(LIB)
+	$(CC) $(CFLAGS) -o $@ $^
+
+%.o: ../%.c
+	$(CC) $(CFLAGS) -I.. -c -o $@ $<
+
+$(TEST).o: test.c
+	$(CC) $(CFLAGS) -c -o $@ $< -DTEST='"$(TEST).h"'
+
+clean_run:
+	rm -f $(TEST).run $(TEST).bin $(TEST).o
+
+clean:
+	rm -f *.o *.bin *.run *.run_pre *.gcov *.gcda *.gcno test*.txt a.out \
+		*.valgrind
diff --git a/openair2/LAYER2/nr_rlc/tests/run_tests.sh b/openair2/LAYER2/nr_rlc/tests/run_tests.sh
new file mode 100755
index 0000000000..ac22516064
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/run_tests.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+test_count=13
+
+for i in `seq $test_count`
+do
+  make all TEST=test$i >/dev/null 2>/dev/null
+  if [ $? != 0 ]
+  then
+    echo TEST $i FAILURE
+  fi
+done
diff --git a/openair2/LAYER2/nr_rlc/tests/test.c b/openair2/LAYER2/nr_rlc/tests/test.c
new file mode 100644
index 0000000000..742e79ca2c
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test.c
@@ -0,0 +1,483 @@
+#include "../nr_rlc_entity.h"
+#include "../nr_rlc_entity_am.h"
+#include "../nr_rlc_entity_um.h"
+#include "../nr_rlc_entity_tm.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/*
+ * GNB_AM <rx_maxsize> <tx_maxsize> <t_poll_retransmit> <t_reassembly>
+ *       <t_status_prohibit> <poll_pdu> <poll_byte> <max_retx_threshold>
+ *       <sn_field_length>
+ *       create the gNB RLC AM entity with given parameters
+ *
+ * UE_AM <rx_maxsize> <tx_maxsize> <t_poll_retransmit> <t_reassembly>
+ *      <t_status_prohibit> <poll_pdu> <poll_byte> <max_retx_threshold>
+ *      <sn_field_length>
+ *       create the UE RLC AM entity with given parameters
+ *
+ * GNB_UM <rx_maxsize> <tx_maxsize> <t_reassembly> <sn_field_length>
+ *     create the eNB RLC UM entity with given parameters
+ *
+ * UE_UM <rx_maxsize> <tx_maxsize> <t_reassembly> <sn_field_length>
+ *     create the UE RLC UM entity with given parameters
+ *
+ * GNB_TM <tx_maxsize>
+ *     create the eNB RLC TM entity with given parameters
+ *
+ * UE_UM <tx_maxsize>
+ *     create the UE RLC TM entity with given parameters
+ *
+ * TIME <time>
+ *     following actions to be performed at time <time>
+ *     <time> starts at 1
+ *     You must end your test definition with a line 'TIME, -1'.
+ *
+ * GNB_SDU <id> <size>
+ *     send an SDU to eNB with id <i> and size <size>
+ *     the SDU is [00 01 ... ff 01 ...]
+ *     (ie. start byte is 00 then we increment for each byte, loop if needed)
+ *
+ * UE_SDU <id> <size>
+ *     same as GNB_SDU but the SDU is sent to the UE
+ *
+ * GNB_PDU <size> <'size' bytes>
+ *     send a custom PDU from eNB to UE (eNB does not see this PDU at all)
+ *
+ * UE_PDU <size> <'size' bytes>
+ *     send a custom PDU from UE to eNB (UE does not see this PDU at all)
+ *
+ * GNB_PDU_SIZE <size>
+ *     set 'gnb_pdu_size'
+ *
+ * UE_PDU_SIZE <size>
+ *     set 'ue_pdu_size'
+ *
+ * GNB_RECV_FAILS <fails>
+ *     set the 'gnb_recv_fails' flag to <fails>
+ *     (1: recv will fail, 0: recv will succeed)
+ *
+ * UE_RECV_FAILS <fails>
+ *     same as GNB_RECV_FAILS but for 'ue_recv_fails'
+ *
+ * MUST_FAIL
+ *     to be used as first command after the first TIME to indicate
+ *     that the test must fail (ie. exit with non zero, crash not allowed)
+ *
+ * GNB_BUFFER_STATUS
+ *     call buffer_status for eNB and print result
+ *
+ * UE_BUFFER_STATUS
+ *     call buffer_status for UE and print result
+ *
+ * GNB_DISCARD_SDU <sdu ID>
+ *     discards given SDU
+ *
+ * UE_DISCARD_SDU <sdu ID>
+ *     discards given SDU
+ *
+ * RE_ESTABLISH
+ *     re-establish both eNB and UE
+ */
+
+enum action {
+  GNB_AM, UE_AM,
+  GNB_UM, UE_UM,
+  GNB_TM, UE_TM,
+  TIME, GNB_SDU, UE_SDU, GNB_PDU, UE_PDU,
+  GNB_PDU_SIZE, UE_PDU_SIZE,
+  GNB_RECV_FAILS, UE_RECV_FAILS,
+  MUST_FAIL,
+  GNB_BUFFER_STATUS, UE_BUFFER_STATUS,
+  GNB_DISCARD_SDU, UE_DISCARD_SDU,
+  RE_ESTABLISH
+};
+
+int test[] = {
+/* TEST is defined at compilation time */
+#include TEST
+};
+
+void deliver_sdu_gnb_am(void *deliver_sdu_data,
+                        struct nr_rlc_entity_t *_entity,
+                        char *buf, int size)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  printf("TEST: GNB: %"PRIu64": deliver SDU size %d [",
+         entity->t_current, size);
+  for (int i = 0; i < size; i++) printf(" %2.2x", (unsigned char)buf[i]);
+  printf("]\n");
+}
+
+void deliver_sdu_gnb_um(void *deliver_sdu_data,
+                        struct nr_rlc_entity_t *_entity,
+                        char *buf, int size)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  printf("TEST: GNB: %"PRIu64": deliver SDU size %d [",
+         entity->t_current, size);
+  for (int i = 0; i < size; i++) printf(" %2.2x", (unsigned char)buf[i]);
+  printf("]\n");
+}
+
+void deliver_sdu_gnb_tm(void *deliver_sdu_data,
+                        struct nr_rlc_entity_t *_entity,
+                        char *buf, int size)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+  printf("TEST: GNB: %"PRIu64": deliver SDU size %d [",
+         entity->t_current, size);
+  for (int i = 0; i < size; i++) printf(" %2.2x", (unsigned char)buf[i]);
+  printf("]\n");
+}
+
+void successful_delivery_gnb(void *successful_delivery_data,
+                             nr_rlc_entity_t *_entity, int sdu_id)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  printf("TEST: GNB: %"PRIu64": SDU %d was successfully delivered.\n",
+         entity->t_current, sdu_id);
+}
+
+void max_retx_reached_gnb(void *max_retx_reached_data,
+                          nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  printf("TEST: GNB: %"PRIu64": max RETX reached! radio link failure!\n",
+         entity->t_current);
+  exit(1);
+}
+
+void deliver_sdu_ue_am(void *deliver_sdu_data,
+                       struct nr_rlc_entity_t *_entity,
+                       char *buf, int size)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  printf("TEST: UE: %"PRIu64": deliver SDU size %d [",
+         entity->t_current, size);
+  for (int i = 0; i < size; i++) printf(" %2.2x", (unsigned char)buf[i]);
+  printf("]\n");
+}
+
+void deliver_sdu_ue_um(void *deliver_sdu_data,
+                       struct nr_rlc_entity_t *_entity,
+                       char *buf, int size)
+{
+  nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
+  printf("TEST: UE: %"PRIu64": deliver SDU size %d [",
+         entity->t_current, size);
+  for (int i = 0; i < size; i++) printf(" %2.2x", (unsigned char)buf[i]);
+  printf("]\n");
+}
+
+void deliver_sdu_ue_tm(void *deliver_sdu_data,
+                       struct nr_rlc_entity_t *_entity,
+                       char *buf, int size)
+{
+  nr_rlc_entity_tm_t *entity = (nr_rlc_entity_tm_t *)_entity;
+  printf("TEST: UE: %"PRIu64": deliver SDU size %d [",
+         entity->t_current, size);
+  for (int i = 0; i < size; i++) printf(" %2.2x", (unsigned char)buf[i]);
+  printf("]\n");
+}
+
+void successful_delivery_ue(void *successful_delivery_data,
+                            nr_rlc_entity_t *_entity, int sdu_id)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  printf("TEST: UE: %"PRIu64": SDU %d was successfully delivered.\n",
+         entity->t_current, sdu_id);
+}
+
+void max_retx_reached_ue(void *max_retx_reached_data,
+                         nr_rlc_entity_t *_entity)
+{
+  nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
+  printf("TEST: UE: %"PRIu64", max RETX reached! radio link failure!\n",
+         entity->t_current);
+  exit(1);
+}
+
+int test_main(void)
+{
+  nr_rlc_entity_t *gnb = NULL;
+  nr_rlc_entity_t *ue = NULL;
+  int i;
+  int k;
+  char *sdu;
+  char *pdu;
+  nr_rlc_entity_buffer_status_t buffer_status;
+  int gnb_do_buffer_status = 0;
+  int ue_do_buffer_status = 0;
+  int size;
+  int pos;
+  int next_byte_gnb = 0;
+  int next_byte_ue = 0;
+  int gnb_recv_fails = 0;
+  int ue_recv_fails = 0;
+  int gnb_pdu_size = 1000;
+  int ue_pdu_size = 1000;
+
+  printf("TEST: start\n");
+
+  sdu = malloc(16001);
+  pdu = malloc(3000);
+  if (sdu == NULL || pdu == NULL) {
+    printf("out of memory\n");
+    exit(1);
+  }
+
+  for (i = 0; i < 16001; i++)
+    sdu[i] = i & 255;
+
+  pos = 0;
+  if (test[pos] != TIME) {
+    printf("%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  for (i = 1; i < 1000; i++) {
+    if (i == test[pos+1]) {
+      pos += 2;
+      while (test[pos] != TIME)
+        switch (test[pos]) {
+        default: printf("fatal: unknown action\n"); exit(1);
+        case GNB_AM:
+          gnb = new_nr_rlc_entity_am(test[pos+1], test[pos+2],
+                                     deliver_sdu_gnb_am, NULL,
+                                     successful_delivery_gnb, NULL,
+                                     max_retx_reached_gnb, NULL,
+                                     test[pos+3], test[pos+4], test[pos+5],
+                                     test[pos+6], test[pos+7], test[pos+8],
+                                     test[pos+9]);
+          pos += 10;
+          break;
+        case UE_AM:
+          ue = new_nr_rlc_entity_am(test[pos+1], test[pos+2],
+                                    deliver_sdu_ue_am, NULL,
+                                    successful_delivery_ue, NULL,
+                                    max_retx_reached_ue, NULL,
+                                    test[pos+3], test[pos+4], test[pos+5],
+                                    test[pos+6], test[pos+7], test[pos+8],
+                                    test[pos+9]);
+          pos += 10;
+          break;
+        case GNB_UM:
+          gnb = new_nr_rlc_entity_um(test[pos+1], test[pos+2],
+                                     deliver_sdu_gnb_um, NULL,
+                                     test[pos+3], test[pos+4]);
+          pos += 5;
+          break;
+        case UE_UM:
+          ue = new_nr_rlc_entity_um(test[pos+1], test[pos+2],
+                                    deliver_sdu_ue_um, NULL,
+                                    test[pos+3], test[pos+4]);
+          pos += 5;
+          break;
+        case GNB_TM:
+          gnb = new_nr_rlc_entity_tm(test[pos+1], deliver_sdu_gnb_tm, NULL);
+          pos += 2;
+          break;
+        case UE_TM:
+          ue = new_nr_rlc_entity_tm(test[pos+1], deliver_sdu_ue_tm, NULL);
+          pos += 2;
+          break;
+        case GNB_SDU:
+          for (k = 0; k < test[pos+2]; k++, next_byte_gnb++)
+            sdu[k] = next_byte_gnb;
+          printf("TEST: GNB: %d: recv_sdu (id %d): size %d: [",
+                 i, test[pos+1], test[pos+2]);
+          for (k = 0; k < test[pos+2]; k++)
+            printf(" %2.2x", (unsigned char)sdu[k]);
+          printf("]\n");
+          gnb->recv_sdu(gnb, sdu, test[pos+2], test[pos+1]);
+          pos += 3;
+          break;
+        case UE_SDU:
+          for (k = 0; k < test[pos+2]; k++, next_byte_ue++)
+            sdu[k] = next_byte_ue;
+          printf("TEST: UE: %d: recv_sdu (id %d): size %d: [",
+                 i, test[pos+1], test[pos+2]);
+          for (k = 0; k < test[pos+2]; k++)
+            printf(" %2.2x", (unsigned char)sdu[k]);
+          printf("]\n");
+          ue->recv_sdu(ue, sdu, test[pos+2], test[pos+1]);
+          pos += 3;
+          break;
+        case GNB_PDU:
+          for (k = 0; k < test[pos+1]; k++)
+            pdu[k] = test[pos+2+k];
+          printf("TEST: GNB: %d: custom PDU: size %d: [", i, test[pos+1]);
+          for (k = 0; k < test[pos+1]; k++) printf(" %2.2x", (unsigned char)pdu[k]);
+          printf("]\n");
+          if (!ue_recv_fails)
+            ue->recv_pdu(ue, pdu, test[pos+1]);
+          pos += 2 + test[pos+1];
+          break;
+        case UE_PDU:
+          for (k = 0; k < test[pos+1]; k++)
+            pdu[k] = test[pos+2+k];
+          printf("TEST: UE: %d: custom PDU: size %d: [", i, test[pos+1]);
+          for (k = 0; k < test[pos+1]; k++) printf(" %2.2x", (unsigned char)pdu[k]);
+          printf("]\n");
+          if (!gnb_recv_fails)
+            gnb->recv_pdu(gnb, pdu, test[pos+1]);
+          pos += 2 + test[pos+1];
+          break;
+        case GNB_PDU_SIZE:
+          gnb_pdu_size = test[pos+1];
+          pos += 2;
+          break;
+        case UE_PDU_SIZE:
+          ue_pdu_size = test[pos+1];
+          pos += 2;
+          break;
+        case GNB_RECV_FAILS:
+          gnb_recv_fails = test[pos+1];
+          pos += 2;
+          break;
+        case UE_RECV_FAILS:
+          ue_recv_fails = test[pos+1];
+          pos += 2;
+          break;
+        case MUST_FAIL:
+          /* do nothing, only used by caller */
+          pos++;
+          break;
+        case GNB_BUFFER_STATUS:
+          gnb_do_buffer_status = 1;
+          pos++;
+          break;
+        case UE_BUFFER_STATUS:
+          ue_do_buffer_status = 1;
+          pos++;
+          break;
+        case GNB_DISCARD_SDU:
+          printf("TEST: GNB: %d: discard SDU %d\n", i, test[pos+1]);
+          gnb->discard_sdu(gnb, test[pos+1]);
+          pos += 2;
+          break;
+        case UE_DISCARD_SDU:
+          printf("TEST: UE: %d: discard SDU %d\n", i, test[pos+1]);
+          ue->discard_sdu(ue, test[pos+1]);
+          pos += 2;
+          break;
+        case RE_ESTABLISH:
+          printf("TEST: %d: re-establish eNB and UE\n", i);
+          gnb->reestablishment(gnb);
+          ue->reestablishment(ue);
+          pos++;
+          break;
+        }
+    }
+
+    gnb->set_time(gnb, i);
+    ue->set_time(ue, i);
+
+    if (gnb_do_buffer_status) {
+      gnb_do_buffer_status = 0;
+      buffer_status = gnb->buffer_status(gnb, gnb_pdu_size);
+      printf("TEST: GNB: %d: buffer_status: status_size %d tx_size %d retx_size %d\n",
+             i,
+             buffer_status.status_size,
+             buffer_status.tx_size,
+             buffer_status.retx_size);
+    }
+
+    size = gnb->generate_pdu(gnb, pdu, gnb_pdu_size);
+    if (size) {
+      printf("TEST: GNB: %d: generate_pdu: size %d: [", i, size);
+      for (k = 0; k < size; k++) printf(" %2.2x", (unsigned char)pdu[k]);
+      printf("]\n");
+      if (!ue_recv_fails)
+        ue->recv_pdu(ue, pdu, size);
+    }
+
+    if (ue_do_buffer_status) {
+      ue_do_buffer_status = 0;
+      buffer_status = ue->buffer_status(ue, ue_pdu_size);
+      printf("TEST: UE: %d: buffer_status: status_size %d tx_size %d retx_size %d\n",
+             i,
+             buffer_status.status_size,
+             buffer_status.tx_size,
+             buffer_status.retx_size);
+    }
+
+    size = ue->generate_pdu(ue, pdu, ue_pdu_size);
+    if (size) {
+      printf("TEST: UE: %d: generate_pdu: size %d: [", i, size);
+      for (k = 0; k < size; k++) printf(" %2.2x", (unsigned char)pdu[k]);
+      printf("]\n");
+      if (!gnb_recv_fails)
+        gnb->recv_pdu(gnb, pdu, size);
+    }
+  }
+
+  gnb->delete(gnb);
+  ue->delete(ue);
+
+  free(sdu);
+  free(pdu);
+
+  return 0;
+}
+
+void usage(void)
+{
+  printf("options:\n");
+  printf("    -no-fork\n");
+  printf("        don't fork (to ease debugging with gdb)\n");
+  exit(0);
+}
+
+int main(int n, char **v)
+{
+  int must_fail = 0;
+  int son;
+  int status;
+  int i;
+  int no_fork = 0;
+
+  for (i = 1; i < n; i++) {
+    if (!strcmp(v[i], "-no-fork")) { no_fork = 1; continue; }
+    usage();
+  }
+
+  if (test[2] == MUST_FAIL)
+    must_fail = 1;
+
+  if (no_fork) return test_main();
+
+  son = fork();
+  if (son == -1) {
+    perror("fork");
+    return 1;
+  }
+
+  if (son == 0)
+    return test_main();
+
+  if (wait(&status) == -1) {
+    perror("wait");
+    return 1;
+  }
+
+  /* child must quit properly */
+  if (!WIFEXITED(status))
+    return 1;
+
+  /* child must fail if expected to */
+  if (must_fail && WEXITSTATUS(status) == 0)
+    return 1;
+
+  /* child must not fail if not expected to */
+  if (!must_fail && WEXITSTATUS(status))
+    return 1;
+
+  return 0;
+}
diff --git a/openair2/LAYER2/nr_rlc/tests/test1.h b/openair2/LAYER2/nr_rlc/tests/test1.h
new file mode 100644
index 0000000000..3240ecfc36
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test1.h
@@ -0,0 +1,14 @@
+/*
+ * basic am test (SN field size 12):
+ * at time 1, gNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    GNB_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 12,
+    UE_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 12,
+    GNB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test1.txt.gz b/openair2/LAYER2/nr_rlc/tests/test1.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..d3852d070cb17dae5f7db1678ef427b0e7f85a96
GIT binary patch
literal 246
zcmV<S015veiwFp0zp`Bd19W9`bTKY;cys`jkUek0KoCTGe#MleiN-h%!DR%Zqrp@N
zS&nZVS+Y=c9|ZF2F-IJMV@syGEpFe=ykxOXVi=ogJ4*b#T)oE#Vra<^sZkE&Qh`tP
zr}9S-Fvb=n7=RNn1Yf`id<CcA8#vpwK%cS~omol0lxrxH3nixq4Pk8-9;Kuz52CT2
zZ<;tm)BF-XTv3Ql>%!I<U%Y-(&kA9W*NJuczxjT@qfB^XgzfT|q{Me-U|gPyF@>wD
wQ&U9L^DUlz)}#N@cdf;aVADRy_aWKgHR`>0-Fd)-4>-ug8B~lLdyE1A0C8V&X#fBK

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test10.h b/openair2/LAYER2/nr_rlc/tests/test10.h
new file mode 100644
index 0000000000..1653736f26
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test10.h
@@ -0,0 +1,13 @@
+/*
+ * um test (SN field size 6):
+ * test reassembly
+ * at time 1, gNB receives an SDU of 50 bytes
+ * sends it in 3 parts
+ */
+
+TIME, 1,
+    GNB_UM, 100000, 100000, 35, 6,
+    UE_UM, 100000, 100000, 35, 6,
+    GNB_PDU_SIZE, 22,
+    GNB_SDU, 0, 50,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test10.txt.gz b/openair2/LAYER2/nr_rlc/tests/test10.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..12430b53c5647e7c17d1428aa1413ada5b7e5b67
GIT binary patch
literal 248
zcmV<U00;jciwFpl0<&EL19W9`bTKe4ba-?C&5^lI!%z@Kd%oh7(A}Bmy+lNGG;u0~
zBFBD4mJmXYlY*}&wx9??2th+P#mt<$)T?z}v7ef8x~P}=_U^jED#q9zxBKqE<<Oz4
z)6(!7NmX%=gbX<fC_yR8P>u>zq6Y0yi%y3T^_Vcj91B>2rC5gLSb>#TgYB>u>#&IR
zxNyTA4|sy7c!uYAftPrL@9-Ay@QC*W5$xfUfyek9$7YJ#m+tVc-hEb|=#K`@Nd66}
yT}ZRtUphV`g)d1KW>T?`=6rq<(Pp)L>*CXJj3eut%}2Zcx7;@}2S^dB0ssK%)_l4E

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test11.h b/openair2/LAYER2/nr_rlc/tests/test11.h
new file mode 100644
index 0000000000..41465e09e1
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test11.h
@@ -0,0 +1,13 @@
+/*
+ * um test (SN field size 12):
+ * test reassembly
+ * at time 1, gNB receives an SDU of 50 bytes
+ * sends it in 3 parts
+ */
+
+TIME, 1,
+    GNB_UM, 100000, 100000, 35, 12,
+    UE_UM, 100000, 100000, 35, 12,
+    GNB_PDU_SIZE, 22,
+    GNB_SDU, 0, 50,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test11.txt.gz b/openair2/LAYER2/nr_rlc/tests/test11.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..1a5294ffa023c3fa3a2b2853e0f846fbbc9d4807
GIT binary patch
literal 251
zcmV<X00jRZiwFol1G8NM19W9`bTKh5ba-?C&5^xM!%z%Hcb?*mu-U%(9}!hImZlRz
z)lKeA6$zm#O*`=PG!Yq!5JH{UJlH-ypX=48t~ic<81L%2UOzupXvGlQ^X?c<-1h-_
zxHR=2kyI*P5J40%#32ciA{mk+1yUj&X^<AV3`V3w1y$5ghbCx>W@wHUXo-5XL0dGS
z5$!O+6f?|W36^3RmSY80VjgR-77JL!I-KD2^_##xzQ@6jad!x(uj|dG>l2v;Zg;pM
z*)ODaAx+Y^cwdp+k0i||Qn8Sxp#GlKb~UdJ@vT3{fz8u)dGr7M@+WSJh%B=L0034L
BaJ~Ql

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test12.h b/openair2/LAYER2/nr_rlc/tests/test12.h
new file mode 100644
index 0000000000..4ab28dd3f2
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test12.h
@@ -0,0 +1,18 @@
+/*
+ * um test (SN field size 6):
+ * test reassembly and timeout
+ * at time 1, gNB receives an SDU of 50 bytes
+ * sends it in 3 parts
+ * part 2 not received
+ */
+
+TIME, 1,
+    GNB_UM, 100000, 100000, 35, 6,
+    UE_UM, 100000, 100000, 35, 6,
+    GNB_PDU_SIZE, 22,
+    GNB_SDU, 0, 50,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 3,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test12.txt.gz b/openair2/LAYER2/nr_rlc/tests/test12.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..ac85aa89f39fb5c76f5e21b07d1658c461839e0c
GIT binary patch
literal 227
zcmV<90381xiwFqg1hZWN19W9`bTKk6ba-?CrI0;O!$1&3dw#_$p_|?FcZr1PXmE#6
zbUvR^ng}@#1;3uyq6tDNpxa_rZ{G9|&wa;niSu&PZ};QVeTQ|-IeqMo<B8k-i0;mz
z{b#0B#|siN<S3v5m8e2BYEX+J8c;&#$BZUSm|>0utiVdF!fLF+S}bA%maq}a*n|r=
z-0^@Hc!^hdjW>9UM|{8&KH?dl2qM_)4}rIQ$a7rs?tMIcw|8IL7y75b1<9|F(neZ!
d|7d(c3cr#ptfXcmt@~V`=nKslBXIBm004?2Y|#J!

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test13.h b/openair2/LAYER2/nr_rlc/tests/test13.h
new file mode 100644
index 0000000000..fc973fdcff
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test13.h
@@ -0,0 +1,18 @@
+/*
+ * um test (SN field size 12):
+ * test reassembly and timeout
+ * at time 1, gNB receives an SDU of 50 bytes
+ * sends it in 3 parts
+ * part 2 not received
+ */
+
+TIME, 1,
+    GNB_UM, 100000, 100000, 35, 12,
+    UE_UM, 100000, 100000, 35, 12,
+    GNB_PDU_SIZE, 22,
+    GNB_SDU, 0, 50,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 3,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test13.txt.gz b/openair2/LAYER2/nr_rlc/tests/test13.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..3838dcf15214dda2c8bbd7fba5e60c9016b4a8c2
GIT binary patch
literal 230
zcmV<C02%)uiwFqs1hZWN19W9`bTKn7ba-?Cosd0G!$1&3dw#_$q1&DFcZr1PXmE#6
zbUvR^ng}@#1;3s+B1I5FDBTvbdh@1#c<wv)OPrUR{&YV+-FJA$oYTi<KOVT<j@aEX
zwEfJK*71TF=9sX66<CQ?SdBGUi$!d}5_Wvd*n}JIxbT1%c!^hdjW>9UM|{8&KH?dl
zkReAx0Trl36{=B#S`^WM5*krP6Tt}n`a|F?@A4d%ym=oF-|eNfePJho>m4pg`h%3t
gq*eMF?+a4+mE_e*YR;r}sQ+yB1=SY*gZTjf0Cp8^d;kCd

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test2.h b/openair2/LAYER2/nr_rlc/tests/test2.h
new file mode 100644
index 0000000000..7adc349c1e
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test2.h
@@ -0,0 +1,14 @@
+/*
+ * basic am test (SN field size 18):
+ * at time 1, gNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    GNB_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 18,
+    UE_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 18,
+    GNB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test2.txt.gz b/openair2/LAYER2/nr_rlc/tests/test2.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..04092cdaa2d778f3a65dfbe26e9879a01c740428
GIT binary patch
literal 250
zcmV<W00sXaiwFpBzp`Bd19W9`bTTe<cys`jkWEj*Komvy{))Sljk&c{u}cGS=Z3N}
zq$!<O)1)C}J^=mo4g)1pimAyY^A_{Y+<UUwJWFA0q3vB3=C}9P6j2H-<u*6U;l5Vj
z$UmyT1rbwxfCMA(5sbkxn1D~<GdKZXmL1TitVI`A@{MvWB|cC}1`vng7PCN8($rhg
zm`@jNqM?CKgq5o*(YfzzoeACjod;G7yFX6!{m=8|eovpcmEzC1+y9W1*cArGm8BR{
zxu*GQi->yojpr@#$+h=oXR#;P<PXo6IoYE&x%uUB=N~dc#z8WE0HFwOcaj1C0ODhM
AegFUf

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test3.h b/openair2/LAYER2/nr_rlc/tests/test3.h
new file mode 100644
index 0000000000..db5454f8de
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test3.h
@@ -0,0 +1,14 @@
+/*
+ * basic um test (SN field size 6):
+ * at time 1, gNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    GNB_UM, 100000, 100000, 35, 6,
+    UE_UM, 100000, 100000, 35, 6,
+    GNB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test3.txt.gz b/openair2/LAYER2/nr_rlc/tests/test3.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..070981d99126bc7e90585422defb1befa6f315bf
GIT binary patch
literal 200
zcmV;(05|_1iwFpJzp`Bd19W9`bTck=cys`bkTDCwFc5`zf5jVhyQr<&Q7mpQZ6_(A
znyVB<Bu$F=>n*{E)(B2{L+-nKU!G=pg4R^pjB=N^yLAGQK=ZMat+E(53N-0IHFt)f
z1bYyH1ZWDHf##qH6oVF^C1_O+KqtPit_;hovYsM5<sVRyI*#IV)3HXjDf}(}p7JCd
zY+bXKeOqH&*SdeDV-d{FC(W<?K2N`#MBhrv!C=5a98~Zrj{>cuz_JGbGdsJ+0RR9i
C$634p

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test4.h b/openair2/LAYER2/nr_rlc/tests/test4.h
new file mode 100644
index 0000000000..1c9bcbc0b9
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test4.h
@@ -0,0 +1,14 @@
+/*
+ * basic um test (SN field size 12):
+ * at time 1, gNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    GNB_UM, 100000, 100000, 35, 6,
+    UE_UM, 100000, 100000, 35, 6,
+    GNB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test4.txt.gz b/openair2/LAYER2/nr_rlc/tests/test4.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..f4230c8522fe334bb0a8d294b36d62e70c761f88
GIT binary patch
literal 200
zcmV;(05|_1iwFpQzp`Bd19W9`bTlq>cys`bkTDCwFc5`zf5jVhyQr<&Q7mpQZ6_(A
znyVB<Bu$F=>n*{E)(B2{L+-nKU!G=pg4R^pjB=N^yLAGQK=ZMat+E(53N-0IHFt)f
z1bYyH1ZWDHf##qH6oVF^C1_O+KqtPit_;hovYsM5<sVRyI*#IV)3HXjDf}(}p7JCd
zY+bXKeOqH&*SdeDV-d{FC(W<?K2N`#MBhrv!C=5a98~Zrj{>cuz_JGbGdsJ+0RR9o
C-C4u{

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test5.h b/openair2/LAYER2/nr_rlc/tests/test5.h
new file mode 100644
index 0000000000..ea090adcb5
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test5.h
@@ -0,0 +1,15 @@
+/*
+ * basic am test (SN field size 11, not exiting, test must fail):
+ * at time 1, gNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    MUST_FAIL,
+    GNB_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 11,
+    UE_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 11,
+    GNB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test5.txt.gz b/openair2/LAYER2/nr_rlc/tests/test5.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..812365c1e36f0b492cb94e3e45c4fe74f6d5888d
GIT binary patch
literal 42
xcmb2|=HTewyD655xg@o?#8j`OqJ)9Fke6>`kcOV8j@CmahFVVX-8>8o3;-0C3vd7c

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test6.h b/openair2/LAYER2/nr_rlc/tests/test6.h
new file mode 100644
index 0000000000..86d7724dd2
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test6.h
@@ -0,0 +1,15 @@
+/*
+ * basic um test (SN field size 19, bad size, test must fail):
+ * at time 1, gNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    MUST_FAIL,
+    GNB_UM, 100000, 100000, 35, 19,
+    UE_UM, 100000, 100000, 35, 19,
+    GNB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test6.txt.gz b/openair2/LAYER2/nr_rlc/tests/test6.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..1d8575cef043478db1d98f22b0e1aa37dc578bdb
GIT binary patch
literal 42
xcmb2|=HL)Muql>_xg@o?#7wWGqJ)9Fke6>`kcOV8j@CmahFVVX-8>8o3;+XB3j6>7

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test7.h b/openair2/LAYER2/nr_rlc/tests/test7.h
new file mode 100644
index 0000000000..4a380261f4
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test7.h
@@ -0,0 +1,14 @@
+/*
+ * basic tm test:
+ * at time 1, gNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    GNB_TM, 100000,
+    UE_TM, 100000,
+    GNB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test7.txt.gz b/openair2/LAYER2/nr_rlc/tests/test7.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..8689670886558883003d140847b37b2f6c723049
GIT binary patch
literal 192
zcmV;x06+g9iwFoW!m?cg19W9`bT=+^cys`jkWCAMKomvy{ffJ+-L<lhSw_;Xjk*dM
zta*kI5#uP)uTKmk%mP>MF5WqCZxn=)Lg7;DMp2V@`;CI9VC7Wl!ju^22AtGS`9t7Q
z;Q$i!z$rKb=b#TRz$Lf>*GWfomaEv*iN2X~xFrr6iIGcQiAA`LTg5=bvCJ~Dx~}TV
uqN!Y8-M9#MZ<CdG{^a><5dXVL(gAx0_(Q#BV^3wjhj{`VCg!TZ0RRATD_gGs

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test8.h b/openair2/LAYER2/nr_rlc/tests/test8.h
new file mode 100644
index 0000000000..c205f7ab57
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test8.h
@@ -0,0 +1,20 @@
+/*
+ * am test (SN field size 12):
+ * test reassembly
+ * at time 1, gNB receives an SDU of 50 bytes
+ * sends 1st part, received by UE
+ * then sends 2nd part, not received
+ * then sends 3rd part, received
+ * then UE receives all
+ */
+
+TIME, 1,
+    GNB_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 12,
+    UE_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 12,
+    GNB_PDU_SIZE, 22,
+    GNB_SDU, 0, 50,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 3,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test8.txt.gz b/openair2/LAYER2/nr_rlc/tests/test8.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..d69e43acdce5dd621905f0c2d731d189b0807e70
GIT binary patch
literal 330
zcmV-Q0k!@giwFqI{<2*H19W9`bT}?_cys{Ol)FyDKoCXye8nlDK{Mk=Y?p|LjwVTk
zP~^uNMT!uz;~@BYvLO~2NQ5FNkha)jch0?cW_LE3&NA9{-mFKn-TwOKGD9;OS8f(<
z)p540kn`=K<%=Vdj9WwyMGOg$5Q&f&86ydjA~{kZC9=KfkQx<KQ9}bXL?bjt$7q74
zXpR<WiB_mXYfLc33=6Oji?A3QV+odGIaXjLR$&gS34}oJJ{h=o53b4AZt+-kd-Y~s
zeJVQxr+XNX><FpoN#67+z6Ye>Taw0}<a&}1>gQR_Cx^-geV_dr$OzLTj;r6w)+;+e
zO5dd=Z>(^ivG+gB%+KrJx5tb1q{^+98`m(sn)lD;U%Zw`|7Nrf)D1rTA@W6t4^Mc?
cTiUKHUE9{(YW3V_>#B><D~;E~0JQ`F02)D^SO5S3

literal 0
HcmV?d00001

diff --git a/openair2/LAYER2/nr_rlc/tests/test9.h b/openair2/LAYER2/nr_rlc/tests/test9.h
new file mode 100644
index 0000000000..c3fb0e31d1
--- /dev/null
+++ b/openair2/LAYER2/nr_rlc/tests/test9.h
@@ -0,0 +1,20 @@
+/*
+ * am test (SN field size 18):
+ * test reassembly
+ * at time 1, gNB receives an SDU of 50 bytes
+ * sends 1st part, received by UE
+ * then sends 2nd part, not received
+ * then sends 3rd part, received
+ * then UE receives all
+ */
+
+TIME, 1,
+    GNB_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 18,
+    UE_AM, 100000, 100000, 45, 35, 0, -1, -1, 8, 18,
+    GNB_PDU_SIZE, 22,
+    GNB_SDU, 0, 50,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 3,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/nr_rlc/tests/test9.txt.gz b/openair2/LAYER2/nr_rlc/tests/test9.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..6def9c20f8dab3b8faa9284fc8cc24dc4201db2e
GIT binary patch
literal 328
zcmV-O0k{4iiwFqD0kd5K19W9`bU7|`cys{Olfg>EKomvye#KepM&5f#+9pe}xO3BX
zB~p^iL<&VDX)5~lc8Y;SZJ}ZpLKd^g%(?f>oB4D$&uBJzy}6k0*LU|f8Jbc1a=UC?
z%jL=;SG%89@18_59uPqkF(g7_Btb^V7?~g`k|PCDBD;+qsZc=`H8etFG(kt`7@eRg
znxh3;q7L<Fg$bsZVG$N%2{yvU*aS<l94oLAbC|~}A|cYpF9n|bv#;}wU%t5ZGrd_z
zpUS>~{JO?~WJgFvj})q(+<ricek5rcNWMo3#!i*Cm>xPm>MlHdgFJD1q`Lbn$sySR
zl1eX|b)Gxh!NgEt7~b>WRR|nN&aYQnUo*R1bZ_ThJe@?(JZA?0{XvVrB?=aW*Sw)=
a%hESZ)vniXon-H>FFpXwshaG|1ONc&VU^zi

literal 0
HcmV?d00001

diff --git a/targets/TEST/PDCP/with_rlc/test_pdcp_rlc.c b/targets/TEST/PDCP/with_rlc/test_pdcp_rlc.c
index 9f28fd7c78..378ee95c3b 100644
--- a/targets/TEST/PDCP/with_rlc/test_pdcp_rlc.c
+++ b/targets/TEST/PDCP/with_rlc/test_pdcp_rlc.c
@@ -226,7 +226,7 @@ int main(int argc, char **argv)
   mac_xface = malloc(sizeof(MAC_xface));
   Mac_rlc_xface = (MAC_RLC_XFACE*)malloc16(sizeof(MAC_RLC_XFACE));
 
-  rlc_module_init ();
+  rlc_module_init (1);
   pdcp_module_init();
   logInit();
 
-- 
2.26.2