diff --git a/cmake_targets/CMakeLists.txt b/cmake_targets/CMakeLists.txt
index f155d3061db9a80041b292432d8f90b18f210d45..68849c01916111337cd1b32cfd534d61fcea84b3 100644
--- a/cmake_targets/CMakeLists.txt
+++ b/cmake_targets/CMakeLists.txt
@@ -727,7 +727,6 @@ Message("CPU_Affinity flag is ${CPU_AFFINITY}")
 ##############################################################
 
 add_boolean_option(NO_RRM                  True  "DO WE HAVE A RADIO RESSOURCE MANAGER: NO")
-add_boolean_option(RRC_DEFAULT_RAB_IS_AM False "set the RLC mode to AM for the default bearer")
 
 add_boolean_option(OAI_NW_DRIVER_TYPE_ETHERNET False "????")
 add_boolean_option(DEADLINE_SCHEDULER True "Use the Linux scheduler SCHED_DEADLINE: kernel >= 3.14")
@@ -828,7 +827,7 @@ add_boolean_option(TRACE_RLC_UM_TX_STATUS  False "TRACE for RLC UM, TO BE CHANGE
 ##########################
 # RRC LAYER OPTIONS
 ##########################
-add_boolean_option(RRC_DEFAULT_RAB_IS_AM       False  "Otherwise it is UM, configure params are actually set in rrc_eNB.c:rrc_eNB_generate_defaultRRCConnectionReconfiguration(...)")
+add_boolean_option(RRC_DEFAULT_RAB_IS_AM       True   "set the RLC mode to AM for the default bearer, otherwise it is UM.")
 
 
 ##########################
@@ -1640,7 +1639,7 @@ 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(LTE_RLC_SRC
+set(RLC_ENB_V1
   ${RLC_AM_DIR}/rlc_am.c
   ${RLC_AM_DIR}/rlc_am_init.c
   ${RLC_AM_DIR}/rlc_am_timer_poll_retransmit.c
@@ -1670,6 +1669,17 @@ set(LTE_RLC_SRC
   ${RLC_DIR}/rlc_mpls.c
   )
 
+set(RLC_ENB_V2
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/rlc_oai_api.c
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/asn1_utils.c
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/rlc_ue_manager.c
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/rlc_entity.c
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/rlc_entity_am.c
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/rlc_entity_um.c
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/rlc_pdu.c
+  ${OPENAIR2_DIR}/LAYER2/rlc_v2/rlc_sdu.c
+  )
+
 set(NR_RLC_SRC
   ${OPENAIR2_DIR}/LAYER2/nr_rlc/asn1_utils.c
   ${OPENAIR2_DIR}/LAYER2/nr_rlc/nr_rlc_entity.c
@@ -1691,6 +1701,7 @@ set(L2_SRC
   ${PDCP_DIR}/pdcp_util.c
   ${PDCP_DIR}/pdcp_security.c
   ${PDCP_DIR}/pdcp_netlink.c
+  ${RLC_ENB_V2}
 #  ${RRC_DIR}/rrc_UE.c
   ${RRC_DIR}/rrc_eNB.c
   ${RRC_DIR}/rrc_eNB_endc.c
@@ -1704,7 +1715,7 @@ set(L2_SRC
   )
 
 set(L2_LTE_SRC
-  ${LTE_RLC_SRC}
+  ${RLC_ENB_V1}
   )
 
 set(L2_NR_SRC
@@ -1741,7 +1752,7 @@ set(LTE_NR_L2_SRC_UE
   ${PDCP_DIR}/pdcp_util.c
   ${PDCP_DIR}/pdcp_security.c
   ${PDCP_DIR}/pdcp_netlink.c
-  ${LTE_RLC_SRC}
+  ${RLC_ENB_V1}
   )
 
 set(NR_L2_SRC_UE
diff --git a/common/utils/T/tracer/hacks/pilot_timeplot.sh b/common/utils/T/tracer/hacks/pilot_timeplot.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0d9c4694a627e97bd4d9570de6b21627893971a4
--- /dev/null
+++ b/common/utils/T/tracer/hacks/pilot_timeplot.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# use UP and DOWN arrow keys to scroll the view displayed by timeplot
+
+while read -n 1 key
+do
+  case "$key" in
+  'B' )
+    kill -SIGUSR1 `ps aux|grep timeplot|grep -v grep|grep -v sh|tr -s ' ' :|cut -f 2 -d :`
+  ;;
+  'A' )
+    kill -SIGUSR2 `ps aux|grep timeplot|grep -v grep|grep -v sh|tr -s ' ' :|cut -f 2 -d :`
+  ;;
+  esac
+done
diff --git a/common/utils/T/tracer/hacks/time_meas.c b/common/utils/T/tracer/hacks/time_meas.c
index 6bd29503011fb92e306dd10a7dd6539ebd0ee0f9..408189cd80c210d7ce0dcf7cb17a48244336a32f 100644
--- a/common/utils/T/tracer/hacks/time_meas.c
+++ b/common/utils/T/tracer/hacks/time_meas.c
@@ -13,7 +13,8 @@ void usage(void)
 "options:\n"
 "    -d <database file>        this option is mandatory\n"
 "    -ip <host>                connect to given IP address (default %s)\n"
-"    -p <port>                 connect to given port (default %d)\n",
+"    -p <port>                 connect to given port (default %d)\n"
+"    -e <event>                event to trace (default VCD_FUNCTION_ENB_DLSCH_ULSCH_SCHEDULER)\n",
   DEFAULT_REMOTE_IP,
   DEFAULT_REMOTE_PORT
   );
@@ -47,6 +48,7 @@ int main(int n, char **v)
   int ev_fun;
   int start_valid = 0;
   struct timespec start_time, stop_time, delta_time;
+  char *name = "VCD_FUNCTION_ENB_DLSCH_ULSCH_SCHEDULER";
 
   for (i = 1; i < n; i++) {
     if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) usage();
@@ -55,6 +57,7 @@ int main(int n, char **v)
     if (!strcmp(v[i], "-ip")) { if (i > n-2) usage(); ip = v[++i]; continue; }
     if (!strcmp(v[i], "-p"))
       { if (i > n-2) usage(); port = atoi(v[++i]); continue; }
+    if (!strcmp(v[i], "-e")) { if (i > n-2) usage(); name = v[++i]; continue; }
     usage();
   }
 
@@ -71,10 +74,9 @@ int main(int n, char **v)
   is_on = calloc(number_of_events, sizeof(int));
   if (is_on == NULL) abort();
 
-  on_off(database, "VCD_FUNCTION_ENB_DLSCH_ULSCH_SCHEDULER", is_on, 1);
+  on_off(database, name, is_on, 1);
 
-  ev_fun = event_id_from_name(database,
-      "VCD_FUNCTION_ENB_DLSCH_ULSCH_SCHEDULER");
+  ev_fun = event_id_from_name(database, name);
 
   socket = connect_to(ip, port);
 
diff --git a/common/utils/T/tracer/hacks/timeplot.c b/common/utils/T/tracer/hacks/timeplot.c
index 79905d16f99e75e5f17f6417f26afe9a9a61714e..a1ddb2a1c923ffc3df47efec0aad65e98a7a6095 100644
--- a/common/utils/T/tracer/hacks/timeplot.c
+++ b/common/utils/T/tracer/hacks/timeplot.c
@@ -28,6 +28,7 @@ int bins[50];
 
 #define N 1000
 int data[N];
+long start = 100;
 
 void plot(void)
 {
@@ -45,7 +46,8 @@ void plot(void)
     if (data[i] < vmin) vmin = data[i];
     if (data[i] > vmax) vmax = data[i];
     vavg += data[i];
-    int ms2 = data[i]/binsize_ns;
+    int ms2 = (data[i] - start * 1000)/binsize_ns;
+    if (ms2 < 0) ms2 = 0;
     if (ms2 > 49) ms2 = 49;
     bins[ms2]++;
     if (bins[ms2] > max) max = bins[ms2];
@@ -55,7 +57,7 @@ void plot(void)
 
   GOTO(1,1);
   for (i = 0; i < 50; i++) {
-    double binend = (i+1) * binsize_ns / 1000.;
+    double binend = (i+1) * binsize_ns / 1000. + start;
     int k;
     int width = bins[i] * 70 / max;
     /* force at least width of 1 if some point is there */
@@ -68,11 +70,23 @@ void plot(void)
   printf("min %d ns    max %d ns    avg %ld ns\n", vmin, vmax, vavg);
 }
 
+void up(int x)
+{
+  start += 5;
+}
+
+void down(int x)
+{
+  start -= 5;
+}
+
 int main(void)
 {
   int i;
   int pos = 0;
   signal(SIGINT, sig);
+  signal(SIGUSR1, up);
+  signal(SIGUSR2, down);
   RESET();
   HIDE_CURSOR();
   while (!feof(stdin)) {
diff --git a/openair2/COMMON/rrc_messages_def.h b/openair2/COMMON/rrc_messages_def.h
index 1d0c2dfdb85d4186180b9b5954409815c7f9d33a..cde2497880849bccbbbafb3c5e787a946eddde17 100644
--- a/openair2/COMMON/rrc_messages_def.h
+++ b/openair2/COMMON/rrc_messages_def.h
@@ -76,3 +76,6 @@ MESSAGE_DEF(NAS_DOWNLINK_DATA_IND,      MESSAGE_PRIORITY_MED,       NasDlDataInd
 
 // eNB: realtime -> RRC messages
 MESSAGE_DEF(RRC_SUBFRAME_PROCESS,       MESSAGE_PRIORITY_MED,       RrcSubframeProcess,         rrc_subframe_process)
+
+// eNB: RLC -> RRC messages
+MESSAGE_DEF(RLC_SDU_INDICATION,         MESSAGE_PRIORITY_MED,       RlcSduIndication,           rlc_sdu_indication)
diff --git a/openair2/COMMON/rrc_messages_types.h b/openair2/COMMON/rrc_messages_types.h
index 68e1753448b2c9a5bc7d5b0a9f70b4857853a0d7..7582de65ba4c9761f80c572254baaa21fede672c 100644
--- a/openair2/COMMON/rrc_messages_types.h
+++ b/openair2/COMMON/rrc_messages_types.h
@@ -87,6 +87,8 @@
 
 #define RRC_SUBFRAME_PROCESS(mSGpTR)    (mSGpTR)->ittiMsg.rrc_subframe_process
 
+#define RLC_SDU_INDICATION(mSGpTR)      (mSGpTR)->ittiMsg.rlc_sdu_indication
+
 //-------------------------------------------------------------------------------------------//
 typedef struct RrcStateInd_s {
   Rrc_State_t     state;
@@ -429,4 +431,12 @@ typedef struct rrc_subframe_process_s {
   int             CC_id;
 } RrcSubframeProcess;
 
+// eNB: RLC -> RRC messages
+typedef struct rlc_sdu_indication_s {
+  int rnti;
+  int is_successful;
+  int srb_id;
+  int message_id;
+} RlcSduIndication;
+
 #endif /* RRC_MESSAGES_TYPES_H_ */
diff --git a/openair2/LAYER2/rlc_v2/TODO b/openair2/LAYER2/rlc_v2/TODO
new file mode 100644
index 0000000000000000000000000000000000000000..0778d4320b888ac2cf9b695f0e3129863656fa2a
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/TODO
@@ -0,0 +1,18 @@
+RLC AM
+======
+
+- 36.322 5.4 Re-establishment procedure
+  when possible, reassemble RLC SDUs from any byte segments of AMD PDUs
+  with SN < VR(MR) in the receiving side, remove RLC headers when doing
+  so and deliver all reassembled RLC SDUs to upper layer in ascending order
+  of the RLC SN, if not delivered before;
+
+- 36.322 5.2.3 Status reporting
+  delay triggering the STATUS report until x < VR(MS) or x >= VR(MR)
+
+- 36.322 5.1.3.2.3 Actions when a RLC data PDU is placed in the reception
+  buffer
+  [...] and in-sequence byte segments of the AMD PDU with SN = VR(R) [...]
+
+- use SOstart/SOend in NACK reporting, do not NACK full PDU if
+  parts of it have been received
diff --git a/openair2/LAYER2/rlc_v2/asn1_utils.c b/openair2/LAYER2/rlc_v2/asn1_utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..46f7d90da57d2cb7d15cee8c60614a49a832e955
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/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/rlc_v2/asn1_utils.h b/openair2/LAYER2/rlc_v2/asn1_utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..61394c9c6991ccdc32722bfb039bfdac82a741ae
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/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/rlc_v2/rlc_entity.c b/openair2/LAYER2/rlc_v2/rlc_entity.c
new file mode 100644
index 0000000000000000000000000000000000000000..d774e2b7e17788f71a0edc178295f1a682488469
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_entity.c
@@ -0,0 +1,144 @@
+/*
+ * 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_entity.h"
+
+#include <stdlib.h>
+
+#include "rlc_entity_am.h"
+#include "rlc_entity_um.h"
+
+#include "LOG/log.h"
+
+rlc_entity_t *new_rlc_entity_am(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    void (*sdu_successful_delivery)(void *sdu_successful_delivery_data,
+                                    struct rlc_entity_t *entity,
+                                    int sdu_id),
+    void *sdu_successful_delivery_data,
+    void (*max_retx_reached)(void *max_retx_reached_data,
+                             struct rlc_entity_t *entity),
+    void *max_retx_reached_data,
+    int t_reordering,
+    int t_status_prohibit,
+    int t_poll_retransmit,
+    int poll_pdu,
+    int poll_byte,
+    int max_retx_threshold)
+{
+  rlc_entity_am_t *ret;
+
+  ret = calloc(1, sizeof(rlc_entity_am_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  ret->common.recv_pdu      = rlc_entity_am_recv_pdu;
+  ret->common.buffer_status = rlc_entity_am_buffer_status;
+  ret->common.generate_pdu  = rlc_entity_am_generate_pdu;
+
+  ret->common.recv_sdu         = rlc_entity_am_recv_sdu;
+
+  ret->common.set_time = rlc_entity_am_set_time;
+
+  ret->common.discard_sdu = rlc_entity_am_discard_sdu;
+
+  ret->common.reestablishment = rlc_entity_am_reestablishment;
+
+  ret->common.delete = 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;
+
+  ret->rx_maxsize         = rx_maxsize;
+  ret->tx_maxsize         = tx_maxsize;
+  ret->t_reordering       = t_reordering;
+  ret->t_status_prohibit  = t_status_prohibit;
+  ret->t_poll_retransmit  = t_poll_retransmit;
+  ret->poll_pdu           = poll_pdu;
+  ret->poll_byte          = poll_byte;
+  ret->max_retx_threshold = max_retx_threshold;
+
+  return (rlc_entity_t *)ret;
+}
+
+rlc_entity_t *new_rlc_entity_um(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    int t_reordering,
+    int sn_field_length)
+{
+  rlc_entity_um_t *ret;
+
+  ret = calloc(1, sizeof(rlc_entity_um_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  ret->common.recv_pdu      = rlc_entity_um_recv_pdu;
+  ret->common.buffer_status = rlc_entity_um_buffer_status;
+  ret->common.generate_pdu  = rlc_entity_um_generate_pdu;
+
+  ret->common.recv_sdu         = rlc_entity_um_recv_sdu;
+
+  ret->common.set_time = rlc_entity_um_set_time;
+
+  ret->common.discard_sdu = rlc_entity_um_discard_sdu;
+
+  ret->common.reestablishment = rlc_entity_um_reestablishment;
+
+  ret->common.delete = rlc_entity_um_delete;
+
+  ret->common.deliver_sdu      = deliver_sdu;
+  ret->common.deliver_sdu_data = deliver_sdu_data;
+
+  ret->sn_field_length    = sn_field_length;
+  ret->rx_maxsize         = rx_maxsize;
+  ret->tx_maxsize         = tx_maxsize;
+  ret->t_reordering       = t_reordering;
+
+  if (sn_field_length == 5)
+    ret->sn_modulus = 32;
+  else if (sn_field_length == 10)
+    ret->sn_modulus = 1024;
+  else {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  ret->window_size = ret->sn_modulus / 2;
+
+  return (rlc_entity_t *)ret;
+}
diff --git a/openair2/LAYER2/rlc_v2/rlc_entity.h b/openair2/LAYER2/rlc_v2/rlc_entity.h
new file mode 100644
index 0000000000000000000000000000000000000000..c9b35204f03e92d305dc0bba1b40e4d36bd8964e
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_entity.h
@@ -0,0 +1,97 @@
+/*
+ * 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 _RLC_ENTITY_H_
+#define _RLC_ENTITY_H_
+
+#include <stdint.h>
+
+#define SDU_MAX 16000   /* maximum PDCP SDU size is 8188, let's take more */
+
+typedef struct {
+  int status_size;
+  int tx_size;
+  int retx_size;
+} rlc_entity_buffer_status_t;
+
+typedef struct rlc_entity_t {
+  /* functions provided by the RLC module */
+  void (*recv_pdu)(struct rlc_entity_t *entity, char *buffer, int size);
+  rlc_entity_buffer_status_t (*buffer_status)(
+      struct rlc_entity_t *entity, int maxsize);
+  int (*generate_pdu)(struct rlc_entity_t *entity, char *buffer, int size);
+
+  void (*recv_sdu)(struct rlc_entity_t *entity, char *buffer, int size,
+                   int sdu_id);
+
+  void (*set_time)(struct rlc_entity_t *entity, uint64_t now);
+
+  void (*discard_sdu)(struct rlc_entity_t *entity, int sdu_id);
+
+  void (*reestablishment)(struct rlc_entity_t *entity);
+
+  void (*delete)(struct rlc_entity_t *entity);
+
+  /* callbacks provided to the RLC module */
+  void (*deliver_sdu)(void *deliver_sdu_data, struct rlc_entity_t *entity,
+                      char *buf, int size);
+  void *deliver_sdu_data;
+
+  void (*sdu_successful_delivery)(void *sdu_successful_delivery_data,
+                                  struct rlc_entity_t *entity,
+                                  int sdu_id);
+  void *sdu_successful_delivery_data;
+
+  void (*max_retx_reached)(void *max_retx_reached_data,
+                           struct rlc_entity_t *entity);
+  void *max_retx_reached_data;
+} rlc_entity_t;
+
+rlc_entity_t *new_rlc_entity_am(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    void (*sdu_successful_delivery)(void *sdu_successful_delivery_data,
+                                    struct rlc_entity_t *entity,
+                                    int sdu_id),
+    void *sdu_successful_delivery_data,
+    void (*max_retx_reached)(void *max_retx_reached_data,
+                             struct rlc_entity_t *entity),
+    void *max_retx_reached_data,
+    int t_reordering,
+    int t_status_prohibit,
+    int t_poll_retransmit,
+    int poll_pdu,
+    int poll_byte,
+    int max_retx_threshold);
+
+rlc_entity_t *new_rlc_entity_um(
+    int rx_maxsize,
+    int tx_maxsize,
+    void (*deliver_sdu)(void *deliver_sdu_data, struct rlc_entity_t *entity,
+                      char *buf, int size),
+    void *deliver_sdu_data,
+    int t_reordering,
+    int sn_field_length);
+
+#endif /* _RLC_ENTITY_H_ */
diff --git a/openair2/LAYER2/rlc_v2/rlc_entity_am.c b/openair2/LAYER2/rlc_v2/rlc_entity_am.c
new file mode 100644
index 0000000000000000000000000000000000000000..5b3df3ec75e4d767b4cf7cd3f85ef3f54217810d
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_entity_am.c
@@ -0,0 +1,1694 @@
+/*
+ * 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_entity_am.h"
+#include "rlc_pdu.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+/*************************************************************************/
+/* PDU RX functions                                                      */
+/*************************************************************************/
+
+static int modulus_rx(rlc_entity_am_t *entity, int a)
+{
+  /* as per 36.322 7.1, modulus base is vr(r) and modulus is 1024 for rx */
+  int r = a - entity->vr_r;
+  if (r < 0) r += 1024;
+  return r;
+}
+
+/* used in both RX and TX processing */
+static int modulus_tx(rlc_entity_am_t *entity, int a)
+{
+  /* as per 36.322 7.1, modulus base is vt(a) and modulus is 1024 for tx */
+  int r = a - entity->vt_a;
+  if (r < 0) r += 1024;
+  return r;
+}
+
+static int sn_in_recv_window(void *_entity, int sn)
+{
+  rlc_entity_am_t *entity = _entity;
+  int mod_sn = modulus_rx(entity, sn);
+  /* we simplify vr(r)<=sn<vr(mr). base is vr(r) and vr(mr) = vr(r) + 512 */
+  return mod_sn < 512;
+}
+
+static int sn_compare_rx(void *_entity, int a, int b)
+{
+  rlc_entity_am_t *entity = _entity;
+  return modulus_rx(entity, a) - modulus_rx(entity, b);
+}
+
+/* used in both RX and TX processing */
+static int sn_compare_tx(void *_entity, int a, int b)
+{
+  rlc_entity_am_t *entity = _entity;
+  return modulus_tx(entity, a) - modulus_tx(entity, b);
+}
+
+static int segment_already_received(rlc_entity_am_t *entity,
+    int sn, int so, int data_size)
+{
+  /* TODO: optimize */
+  rlc_rx_pdu_segment_t *l = entity->rx_list;
+
+  while (l != NULL) {
+    if (l->sn == sn && l->so <= so &&
+        l->so + l->size - l->data_offset >= so + data_size)
+      return 1;
+    l = l->next;
+  }
+
+  return 0;
+}
+
+static int rlc_am_segment_full(rlc_entity_am_t *entity, int sn)
+{
+  rlc_rx_pdu_segment_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;
+  }
+  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 - l->data_offset - 1;
+    if (new_last_byte > last_byte)
+      last_byte = new_last_byte;
+    l = l->next;
+  }
+  return 0;
+}
+
+/* return 1 if the new segment has some data to consume, 0 if not */
+static int rlc_am_reassemble_next_segment(rlc_am_reassemble_t *r)
+{
+  int rf;
+  int sn;
+
+  r->sdu_offset = r->start->data_offset;
+
+  rlc_pdu_decoder_init(&r->dec, r->start->data, r->start->size);
+
+  rlc_pdu_decoder_get_bits(&r->dec, 1);            /* dc */
+  rf    = rlc_pdu_decoder_get_bits(&r->dec, 1);
+  rlc_pdu_decoder_get_bits(&r->dec, 1);            /* p */
+  r->fi = rlc_pdu_decoder_get_bits(&r->dec, 2);
+  r->e  = rlc_pdu_decoder_get_bits(&r->dec, 1);
+  sn    = rlc_pdu_decoder_get_bits(&r->dec, 10);
+  if (rf) {
+    rlc_pdu_decoder_get_bits(&r->dec, 1);          /* lsf */
+    r->so = rlc_pdu_decoder_get_bits(&r->dec, 15);
+  } else {
+    r->so = 0;
+  }
+
+  if (r->e) {
+    r->e       = rlc_pdu_decoder_get_bits(&r->dec, 1);
+    r->sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
+  } else
+    r->sdu_len = r->start->size - r->sdu_offset;
+
+  /* new sn: read starts from PDU byte 0 */
+  if (sn != r->sn) {
+    r->pdu_byte = 0;
+    r->sn = sn;
+  }
+
+  r->data_pos = r->start->data_offset + r->pdu_byte - r->so;
+
+  /* TODO: remove this check, it is useless, data has been validated before */
+  if (r->pdu_byte < r->so) {
+    LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+
+  /* if pdu_byte is not in [so .. so+len-1] then all bytes from this segment
+   * have already been consumed
+   */
+  if (r->pdu_byte >= r->so + r->start->size - r->start->data_offset)
+    return 0;
+
+  /* go to correct SDU */
+  while (r->pdu_byte >= r->so + (r->sdu_offset - r->start->data_offset) + r->sdu_len) {
+    r->sdu_offset += r->sdu_len;
+    if (r->e) {
+      r->e       = rlc_pdu_decoder_get_bits(&r->dec, 1);
+      r->sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
+    } else {
+      r->sdu_len = r->start->size - r->sdu_offset;
+    }
+  }
+
+  return 1;
+}
+
+static void rlc_am_reassemble(rlc_entity_am_t *entity)
+{
+  rlc_am_reassemble_t *r = &entity->reassemble;
+
+  while (r->start != NULL) {
+    if (r->sdu_pos >= SDU_MAX) {
+      /* TODO: proper error handling (discard PDUs with current sn from
+       * reassembly queue? something else?)
+       */
+      LOG_E(RLC, "%s:%d:%s: bad RLC PDU\n", __FILE__, __LINE__, __FUNCTION__);
+      exit(1);
+    }
+    r->sdu[r->sdu_pos] = r->start->data[r->data_pos];
+    r->sdu_pos++;
+    r->data_pos++;
+    r->pdu_byte++;
+    if (r->data_pos == r->sdu_offset + r->sdu_len) {
+      /* all bytes of SDU are consumed, check if SDU is fully there.
+       * It is if the data pointer is not at the end of the PDU segment
+       * or if 'fi' & 1 == 0
+       */
+      if (r->data_pos != r->start->size ||
+          (r->fi & 1) == 0) {
+        /* SDU is full - deliver to higher layer */
+        entity->common.deliver_sdu(entity->common.deliver_sdu_data,
+                                   (rlc_entity_t *)entity,
+                                   r->sdu, r->sdu_pos);
+        r->sdu_pos = 0;
+      }
+      if (r->data_pos != r->start->size) {
+        /* not at the end, process next SDU */
+        r->sdu_offset += r->sdu_len;
+        if (r->e) {
+          r->e       = rlc_pdu_decoder_get_bits(&r->dec, 1);
+          r->sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
+        } else
+          r->sdu_len = r->start->size - r->sdu_offset;
+      } else {
+        /* all bytes are consumend, go to next segment not already fully
+         * processed, if any
+         */
+        do {
+          rlc_rx_pdu_segment_t *e = r->start;
+          entity->rx_size -= e->size;
+          r->start = r->start->next;
+          rlc_rx_free_pdu_segment(e);
+        } while (r->start != NULL && !rlc_am_reassemble_next_segment(r));
+      }
+    }
+  }
+}
+
+static void rlc_am_reception_actions(rlc_entity_am_t *entity,
+    rlc_rx_pdu_segment_t *pdu_segment)
+{
+  int x = pdu_segment->sn;
+  int vr_ms;
+  int vr_r;
+
+  if (modulus_rx(entity, x) >= modulus_rx(entity, entity->vr_h))
+    entity->vr_h = (x + 1) % 1024;
+
+  vr_ms = entity->vr_ms;
+  while (rlc_am_segment_full(entity, vr_ms))
+    vr_ms = (vr_ms + 1) % 1024;
+  entity->vr_ms = vr_ms;
+
+  if (x == entity->vr_r) {
+    vr_r = entity->vr_r;
+    while (rlc_am_segment_full(entity, vr_r)) {
+      /* move segments with sn=vr(r) from rx list to end of reassembly list */
+      while (entity->rx_list != NULL && entity->rx_list->sn == vr_r) {
+        rlc_rx_pdu_segment_t *e = entity->rx_list;
+        entity->rx_list = e->next;
+        e->next = NULL;
+        if (entity->reassemble.start == NULL) {
+          entity->reassemble.start = e;
+          /* the list was empty, we need to init decoder */
+          entity->reassemble.sn = -1;
+          if (!rlc_am_reassemble_next_segment(&entity->reassemble)) {
+            /* TODO: proper error recovery (or remove the test, it should not happen) */
+            LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
+            exit(1);
+          }
+        } else {
+          entity->reassemble.end->next = e;
+        }
+        entity->reassemble.end = e;
+      }
+
+      /* update vr_r */
+      vr_r = (vr_r + 1) % 1024;
+    }
+    entity->vr_r = vr_r;
+  }
+
+  rlc_am_reassemble(entity);
+
+  if (entity->t_reordering_start) {
+    int vr_x = entity->vr_x;
+    if (vr_x < entity->vr_r) vr_x += 1024;
+    if (vr_x == entity->vr_r || vr_x > entity->vr_r + 512)
+      entity->t_reordering_start = 0;
+  }
+
+  if (entity->t_reordering_start == 0) {
+    if (sn_compare_rx(entity, entity->vr_h, entity->vr_r) > 0) {
+      entity->t_reordering_start = entity->t_current;
+      entity->vr_x = entity->vr_h;
+    }
+  }
+}
+
+static void process_received_ack(rlc_entity_am_t *entity, int sn)
+{
+  rlc_tx_pdu_segment_t head;
+  rlc_tx_pdu_segment_t *cur;
+  rlc_tx_pdu_segment_t *prev;
+
+  /* put all PDUs from wait and retransmit lists with SN < '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->sn, sn) < 0) {
+      /* remove from wait list */
+      prev->next = cur->next;
+      /* put the PDU in the ack list */
+      entity->ack_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
+                                             entity->ack_list, cur);
+      cur = prev->next;
+    } else {
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->wait_list = head.next;
+
+  /* process retransmit list */
+  head.next = entity->retransmit_list;
+  prev = &head;
+  cur = entity->retransmit_list;
+  while (cur != NULL) {
+    if (sn_compare_tx(entity, cur->sn, sn) < 0) {
+      /* dec. retx_count in case we put this segment back in retransmit list
+       * in 'process_received_nack'
+       */
+      cur->retx_count--;
+      /* remove from retransmit list */
+      prev->next = cur->next;
+      /* put the PDU in the ack list */
+      entity->ack_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
+                                             entity->ack_list, cur);
+      cur = prev->next;
+    } else {
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->retransmit_list = head.next;
+
+}
+
+static void consider_retransmission(rlc_entity_am_t *entity,
+    rlc_tx_pdu_segment_t *cur)
+{
+  cur->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 (cur->retx_count >= entity->max_retx_threshold) {
+    entity->common.max_retx_reached(entity->common.max_retx_reached_data,
+                                    (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)
+   */
+  entity->retransmit_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
+                                                entity->retransmit_list, cur);
+}
+
+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_received_nack(rlc_entity_am_t *entity, int sn,
+    int so_start, int so_end)
+{
+  /* put all PDU 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 PDU segments should all be in ack list when calling
+   * the current function? in doubt let's accept anything and thus process
+   * also wait list.
+   */
+  rlc_tx_pdu_segment_t head;
+  rlc_tx_pdu_segment_t *cur;
+  rlc_tx_pdu_segment_t *prev;
+
+  /* check that VT(A) <= sn < VT(S) */
+  if (!(sn_compare_tx(entity, entity->vt_a, sn) <= 0 &&
+        sn_compare_tx(entity, sn, entity->vt_s) < 0))
+    return;
+
+  /* process wait list */
+  head.next = entity->wait_list;
+  prev = &head;
+  cur = entity->wait_list;
+  while (cur != NULL) {
+    if (cur->sn == sn &&
+        so_overlap(so_start, so_end, cur->so, cur->so + cur->data_size - 1)) {
+      /* remove from wait list */
+      prev->next = cur->next;
+      /* consider the PDU segment for retransmission */
+      consider_retransmission(entity, cur);
+      cur = prev->next;
+    } else {
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->wait_list = head.next;
+
+  /* process ack list */
+  head.next = entity->ack_list;
+  prev = &head;
+  cur = entity->ack_list;
+  while (cur != NULL) {
+    if (cur->sn == sn &&
+        so_overlap(so_start, so_end, cur->so, cur->so + cur->data_size - 1)) {
+      /* remove from ack list */
+      prev->next = cur->next;
+      /* consider the PDU segment for retransmission */
+      consider_retransmission(entity, cur);
+      cur = prev->next;
+    } else {
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+  entity->ack_list = head.next;
+}
+
+int tx_pdu_in_ack_list_full(rlc_tx_pdu_segment_t *pdu)
+{
+  int sn = pdu->sn;
+  int last_byte = -1;
+  int new_last_byte;
+  int is_last_seen = 0;
+
+  while (pdu != NULL && pdu->sn == sn) {
+    if (pdu->so > last_byte + 1) return 0;
+    if (pdu->is_last)
+      is_last_seen = 1;
+    new_last_byte = pdu->so + pdu->data_size - 1;
+    if (new_last_byte > last_byte)
+      last_byte = new_last_byte;
+    pdu = pdu->next;
+  }
+
+  return is_last_seen == 1;
+}
+
+int tx_pdu_in_ack_list_size(rlc_tx_pdu_segment_t *pdu)
+{
+  int sn = pdu->sn;
+  int ret = 0;
+
+  while (pdu != NULL && pdu->sn == sn) {
+    ret += pdu->data_size;
+    pdu = pdu->next;
+  }
+
+  return ret;
+}
+
+void ack_sdu_bytes(rlc_sdu_t *start, int start_byte, int sdu_size)
+{
+  rlc_sdu_t *cur = start;
+  int remaining_size = sdu_size;
+
+  while (remaining_size) {
+    int cursize = cur->size - start_byte;
+    if (cursize > remaining_size)
+      cursize = remaining_size;
+    cur->acked_bytes += cursize;
+    remaining_size -= cursize;
+    /* start_byte is only meaningful for the 1st SDU, then it is 0 */
+    start_byte = 0;
+    cur = cur->next;
+  }
+}
+
+rlc_tx_pdu_segment_t *tx_list_remove_sn(rlc_tx_pdu_segment_t *list, int sn)
+{
+  rlc_tx_pdu_segment_t head;
+  rlc_tx_pdu_segment_t *cur;
+  rlc_tx_pdu_segment_t *prev;
+
+  head.next = list;
+  cur = list;
+  prev = &head;
+
+  while (cur != NULL) {
+    if (cur->sn == sn) {
+      prev->next = cur->next;
+      rlc_tx_free_pdu(cur);
+      cur = prev->next;
+    } else {
+      prev = cur;
+      cur = cur->next;
+    }
+  }
+
+  return head.next;
+}
+
+void cleanup_sdu_list(rlc_entity_am_t *entity)
+{
+  rlc_sdu_t head;
+  rlc_sdu_t *cur;
+  rlc_sdu_t *prev;
+
+  /* remove fully acked SDUs, indicate successful delivery to upper layer */
+  head.next = entity->tx_list;
+  cur = entity->tx_list;
+  prev = &head;
+
+  while (cur != NULL) {
+    if (cur->acked_bytes == cur->size) {
+      prev->next = cur->next;
+      entity->tx_size -= cur->size;
+      entity->common.sdu_successful_delivery(
+          entity->common.sdu_successful_delivery_data,
+          (rlc_entity_t *)entity, cur->upper_layer_id);
+      rlc_free_sdu(cur);
+      entity->tx_end = prev;
+      cur = prev->next;
+    } else {
+      entity->tx_end = cur;
+      cur = cur->next;
+    }
+  }
+
+  entity->tx_list = head.next;
+
+  /* if tx_end == head then it means that the list is now empty */
+  if (entity->tx_end == &head)
+    entity->tx_end = NULL;
+}
+
+static void finalize_ack_nack_processing(rlc_entity_am_t *entity)
+{
+  int sn;
+  rlc_tx_pdu_segment_t *cur = entity->ack_list;
+  int pdu_size;
+
+  if (cur == NULL)
+    return;
+
+  /* Remove full PDUs and ack the SDU bytes they cover. Start from SN == VT(A)
+   * and process increasing SNs until end of list or missing ACK or PDU not
+   * fully ACKed.
+   */
+  while (cur != NULL && cur->sn == entity->vt_a &&
+         tx_pdu_in_ack_list_full(cur)) {
+    sn = cur->sn;
+    entity->vt_a = (entity->vt_a + 1) % 1024;
+    pdu_size = tx_pdu_in_ack_list_size(cur);
+    ack_sdu_bytes(cur->start_sdu, cur->sdu_start_byte, pdu_size);
+    while (cur != NULL && cur->sn == sn)
+      cur = cur->next;
+    entity->ack_list = tx_list_remove_sn(entity->ack_list, sn);
+  }
+
+  cleanup_sdu_list(entity);
+}
+
+void rlc_entity_am_recv_pdu(rlc_entity_t *_entity, char *buffer, int size)
+{
+#define R(d) do { if (rlc_pdu_decoder_in_error(&d)) goto err; } while (0)
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  rlc_pdu_decoder_t decoder;
+  rlc_pdu_decoder_t data_decoder;
+  rlc_pdu_decoder_t control_decoder;
+
+  int dc;
+  int rf;
+  int p = 0;
+  int fi;
+  int e;
+  int sn;
+  int lsf;
+  int so;
+
+  int cpt;
+  int e1;
+  int e2;
+  int ack_sn;
+  int nack_sn;
+  int so_start;
+  int so_end;
+  int control_e1;
+  int control_e2;
+
+  int data_e;
+  int data_li;
+
+  int packet_count;
+  int data_size;
+  int data_start;
+  int indicated_data_size;
+
+  rlc_rx_pdu_segment_t *pdu_segment;
+
+  rlc_pdu_decoder_init(&decoder, buffer, size);
+  dc = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  if (dc == 0) goto control;
+
+  /* data PDU */
+  rf = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  p  = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  fi = rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
+  e  = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  sn = rlc_pdu_decoder_get_bits(&decoder, 10); R(decoder);
+
+  /* dicard PDU if rx buffer is full */
+  if (entity->rx_size + 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 vr_r %d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+           sn, entity->vr_r);
+    goto discard;
+  }
+
+  if (rf) {
+    lsf = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+    so  = rlc_pdu_decoder_get_bits(&decoder, 15); R(decoder);
+  } else {
+    lsf = 1;
+    so = 0;
+  }
+
+  packet_count = 1;
+
+  /* go to start of data */
+  indicated_data_size = 0;
+  data_decoder = decoder;
+  data_e = e;
+  while (data_e) {
+    data_e = rlc_pdu_decoder_get_bits(&data_decoder, 1); R(data_decoder);
+    data_li = rlc_pdu_decoder_get_bits(&data_decoder, 11); R(data_decoder);
+    if (data_li == 0) {
+      LOG_D(RLC, "%s:%d:%s: warning: discard PDU, li == 0\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      goto discard;
+    }
+    indicated_data_size += data_li;
+    packet_count++;
+  }
+  rlc_pdu_decoder_align(&data_decoder);
+
+  data_start = data_decoder.byte;
+  data_size = size - data_start;
+
+  if (data_size <= 0) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, wrong data size (sum of LI %d data size %d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+           indicated_data_size, data_size);
+    goto discard;
+  }
+  if (indicated_data_size >= data_size) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, bad LIs (sum of LI %d data size %d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+           indicated_data_size, data_size);
+    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;
+  }
+
+  char *fi_str[] = {
+    "first byte: YES  last byte: YES",
+    "first byte: YES  last byte: NO",
+    "first byte: NO   last byte: YES",
+    "first byte: NO   last byte: NO",
+  };
+
+  LOG_D(RLC, "found %d packets, data size %d data start %d [fi %d %s] (sn %d) (p %d)\n",
+        packet_count, data_size, data_decoder.byte, fi, fi_str[fi], sn, p);
+
+  /* put in pdu reception list */
+  entity->rx_size += size;
+  pdu_segment = rlc_rx_new_pdu_segment(sn, so, size, lsf, buffer, data_start);
+  entity->rx_list = rlc_rx_pdu_segment_list_add(sn_compare_rx, entity,
+                                                entity->rx_list, pdu_segment);
+
+  /* do reception actions (36.322 5.1.3.2.3) */
+  rlc_am_reception_actions(entity, pdu_segment);
+
+  if (p) {
+    /* 36.322 5.2.3 says status triggering should be delayed
+     * until x < VR(MS) or x >= VR(MR). This is not clear (what
+     * is x then? we keep the same?). So let's trigger no matter what.
+     */
+    int vr_mr = (entity->vr_r + 512) % 1024;
+    entity->status_triggered = 1;
+    if (!(sn_compare_rx(entity, sn, entity->vr_ms) < 0 ||
+          sn_compare_rx(entity, sn, vr_mr) >= 0)) {
+      LOG_D(RLC, "%s:%d:%s: warning: STATUS trigger should be delayed, according to specs\n",
+            __FILE__, __LINE__, __FUNCTION__);
+    }
+  }
+
+  return;
+
+control:
+  cpt = 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 = rlc_pdu_decoder_get_bits(&decoder, 10); R(decoder);
+  e1 = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+
+  /* let's try to parse the control PDU once to check consistency */
+  control_decoder = decoder;
+  control_e1 = e1;
+  while (control_e1) {
+    rlc_pdu_decoder_get_bits(&control_decoder, 10); R(control_decoder); /* NACK_SN */
+    control_e1 = rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
+    control_e2 = rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
+    if (control_e2) {
+      rlc_pdu_decoder_get_bits(&control_decoder, 15); R(control_decoder); /* SOstart */
+      rlc_pdu_decoder_get_bits(&control_decoder, 15); R(control_decoder); /* SOend */
+    }
+  }
+
+  /* 36.322 5.2.2.2 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);
+
+  while (e1) {
+    nack_sn = rlc_pdu_decoder_get_bits(&decoder, 10); R(decoder);
+    e1 = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+    e2 = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+    if (e2) {
+      so_start = rlc_pdu_decoder_get_bits(&decoder, 15); R(decoder);
+      so_end = rlc_pdu_decoder_get_bits(&decoder, 15); 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 0x7fff indicates 'all bytes to the end' */
+      if (so_end == 0x7fff)
+        so_end = -1;
+    } else {
+      so_start = 0;
+      so_end = -1;
+    }
+    process_received_nack(entity, nack_sn, so_start, so_end);
+
+    /* 36.322 5.2.2.2 says to stop t_poll_retransmit if a ACK or NACK is
+     * received for the SN 'poll_sn'
+     */
+    if (entity->poll_sn == nack_sn)
+      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 pdu_size(rlc_entity_am_t *entity, rlc_tx_pdu_segment_t *pdu)
+{
+  int header_size;
+  int sdu_count;
+  int data_size;
+  int li_bits;
+  rlc_sdu_t *sdu;
+
+  header_size = 2;
+  if (pdu->is_segment)
+    header_size += 2;
+
+  data_size = pdu->data_size;
+
+  sdu = pdu->start_sdu;
+
+  sdu_count = 1;
+  data_size -= sdu->size - pdu->sdu_start_byte;
+  sdu = sdu->next;
+
+  while (data_size > 0) {
+    sdu_count++;
+    data_size -= sdu->size;
+    sdu = sdu->next;
+  }
+
+  li_bits = 12 * (sdu_count - 1);
+  header_size += (li_bits + 7) / 8;
+
+  return header_size + pdu->data_size;
+}
+
+static int header_size(int sdu_count)
+{
+  int bits = 16 + 12 * (sdu_count - 1);
+  /* padding if we have to */
+  return (bits + 7) / 8;
+}
+
+typedef struct {
+  int sdu_count;
+  int data_size;
+  int header_size;
+} tx_pdu_size_t;
+
+static tx_pdu_size_t compute_new_pdu_size(rlc_entity_am_t *entity, int maxsize)
+{
+  tx_pdu_size_t ret;
+  int sdu_count;
+  int sdu_size;
+  int pdu_data_size;
+  rlc_sdu_t *sdu;
+
+  int vt_ms = (entity->vt_a + 512) % 1024;
+
+  ret.sdu_count = 0;
+  ret.data_size = 0;
+  ret.header_size = 0;
+
+  /* sn out of window? nothing to do */
+  if (!(sn_compare_tx(entity, entity->vt_s, entity->vt_a) >= 0 &&
+        sn_compare_tx(entity, entity->vt_s, vt_ms) < 0))
+    return ret;
+
+  /* TX PDU - let's make the biggest PDU we can with the SDUs we have */
+  sdu_count = 0;
+  pdu_data_size = 0;
+  sdu = entity->tx_list;
+  while (sdu != NULL) {
+    /* include SDU only if it has not been fully included in PDUs already */
+    if (sdu->next_byte != sdu->size) {
+      int new_header_size = header_size(sdu_count + 1);
+      /* if we cannot put new header + at least 1 byte of data then over */
+      if (new_header_size + pdu_data_size + 1 > maxsize)
+        break;
+      sdu_count++;
+      /* only include the bytes of this SDU not included in PDUs already */
+      sdu_size = sdu->size - sdu->next_byte;
+      /* don't feed more than 'maxsize' bytes */
+      if (new_header_size + pdu_data_size + sdu_size > maxsize)
+        sdu_size = maxsize - new_header_size - pdu_data_size;
+      pdu_data_size += sdu_size;
+      /* if we put more than 2^11-1 bytes then the LI field cannot be used,
+       * so this is the last SDU we can put
+       */
+      if (sdu_size > 2047)
+        break;
+    }
+    sdu = sdu->next;
+  }
+
+  if (sdu_count) {
+    ret.sdu_count = sdu_count;
+    ret.data_size = pdu_data_size;
+    ret.header_size = header_size(sdu_count);
+  }
+
+  return ret;
+}
+
+static int status_size(rlc_entity_am_t *entity, int maxsize)
+{
+  /* let's count bits */
+  int bits = 15;               /* minimum size is 15 (header+ack_sn+e1) */
+  int sn;
+
+  maxsize *= 8;
+
+  if (bits > maxsize) {
+    LOG_W(RLC, "%s:%d:%s: warning: cannot generate status PDU, not enough room\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    return 0;
+  }
+
+  /* each NACK adds 12 bits */
+  sn = entity->vr_r;
+  while (bits + 12 <= maxsize && sn_compare_rx(entity, sn, entity->vr_ms) < 0) {
+    if (!(rlc_am_segment_full(entity, sn)))
+      bits += 12;
+    sn = (sn + 1) % 1024;
+  }
+
+  return (bits + 7) / 8;
+}
+
+static int generate_status(rlc_entity_am_t *entity, char *buffer, int size)
+{
+  /* let's count bits */
+  int bits = 15;               /* minimum size is 15 (header+ack_sn+e1) */
+  int sn;
+  rlc_pdu_encoder_t encoder;
+  int has_nack = 0;
+  int ack;
+
+  rlc_pdu_encoder_init(&encoder, buffer, size);
+
+  size *= 8;
+
+  if (bits > size) {
+    LOG_W(RLC, "%s:%d:%s: warning: cannot generate status PDU, not enough room\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    return 0;
+  }
+
+  /* header */
+  rlc_pdu_encoder_put_bits(&encoder, 0, 1);   /* D/C */
+  rlc_pdu_encoder_put_bits(&encoder, 0, 3);   /* CPT */
+
+  /* reserve room for ACK (it will be set after putting the NACKs) */
+  rlc_pdu_encoder_put_bits(&encoder, 0, 10);
+
+  /* at this point, ACK is VR(R) */
+  ack = entity->vr_r;
+
+  /* each NACK adds 12 bits */
+  sn = entity->vr_r;
+  while (bits + 12 <= size && sn_compare_rx(entity, sn, entity->vr_ms) < 0) {
+    if (!(rlc_am_segment_full(entity, sn))) {
+      /* put previous e1 (is 1) */
+      rlc_pdu_encoder_put_bits(&encoder, 1, 1);
+      /* if previous was NACK, put previous e2 (0, we don't do 'so' thing) */
+      if (has_nack)
+        rlc_pdu_encoder_put_bits(&encoder, 0, 1);
+      /* put NACKed sn */
+      rlc_pdu_encoder_put_bits(&encoder, sn, 10);
+      has_nack = 1;
+      bits += 12;
+    } else {
+      /* this sn is full and we put all NACKs before it, use it for ACK */
+      ack = (sn + 1) % 1024;
+    }
+    sn = (sn + 1) % 1024;
+  }
+
+  /* go to highest full sn+1 for ACK, VR(MS) is the limit */
+  while (sn_compare_rx(entity, sn, entity->vr_ms) < 0 &&
+         rlc_am_segment_full(entity, sn)) {
+    ack = (sn + 1) % 1024;
+    sn = (sn + 1) % 1024;
+  }
+
+  /* at this point, if last put was NACK then put 2 bits else put 1 bit */
+  if (has_nack)
+    rlc_pdu_encoder_put_bits(&encoder, 0, 2);
+  else
+    rlc_pdu_encoder_put_bits(&encoder, 0, 1);
+
+  rlc_pdu_encoder_align(&encoder);
+
+  /* let's put the ACK */
+  buffer[0] |= ack >> 6;
+  buffer[1] |= (ack & 0x3f) << 2;
+
+  /* reset the trigger */
+  entity->status_triggered = 0;
+
+  /* start t_status_prohibit */
+  entity->t_status_prohibit_start = entity->t_current;
+
+  return encoder.byte;
+}
+
+int transmission_buffer_empty(rlc_entity_am_t *entity)
+{
+  rlc_sdu_t *sdu;
+
+  /* is transmission buffer empty? */
+  sdu = entity->tx_list;
+  while (sdu != NULL) {
+    if (sdu->next_byte != sdu->size)
+      return 0;
+    sdu = sdu->next;
+  }
+  return 1;
+}
+
+int check_poll_after_pdu_assembly(rlc_entity_am_t *entity)
+{
+  int retransmission_buffer_empty;
+  int window_stalling;
+  int vt_ms;
+
+  /* is retransmission buffer empty? */
+  if (entity->retransmit_list == NULL)
+    retransmission_buffer_empty = 1;
+  else
+    retransmission_buffer_empty = 0;
+
+  /* is window stalling? */
+  vt_ms = (entity->vt_a + 512) % 1024;
+  if (!(sn_compare_tx(entity, entity->vt_s, entity->vt_a) >= 0 &&
+        sn_compare_tx(entity, entity->vt_s, vt_ms) < 0))
+    window_stalling = 1;
+  else
+    window_stalling = 0;
+
+  return (transmission_buffer_empty(entity) && retransmission_buffer_empty) ||
+         window_stalling;
+}
+
+void include_poll(rlc_entity_am_t *entity, char *buffer)
+{
+  /* set the P bit to 1 */
+  buffer[0] |= 0x20;
+
+  entity->pdu_without_poll = 0;
+  entity->byte_without_poll = 0;
+
+  /* set POLL_SN to VT(S) - 1 */
+  entity->poll_sn = (entity->vt_s + 1023) % 1024;
+
+  /* start t_poll_retransmit */
+  entity->t_poll_retransmit_start = entity->t_current;
+}
+
+static int serialize_pdu(rlc_entity_am_t *entity, char *buffer, int bufsize,
+                         rlc_tx_pdu_segment_t *pdu, int p)
+{
+  int                  first_sdu_full;
+  int                  last_sdu_full;
+  int                  sdu_next_byte;
+  rlc_sdu_t            *sdu;
+  int                  i;
+  int                  cursize;
+  rlc_pdu_encoder_t    encoder;
+  int                  fi;
+  int                  e;
+  int                  li;
+  char                 *out;
+  int                  outpos;
+  int                  sdu_count;
+  int                  header_size;
+  int                  sdu_start_byte;
+
+  first_sdu_full = pdu->sdu_start_byte == 0;
+
+  /* is last SDU full? (and also compute sdu_count) */
+  last_sdu_full = 1;
+  sdu = pdu->start_sdu;
+  sdu_next_byte = pdu->sdu_start_byte;
+  cursize = 0;
+  sdu_count = 0;
+  while (cursize != pdu->data_size) {
+    int sdu_size = sdu->size - sdu_next_byte;
+    sdu_count++;
+    if (cursize + sdu_size > pdu->data_size) {
+      last_sdu_full = 0;
+      break;
+    }
+    cursize += sdu_size;
+    sdu = sdu->next;
+    sdu_next_byte = 0;
+  }
+
+  /* generate header */
+  rlc_pdu_encoder_init(&encoder, buffer, bufsize);
+
+  rlc_pdu_encoder_put_bits(&encoder, 1, 1);                /* D/C: 1 = data */
+  rlc_pdu_encoder_put_bits(&encoder, pdu->is_segment, 1);             /* RF */
+  rlc_pdu_encoder_put_bits(&encoder, 0, 1);        /* P: reserve, set later */
+
+  fi = 0;
+  if (!first_sdu_full)
+    fi |= 0x02;
+  if (!last_sdu_full)
+    fi |= 0x01;
+  rlc_pdu_encoder_put_bits(&encoder, fi, 2);                          /* FI */
+
+  /* to understand the logic for Es and LIs:
+   * If we have:
+   *   1 SDU:   E=0
+   *
+   *   2 SDUs:  E=1
+   *     then:  E=0 LI(sdu[0])
+   *
+   *   3 SDUs:  E=1
+   *     then:  E=1 LI(sdu[0])
+   *     then:  E=0 LI(sdu[1])
+   *
+   *   4 SDUs:  E=1
+   *     then:  E=1 LI(sdu[0])
+   *     then:  E=1 LI(sdu[1])
+   *     then:  E=0 LI(sdu[2])
+   */
+  if (sdu_count >= 2)
+    e = 1;
+  else
+    e = 0;
+  rlc_pdu_encoder_put_bits(&encoder, e, 1);                            /* E */
+
+  rlc_pdu_encoder_put_bits(&encoder, pdu->sn, 10);                    /* SN */
+
+  if (pdu->is_segment) {
+    rlc_pdu_encoder_put_bits(&encoder, pdu->is_last, 1);             /* LSF */
+    rlc_pdu_encoder_put_bits(&encoder, pdu->so, 15);                  /* SO */
+  }
+
+  /* put LIs */
+  sdu = pdu->start_sdu;
+  /* first SDU */
+  li = sdu->size - pdu->sdu_start_byte;
+  /* put E+LI only if at least 2 SDUs */
+  if (sdu_count >= 2) {
+    /* E is 1 if at least 3 SDUs */
+    if (sdu_count >= 3)
+      e = 1;
+    else
+      e = 0;
+    rlc_pdu_encoder_put_bits(&encoder, e, 1);                          /* E */
+    rlc_pdu_encoder_put_bits(&encoder, li, 11);                       /* LI */
+  }
+  /* next SDUs, but not the last (no LI for the last) */
+  sdu = sdu->next;
+  for (i = 2; i < sdu_count; i++, sdu = sdu->next) {
+    if (i != sdu_count - 1)
+      e = 1;
+    else
+      e = 0;
+    li = sdu->size;
+    rlc_pdu_encoder_put_bits(&encoder, e, 1);                          /* E */
+    rlc_pdu_encoder_put_bits(&encoder, li, 11);                       /* LI */
+  }
+
+  rlc_pdu_encoder_align(&encoder);
+
+  header_size = encoder.byte;
+
+  /* generate data */
+  out = buffer + header_size;
+  sdu = pdu->start_sdu;
+  sdu_start_byte = pdu->sdu_start_byte;
+  outpos = 0;
+  for (i = 0; i < sdu_count; i++, sdu = sdu->next) {
+    li = sdu->size - sdu_start_byte;
+    if (outpos + li >= pdu->data_size)
+      li = pdu->data_size - outpos;
+    memcpy(out+outpos, sdu->data + sdu_start_byte, li);
+    outpos += li;
+    sdu_start_byte = 0;
+  }
+
+  if (p)
+    include_poll(entity, buffer);
+
+  return header_size + pdu->data_size;
+}
+
+static int generate_tx_pdu(rlc_entity_am_t *entity, char *buffer, int bufsize)
+{
+  int                  vt_ms;
+  tx_pdu_size_t        pdu_size;
+  rlc_sdu_t            *sdu;
+  int                  i;
+  int                  cursize;
+  int                  p;
+  rlc_tx_pdu_segment_t *pdu;
+
+  /* sn out of window? do nothing */
+  vt_ms = (entity->vt_a + 512) % 1024;
+  if (!(sn_compare_tx(entity, entity->vt_s, entity->vt_a) >= 0 &&
+        sn_compare_tx(entity, entity->vt_s, vt_ms) < 0))
+    return 0;
+
+  pdu_size = compute_new_pdu_size(entity, bufsize);
+  if (pdu_size.sdu_count == 0)
+    return 0;
+
+  pdu = rlc_tx_new_pdu();
+
+  pdu->sn = entity->vt_s;
+  entity->vt_s = (entity->vt_s + 1) % 1024;
+
+  /* go to first SDU (skip those already fully processed) */
+  sdu = entity->tx_list;
+  while (sdu->next_byte == sdu->size)
+    sdu = sdu->next;
+
+  pdu->start_sdu = sdu;
+
+  pdu->sdu_start_byte = sdu->next_byte;
+
+  pdu->so = 0;
+  pdu->is_segment = 0;
+  pdu->is_last = 1;
+  /* to conform to specs' logic, put -1 (specs say "for 1st retransmission
+   * put 0 otherwise increase", let's put -1 and always increase when the
+   * segment goes to retransmit list)
+   */
+  pdu->retx_count = -1;
+
+  /* reserve SDU bytes */
+  cursize = 0;
+  for (i = 0; i < pdu_size.sdu_count; i++, sdu = sdu->next) {
+    int sdu_size = sdu->size - sdu->next_byte;
+    if (cursize + sdu_size > pdu_size.data_size)
+      sdu_size = pdu_size.data_size - cursize;
+    sdu->next_byte += sdu_size;
+    cursize += sdu_size;
+  }
+
+  pdu->data_size = cursize;
+
+  /* put PDU at the end of the wait list */
+  entity->wait_list = rlc_tx_pdu_list_append(entity->wait_list, pdu);
+
+  /* polling actions for a new PDU */
+  entity->pdu_without_poll++;
+  entity->byte_without_poll += pdu_size.data_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_pdu(entity, buffer, bufsize, pdu, p);
+}
+
+static void resegment(rlc_tx_pdu_segment_t *pdu, int size)
+{
+  rlc_tx_pdu_segment_t *new_pdu;
+  rlc_sdu_t *sdu;
+  int sdu_count;
+  int pdu_header_size;
+  int pdu_data_size;
+  int sdu_pos;
+  int sdu_bytes_to_take;
+
+  /* PDU segment too big, cut in two parts so that first part fits into
+   * size bytes (including header)
+   */
+  sdu = pdu->start_sdu;
+  pdu_data_size = 0;
+  sdu_pos = pdu->sdu_start_byte;
+  sdu_count = 0;
+  while (1) {
+    /* can we put a new header and at least one byte of data? */
+    /* header has 2 more bytes for SO */
+    pdu_header_size = 2 + header_size(sdu_count + 1);
+    if (pdu_header_size + pdu_data_size + 1 > size) {
+      /* no we can't, stop here */
+      break;
+    }
+    /* yes we can, go ahead */
+    sdu_count++;
+    sdu_bytes_to_take = sdu->size - sdu_pos;
+    if (pdu_header_size + pdu_data_size + sdu_bytes_to_take > size) {
+      sdu_bytes_to_take = size - (pdu_header_size + pdu_data_size);
+    }
+    sdu_pos += sdu_bytes_to_take;
+    if (sdu_pos == sdu->size) {
+      sdu = sdu->next;
+      sdu_pos = 0;
+    }
+    pdu_data_size += sdu_bytes_to_take;
+  }
+
+  new_pdu = rlc_tx_new_pdu();
+  pdu->is_segment = 1;
+  *new_pdu = *pdu;
+
+  new_pdu->so = pdu->so + pdu_data_size;
+  new_pdu->data_size = pdu->data_size - pdu_data_size;
+  new_pdu->start_sdu = sdu;
+  new_pdu->sdu_start_byte = sdu_pos;
+
+  pdu->is_last = 0;
+  pdu->data_size = pdu_data_size;
+  pdu->next = new_pdu;
+}
+
+static int generate_retx_pdu(rlc_entity_am_t *entity, char *buffer, int size)
+{
+  rlc_tx_pdu_segment_t *pdu;
+  int orig_size;
+  int p;
+
+  pdu = entity->retransmit_list;
+  orig_size = pdu_size(entity, pdu);
+
+  if (orig_size > size) {
+    /* we can't resegment if size is less than 5
+     * (4 bytes for header, 1 byte for data)
+     */
+    if (size < 5)
+      return 0;
+    resegment(pdu, size);
+  }
+
+  /* remove from retransmit list and put in wait list */
+  entity->retransmit_list = pdu->next;
+  entity->wait_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
+                                          entity->wait_list, pdu);
+
+  p = check_poll_after_pdu_assembly(entity);
+
+  if (entity->force_poll) {
+    p = 1;
+    entity->force_poll = 0;
+  }
+
+  return serialize_pdu(entity, buffer, orig_size, pdu, p);
+}
+
+static int status_to_report(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 retx_pdu_size(rlc_entity_am_t *entity, int maxsize)
+{
+  int size;
+
+  if (entity->retransmit_list == NULL)
+    return 0;
+
+  size = pdu_size(entity, entity->retransmit_list);
+  if (size <= maxsize)
+    return size;
+
+  /* we can segment head of retransmist list if maxsize is large enough
+   * to hold a PDU segment with at least 1 data byte (so 5 bytes: 4 bytes
+   * header + 1 byte data)
+   */
+  if (maxsize < 5)
+    return 0;
+
+  /* a later segmentation of the head of retransmit list will generate a pdu
+   * of maximum size 'maxsize' (can be less)
+   */
+  return maxsize;
+}
+
+rlc_entity_buffer_status_t rlc_entity_am_buffer_status(
+    rlc_entity_t *_entity, int maxsize)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  rlc_entity_buffer_status_t ret;
+  tx_pdu_size_t tx_size;
+
+  /* status PDU, if we have to */
+  if (status_to_report(entity))
+    ret.status_size = status_size(entity, maxsize);
+  else
+    ret.status_size = 0;
+
+  /* TX PDU */
+  tx_size = compute_new_pdu_size(entity, maxsize);
+  ret.tx_size = tx_size.data_size + tx_size.header_size;
+
+  /* reTX PDU */
+  ret.retx_size = retx_pdu_size(entity, maxsize);
+
+  return ret;
+}
+
+int rlc_entity_am_generate_pdu(rlc_entity_t *_entity, char *buffer, int size)
+{
+  rlc_entity_am_t *entity = (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 rlc_entity_am_recv_sdu(rlc_entity_t *_entity, char *buffer, int size,
+                            int sdu_id)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  rlc_sdu_t *sdu;
+
+  if (size > 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 = rlc_new_sdu(buffer, size, sdu_id);
+  rlc_sdu_list_add(&entity->tx_list, &entity->tx_end, sdu);
+}
+
+/*************************************************************************/
+/* time/timers                                                           */
+/*************************************************************************/
+
+static void check_t_poll_retransmit(rlc_entity_am_t *entity)
+{
+  rlc_tx_pdu_segment_t head;
+  rlc_tx_pdu_segment_t *cur;
+  rlc_tx_pdu_segment_t *prev;
+  int sn;
+
+  /* 36.322 5.2.2.3 */
+  /* 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;
+
+  /* 36.322 5.2.2.3 says:
+   *
+   *     - include a poll in a RLC data PDU as described in section 5.2.2.1
+   *
+   * 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 36.322 5.2.2.3? */
+  if (!check_poll_after_pdu_assembly(entity))
+    return;
+
+  /* search wait list for PDU with SN = VT(S)-1 */
+  sn = (entity->vt_s + 1023) % 1024;
+
+  head.next = entity->wait_list;
+  cur = entity->wait_list;
+  prev = &head;
+
+  while (cur != NULL) {
+    if (cur->sn == sn)
+      break;
+    prev = cur;
+    cur = cur->next;
+  }
+
+  /* PDU with SN = VT(S)-1 not found?, take the head of wait list */
+  if (cur == NULL) {
+    cur = entity->wait_list;
+    prev = &head;
+    sn = cur->sn;
+  }
+
+  /* 36.322 says "PDU", not "PDU segment", so let's retransmit all
+   * PDU segments with this SN
+   */
+  while (cur != NULL && cur->sn == sn) {
+    prev->next = cur->next;
+    entity->wait_list = head.next;
+    /* put in retransmit list */
+    consider_retransmission(entity, cur);
+    cur = prev->next;
+  }
+}
+
+static void check_t_reordering(rlc_entity_am_t *entity)
+{
+  int sn;
+
+  /* is t_reordering running and if yes has it expired? */
+  if (entity->t_reordering_start == 0 ||
+      entity->t_current <= entity->t_reordering_start + entity->t_reordering)
+    return;
+
+  /* stop timer */
+  entity->t_reordering_start = 0;
+
+  LOG_D(RLC, "%s:%d:%s: t_reordering expired\n", __FILE__, __LINE__, __FUNCTION__);
+
+  /* update VR(MS) to first SN >= VR(X) for which not all PDU segments
+   * have been received
+   */
+  sn = entity->vr_x;
+  while (rlc_am_segment_full(entity, sn))
+    sn = (sn + 1) % 1024;
+  entity->vr_ms = sn;
+
+  if (sn_compare_rx(entity, entity->vr_h, entity->vr_ms) > 0) {
+    entity->t_reordering_start = entity->t_current;
+    entity->vr_x = entity->vr_h;
+  }
+
+  /* trigger STATUS report */
+  entity->status_triggered = 1;
+}
+
+void rlc_entity_am_set_time(rlc_entity_t *_entity, uint64_t now)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+
+  entity->t_current = now;
+
+  check_t_poll_retransmit(entity);
+
+  check_t_reordering(entity);
+
+  /* t_status_prohibit is handled by generate_status */
+}
+
+/*************************************************************************/
+/* discard/re-establishment/delete                                       */
+/*************************************************************************/
+
+void rlc_entity_am_discard_sdu(rlc_entity_t *_entity, int sdu_id)
+{
+  /* implements 36.322 5.3 */
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  rlc_sdu_t head;
+  rlc_sdu_t *cur;
+  rlc_sdu_t *prev;
+
+  head.next = entity->tx_list;
+  cur = entity->tx_list;
+  prev = &head;
+
+  while (cur != NULL && cur->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->next_byte != 0)
+    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;
+  }
+
+  rlc_free_sdu(cur);
+}
+
+static void free_pdu_segment_list(rlc_tx_pdu_segment_t *l)
+{
+  rlc_tx_pdu_segment_t *cur;
+
+  while (l != NULL) {
+    cur = l;
+    l = l->next;
+    rlc_tx_free_pdu(cur);
+  }
+}
+
+static void clear_entity(rlc_entity_am_t *entity)
+{
+  rlc_rx_pdu_segment_t *cur_rx;
+  rlc_sdu_t            *cur_tx;
+
+  entity->vr_r = 0;
+  entity->vr_x = 0;
+  entity->vr_ms = 0;
+  entity->vr_h = 0;
+
+  entity->status_triggered = 0;
+
+  entity->vt_a = 0;
+  entity->vt_s = 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_reordering_start = 0;
+  entity->t_status_prohibit_start = 0;
+  entity->t_poll_retransmit_start = 0;
+
+  cur_rx = entity->rx_list;
+  while (cur_rx != NULL) {
+    rlc_rx_pdu_segment_t *p = cur_rx;
+    cur_rx = cur_rx->next;
+    rlc_rx_free_pdu_segment(p);
+  }
+  entity->rx_list = NULL;
+  entity->rx_size = 0;
+
+  memset(&entity->reassemble, 0, sizeof(rlc_am_reassemble_t));
+
+  cur_tx = entity->tx_list;
+  while (cur_tx != NULL) {
+    rlc_sdu_t *p = cur_tx;
+    cur_tx = cur_tx->next;
+    rlc_free_sdu(p);
+  }
+  entity->tx_list = NULL;
+  entity->tx_end = NULL;
+  entity->tx_size = 0;
+
+  free_pdu_segment_list(entity->wait_list);
+  free_pdu_segment_list(entity->retransmit_list);
+  free_pdu_segment_list(entity->ack_list);
+  entity->wait_list = NULL;
+  entity->retransmit_list = NULL;
+  entity->ack_list = NULL;
+}
+
+void rlc_entity_am_reestablishment(rlc_entity_t *_entity)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+
+  /* 36.322 5.4 says to deliver SDUs if possible.
+   * Let's not do that, it makes the code simpler.
+   * TODO: change this behavior if wanted/needed.
+   */
+
+  clear_entity(entity);
+}
+
+void rlc_entity_am_delete(rlc_entity_t *_entity)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  clear_entity(entity);
+  free(entity);
+}
diff --git a/openair2/LAYER2/rlc_v2/rlc_entity_am.h b/openair2/LAYER2/rlc_v2/rlc_entity_am.h
new file mode 100644
index 0000000000000000000000000000000000000000..0437f17ad8e63e97c9a9cca6e92a5c85a73fb604
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_entity_am.h
@@ -0,0 +1,285 @@
+/*
+ * 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 _RLC_ENTITY_AM_H_
+#define _RLC_ENTITY_AM_H_
+
+#include <stdint.h>
+
+#include "rlc_entity.h"
+#include "rlc_pdu.h"
+#include "rlc_sdu.h"
+
+/*
+ * Here comes some documentation to understand the reassembly
+ * logic in the code and the fields in the structure rlc_am_reassemble_t.
+ *
+ * Inside RLC, we deal with SDUs, PDUs and PDU segments.
+ * SDUs are packets coming from upper layer.
+ * A PDU is made of a header and a payload.
+ * In the payload there are SDUs.
+ * First SDU and last SDU in a PDU may be incomplete.
+ * PDU segments exist in case of retransmissions when the MAC
+ * layer asks for less data than previously, in which case
+ * only part of the previous PDU is sent.
+ *
+ * This is PDU data (just bytes):
+ * ---------------------------------------------------------
+ * |  PDU data                                             |
+ * ---------------------------------------------------------
+ * It contains SDUs, like:
+ * ---------------------------------------------------------
+ * | SDU 1 | SDU 2     |  [...]                   | SDU n  |
+ * ---------------------------------------------------------
+ * SDU 1 may be only the end of an SDU from which previous bytes were
+ * transmitted in previous PDUs.
+ * SDU n may be only the start of an SDU, that is more bytes from
+ * this SDU may be sent in successive PDUs.
+ *
+ * At front of the PDU data, we have a header:
+ * ---------------  ---------------------------------------------------------
+ * | PDU header  |  | SDU 1 | SDU 2     |  [...]                   | SDU n  |
+ * ---------------  ---------------------------------------------------------
+ * PDU header describes PDU data (most notably lengths).
+ *
+ * A PDU segment is a part of a PDU. For example, from this PDU data:
+ * ---------------------------------------------------------
+ * | SDU 1 | SDU 2     |  [...]                   | SDU n  |
+ * ---------------------------------------------------------
+ * We can extract the following PDU segment (data part only):
+ *                ----------------------
+ *                | PDU segment data   |
+ *                ----------------------
+ * This PDU segment would contain the end of SDU 2 above and some SDUs up to,
+ * let's say SDU x (x is 5 below).
+ *
+ * In front of a transmitted PDU segment, we have a header,
+ * containing the important variable 'so' (segment offset) that gives
+ * the index of the first byte of the segment in the original PDU.
+ * -------------- ----------------------
+ * | seg. header| | PDU segment data   |
+ * -------------- ----------------------
+ *
+ * Let's now explain the data structure rlc_am_reassemble_t.
+ *
+ * In the structure rlc_am_reassemble_t, the fields fi, e, sn and so
+ * are coming from the PDU segment header and the semantics is the
+ * one of the RLC specs.
+ *
+ * The currently processed PDU segment is stored in 'start'.
+ * We have 'start->s->data_offset' and 'start->s->size'.
+ * start->s->data_offset is the index of the start of the data in the
+ * PDU segment. That is if the header is of length 3 bytes
+ * then start->s->data_offset is 3.
+ * start->s->size is the total length of the PDU segment,
+ * including header.
+ * The size of actual data bytes in the PDU segment is thus
+ * start->s->size - start->s->data_offset.
+ *
+ * The field sdu_len is the length of the current SDU being
+ * processed.
+ *
+ * The field sdu_offset is the starting point of the
+ * current SDU being processed (starting from beginning
+ * of PDU segment, including header).
+ *
+ * The field data_pos is the current read pointer. 0 points to
+ * the beginning of the PDU segment (including header).
+ *
+ * The field pdu_byte points to the current byte in the original
+ * PDU (not the PDU segment). It starts at 0 when we start
+ * processing a new PDU (when a new 'sn' is seen) and always
+ * increases after each byte processed. This is tha variable
+ * that is used to know if the next PDU segment will be used
+ * or not and if yes, starting from which data byte (see
+ * function rlc_am_reassemble_next_segment).
+ *
+ * 'so' is important and points to the byte in the original PDU
+ * that is the first byte of the PDU segment.
+ *
+ * For example, let's take this PDU segment data from above:
+ *                ----------------------
+ *                | PDU segment data   |
+ *                ----------------------
+ * Let's say it is decomposed as:
+ *                ----------------------
+ *                |222|33|4444|55555555|
+ *                ----------------------
+ * It contains SDUs 2, 3, 4, and 5.
+ * SDU 2 is 3 bytes, SDU 3 is 2 bytes, SDU 4 is 4 bytes, SDU 5 is 8 bytes.
+ *
+ * Let's suppose that the original PDU starts with:
+ * ----------------
+ * |1111111|222222|
+ * ----------------
+ *
+ * (In this example, in the PDU segment, SDU 2 is not full,
+ * we only have its end.)
+ *
+ * Then 'so' is 13 (SDU 1 is 7 bytes, head of SDU 2 is 6 bytes).
+ *
+ * Let's continue with our PDU segment data.
+ * Let's say we are current processing SDU 4.
+ * Let's say the read pointer (variable 'data_pos') is there:
+ *                ----------------------
+ *                |222|33|4444|55555555|
+ *                ----------------------
+ *                         ^
+ *                      read pointer (data_pos)
+ *
+ * Then:
+ *     - sdu_len is 4
+ *     - sdu_offset is 5 + [PDU segment header length]
+ *       (it points to the beginning of SDU 4, starting
+ *        from the head of the PDU segment, that is
+ *        3 bytes for SDU 2, 2 bytes for SDU 3, and the
+ *        PDU segment header length)
+ *     - start->s->data_offset is [PDU segment header length]
+ *     - pdu_byte is 20
+ *       (13 bytes from beginning of original PDU,
+ *        3 bytes for SDU 2, 2 bytes for SDU 3, then 2 bytes for SDU 4)
+ *     - data_pos = read pointer = 7 + [PDU segment header length]
+ *
+ * To finish this description, in the code, a PDU is simply
+ * seen as a PDU segment with 'so' = 0 (and is_last == 1 (lsf in the specs),
+ * but this variable is not used by the reassembly logic).
+ *
+ * And for [PDU segment header length] we use start->s->data_offset.
+ *
+ * To recap, here is an illustration of the various variables
+ * and what starting point they use. In the figures, the start
+ * of the variable name is aligned to the byte it refers to.
+ * + is used to show the starting point.
+ *
+ * Let's put the PDU segment back into the original PDU.
+ * And let's show the values for when the read pointer
+ * is on the second byte of SDU 4 (as above).
+ *
+ * +++++++++++++++ so
+ * +++++++++++++++++++++++ pdu_byte
+ * ---------------------------------------------------------
+ * | SDU 1| SDU 2..222|33|4444|55555555| [...]   | SDU n   |
+ * ---------------------------------------------------------
+ *
+ * And now the PDU segment with header.
+ *
+ *
+ *                        ++++ sdu_len
+ * ++++++++++++++++++++++ sdu_offset
+ * +++++++++++++++++++++++ data_pos
+ * +++++++++++++++ start->s->data_offset
+ * +++++++++++++++++++++++++++++++++++++ start->s->size
+ * -------------- ----------------------
+ * | seg. header| |222|33|4444|55555555|
+ * -------------- ----------------------
+ *
+ * We see three case for the starting point:
+ *     - start of original PDU (without any header)
+ *     - start of header of current PDU segment
+ *     - start of current SDU (for sdu_len)
+ */
+
+typedef struct {
+  rlc_rx_pdu_segment_t *start;      /* start of list */
+  rlc_rx_pdu_segment_t *end;        /* end of list (last element) */
+  int                  pos;         /* byte to get from current buffer */
+  char                 sdu[SDU_MAX]; /* sdu is reassembled here */
+  int                  sdu_pos;      /* next byte to put in sdu */
+
+  /* decoder of current PDU */
+  rlc_pdu_decoder_t    dec;
+  int fi;
+  int e;
+  int sn;
+  int so;
+  int sdu_len;
+  int sdu_offset;
+  int data_pos;
+  int pdu_byte;
+} rlc_am_reassemble_t;
+
+typedef struct {
+  rlc_entity_t common;
+
+  /* configuration */
+  int t_reordering;
+  int t_status_prohibit;
+  int t_poll_retransmit;
+  int poll_pdu;              /* -1 means infinity */
+  int poll_byte;             /* -1 means infinity */
+  int max_retx_threshold;
+
+  /* runtime rx */
+  int vr_r;
+  int vr_x;
+  int vr_ms;
+  int vr_h;
+
+  int status_triggered;
+
+  /* runtime tx */
+  int vt_a;
+  int vt_s;
+  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_reordering_start;
+  uint64_t t_status_prohibit_start;
+  uint64_t t_poll_retransmit_start;
+
+  /* rx management */
+  rlc_rx_pdu_segment_t *rx_list;
+  int                  rx_size;
+  int                  rx_maxsize;
+
+  /* reassembly management */
+  rlc_am_reassemble_t    reassemble;
+
+  /* tx management */
+  rlc_sdu_t *tx_list;
+  rlc_sdu_t *tx_end;
+  int       tx_size;
+  int       tx_maxsize;
+
+  rlc_tx_pdu_segment_t *wait_list;
+  rlc_tx_pdu_segment_t *retransmit_list;
+
+  rlc_tx_pdu_segment_t *ack_list;
+} rlc_entity_am_t;
+
+void rlc_entity_am_recv_sdu(rlc_entity_t *entity, char *buffer, int size,
+                            int sdu_id);
+void rlc_entity_am_recv_pdu(rlc_entity_t *entity, char *buffer, int size);
+rlc_entity_buffer_status_t rlc_entity_am_buffer_status(
+    rlc_entity_t *entity, int maxsize);
+int rlc_entity_am_generate_pdu(rlc_entity_t *entity, char *buffer, int size);
+void rlc_entity_am_set_time(rlc_entity_t *entity, uint64_t now);
+void rlc_entity_am_discard_sdu(rlc_entity_t *entity, int sdu_id);
+void rlc_entity_am_reestablishment(rlc_entity_t *entity);
+void rlc_entity_am_delete(rlc_entity_t *entity);
+
+#endif /* _RLC_ENTITY_AM_H_ */
diff --git a/openair2/LAYER2/rlc_v2/rlc_entity_um.c b/openair2/LAYER2/rlc_v2/rlc_entity_um.c
new file mode 100644
index 0000000000000000000000000000000000000000..75692851cd200b3f875e93899f1687ab91999310
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_entity_um.c
@@ -0,0 +1,703 @@
+/*
+ * 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_entity_um.h"
+#include "rlc_pdu.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+/*************************************************************************/
+/* PDU RX functions                                                      */
+/*************************************************************************/
+
+static int modulus_rx(rlc_entity_um_t *entity, int a)
+{
+  /* as per 36.322 7.1, modulus base is vr(uh)-window_size and modulus is
+   * 2^sn_field_length (which is 'sn_modulus' in rlc_entity_um_t)
+   */
+  int r = a - (entity->vr_uh - 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)
+{
+  rlc_entity_um_t *entity = _entity;
+  return modulus_rx(entity, a) - modulus_rx(entity, b);
+}
+
+static int sn_in_recv_window(void *_entity, int sn)
+{
+  rlc_entity_um_t *entity = _entity;
+  int mod_sn = modulus_rx(entity, sn);
+  /* we simplify (VR(UH) - UM_Window_Size) <= SN < VR(UH), base is
+   * (VR(UH) - UM_Window_Size) and VR(UH) = base + window_size
+   */
+  return mod_sn < entity->window_size;
+}
+
+/* return 1 if a PDU with SN == 'sn' is in the rx list, 0 otherwise */
+static int rlc_um_pdu_received(rlc_entity_um_t *entity, int sn)
+{
+  rlc_rx_pdu_segment_t *cur = entity->rx_list;
+  while (cur != NULL) {
+    if (cur->sn == sn)
+      return 1;
+    cur = cur->next;
+  }
+  return 0;
+}
+
+static int less_than_vr_ur(rlc_entity_um_t *entity, int sn)
+{
+  return sn_compare_rx(entity, sn, entity->vr_ur) < 0;
+}
+
+static int outside_of_reordering_window(rlc_entity_um_t *entity, int sn)
+{
+  return !sn_in_recv_window(entity, sn);
+}
+
+static int less_than_vr_uh(rlc_entity_um_t *entity, int sn)
+{
+  return sn_compare_rx(entity, sn, entity->vr_uh) < 0;
+}
+
+static void rlc_um_reassemble_pdu(rlc_entity_um_t *entity,
+    rlc_rx_pdu_segment_t *pdu)
+{
+  rlc_um_reassemble_t *r = &entity->reassemble;
+
+  int fi;
+  int e;
+  int sn;
+  int data_pos;
+  int sdu_len;
+  int sdu_offset;
+
+  sdu_offset = pdu->data_offset;
+
+  rlc_pdu_decoder_init(&r->dec, pdu->data, pdu->size);
+
+  if (entity->sn_field_length == 10)
+    rlc_pdu_decoder_get_bits(&r->dec, 3);
+
+  fi = rlc_pdu_decoder_get_bits(&r->dec, 2);
+  e  = rlc_pdu_decoder_get_bits(&r->dec, 1);
+  sn = rlc_pdu_decoder_get_bits(&r->dec, entity->sn_field_length);
+
+  if (e) {
+    e       = rlc_pdu_decoder_get_bits(&r->dec, 1);
+    sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
+  } else
+    sdu_len = pdu->size - sdu_offset;
+
+  /* discard current SDU being reassembled if bad SN or bad FI */
+  if (sn != (r->sn + 1) % entity->sn_modulus ||
+      !(fi & 0x02)) {
+    if (r->sdu_pos)
+      LOG_D(RLC, "%s:%d:%s: warning: discard partially reassembled SDU\n",
+            __FILE__, __LINE__, __FUNCTION__);
+    r->sdu_pos = 0;
+  }
+
+  /* if the head of the SDU is missing, still process the PDU
+   * but remember to discard the reassembled SDU later on (the
+   * head has not been received).
+   * The head is missing if sdu_pos == 0 and fi says the PDU does not
+   * start an SDU.
+   */
+  if (r->sdu_pos == 0 && (fi & 0x02))
+    r->sdu_head_missing = 1;
+
+  r->sn = sn;
+  data_pos = pdu->data_offset;
+
+  while (1) {
+    if (r->sdu_pos >= SDU_MAX) {
+      /* TODO: proper error handling (discard PDUs with current sn from
+       * reassembly queue? something else?)
+       */
+      LOG_E(RLC, "%s:%d:%s: bad RLC PDU\n", __FILE__, __LINE__, __FUNCTION__);
+      exit(1);
+    }
+    r->sdu[r->sdu_pos] = pdu->data[data_pos];
+    r->sdu_pos++;
+    data_pos++;
+    if (data_pos == sdu_offset + sdu_len) {
+      /* all bytes of SDU are consumed, check if SDU is fully there.
+       * It is if the data pointer is not at the end of the PDU segment
+       * or if 'fi' & 1 == 0
+       */
+      if (data_pos != pdu->size || (fi & 1) == 0) {
+        /* time to discard the SDU if we didn't receive the head */
+        if (r->sdu_head_missing) {
+          LOG_D(RLC, "%s:%d:%s: warning: discard SDU, head not received\n",
+                __FILE__, __LINE__, __FUNCTION__);
+          r->sdu_head_missing = 0;
+        } else {
+          /* SDU is full - deliver to higher layer */
+          entity->common.deliver_sdu(entity->common.deliver_sdu_data,
+                                     (rlc_entity_t *)entity,
+                                     r->sdu, r->sdu_pos);
+        }
+        r->sdu_pos = 0;
+      }
+      /* done with PDU? */
+      if (data_pos == pdu->size)
+        break;
+      /* not at the end of PDU, process next SDU */
+      sdu_offset += sdu_len;
+      if (e) {
+        e       = rlc_pdu_decoder_get_bits(&r->dec, 1);
+        sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
+      } else
+        sdu_len = pdu->size - sdu_offset;
+    }
+  }
+}
+
+static void rlc_um_reassemble(rlc_entity_um_t *entity,
+    int (*check_sn)(rlc_entity_um_t *entity, int sn))
+{
+  rlc_rx_pdu_segment_t *cur;
+
+  /* process all PDUs from head of rx list until all is processed or
+   * the SN is not valid anymore with respect to 'check_sn'
+   */
+  while (entity->rx_list != NULL && check_sn(entity, entity->rx_list->sn)) {
+    cur = entity->rx_list;
+    rlc_um_reassemble_pdu(entity, cur);
+    entity->rx_size -= cur->size;
+    entity->rx_list = cur->next;
+    rlc_rx_free_pdu_segment(cur);
+  }
+}
+
+static void rlc_um_reception_actions(rlc_entity_um_t *entity,
+    rlc_rx_pdu_segment_t *pdu_segment)
+{
+  if (!sn_in_recv_window(entity, pdu_segment->sn)) {
+    entity->vr_uh = (pdu_segment->sn + 1) % entity->sn_modulus;
+    rlc_um_reassemble(entity, outside_of_reordering_window);
+    if (!sn_in_recv_window(entity, entity->vr_ur))
+      entity->vr_ur = (entity->vr_uh - entity->window_size
+                         + entity->sn_modulus) % entity->sn_modulus;
+  }
+
+  if (rlc_um_pdu_received(entity, entity->vr_ur)) {
+    do {
+      entity->vr_ur = (entity->vr_ur + 1) % entity->sn_modulus;
+    } while (rlc_um_pdu_received(entity, entity->vr_ur));
+    rlc_um_reassemble(entity, less_than_vr_ur);
+  }
+
+  if (entity->t_reordering_start) {
+    if (sn_compare_rx(entity, entity->vr_ux, entity->vr_ur) <= 0 ||
+        (!sn_in_recv_window(entity, entity->vr_ux) &&
+         entity->vr_ux != entity->vr_uh))
+      entity->t_reordering_start = 0;
+  }
+
+  if (entity->t_reordering_start == 0) {
+    if (sn_compare_rx(entity, entity->vr_uh, entity->vr_ur) > 0) {
+      entity->t_reordering_start = entity->t_current;
+      entity->vr_ux = entity->vr_uh;
+    }
+  }
+}
+
+void rlc_entity_um_recv_pdu(rlc_entity_t *_entity, char *buffer, int size)
+{
+#define R(d) do { if (rlc_pdu_decoder_in_error(&d)) goto err; } while (0)
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+  rlc_pdu_decoder_t decoder;
+  rlc_pdu_decoder_t data_decoder;
+
+  int e;
+  int sn;
+
+  int data_e;
+  int data_li;
+
+  int packet_count;
+  int data_size;
+  int data_start;
+  int indicated_data_size;
+
+  rlc_rx_pdu_segment_t *pdu_segment;
+
+  rlc_pdu_decoder_init(&decoder, buffer, size);
+
+  if (entity->sn_field_length == 10) {
+    rlc_pdu_decoder_get_bits(&decoder, 3); R(decoder);       /* R1 */
+  }
+
+  rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);         /* FI */
+  e  = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
+  sn = rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length); R(decoder);
+
+  /* dicard PDU if rx buffer is full */
+  if (entity->rx_size + size > entity->rx_maxsize) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, RX buffer full\n",
+          __FILE__, __LINE__, __FUNCTION__);
+    return;
+  }
+
+  /* discard according to 36.322 5.1.2.2.2 */
+  if ((sn_compare_rx(entity, entity->vr_ur, sn) < 0 &&
+       sn_compare_rx(entity, sn, entity->vr_uh) < 0 &&
+       rlc_um_pdu_received(entity, sn)) ||
+      (sn_compare_rx(entity, entity->vr_uh - entity->window_size, sn) <= 0 &&
+       sn_compare_rx(entity, sn, entity->vr_ur) < 0)) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU (sn %d vr(ur) %d vr(uh) %d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+          sn, entity->vr_ur, entity->vr_uh);
+    return;
+  }
+
+  packet_count = 1;
+
+  /* go to start of data */
+  indicated_data_size = 0;
+  data_decoder = decoder;
+  data_e = e;
+  while (data_e) {
+    data_e = rlc_pdu_decoder_get_bits(&data_decoder, 1); R(data_decoder);
+    data_li = rlc_pdu_decoder_get_bits(&data_decoder, 11); R(data_decoder);
+    if (data_li == 0) {
+      LOG_D(RLC, "%s:%d:%s: warning: discard PDU, li == 0\n",
+            __FILE__, __LINE__, __FUNCTION__);
+      return;
+    }
+    indicated_data_size += data_li;
+    packet_count++;
+  }
+  rlc_pdu_decoder_align(&data_decoder);
+
+  data_start = data_decoder.byte;
+  data_size = size - data_start;
+
+  if (data_size <= 0) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, wrong data size (sum of LI %d data size %d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+          indicated_data_size, data_size);
+    return;
+  }
+  if (indicated_data_size >= data_size) {
+    LOG_D(RLC, "%s:%d:%s: warning: discard PDU, bad LIs (sum of LI %d data size %d)\n",
+          __FILE__, __LINE__, __FUNCTION__,
+          indicated_data_size, data_size);
+    return;
+  }
+
+  /* put in pdu reception list */
+  entity->rx_size += size;
+  pdu_segment = rlc_rx_new_pdu_segment(sn, 0, size, 1, buffer, data_start);
+  entity->rx_list = rlc_rx_pdu_segment_list_add(sn_compare_rx, entity,
+                                                entity->rx_list, pdu_segment);
+
+  /* do reception actions (36.322 5.1.2.2.3) */
+  rlc_um_reception_actions(entity, pdu_segment);
+
+  return;
+
+err:
+  LOG_D(RLC, "%s:%d:%s: error decoding PDU, discarding\n", __FILE__, __LINE__, __FUNCTION__);
+
+#undef R
+}
+
+/*************************************************************************/
+/* TX functions                                                          */
+/*************************************************************************/
+
+typedef struct {
+  int sdu_count;
+  int data_size;
+  int header_size;
+  int last_sdu_is_full;
+  int first_sdu_length;
+} tx_pdu_size_t;
+
+static int header_size(int sn_field_length, int sdu_count)
+{
+  int bits = 8 + 8 * (sn_field_length == 10) + 12 * (sdu_count - 1);
+  /* padding if we have to */
+  return (bits + 7) / 8;
+}
+
+static tx_pdu_size_t tx_pdu_size(rlc_entity_um_t *entity, int maxsize)
+{
+  tx_pdu_size_t ret;
+  int sdu_count;
+  int sdu_size;
+  int pdu_data_size;
+  rlc_sdu_t *sdu;
+
+  ret.sdu_count = 0;
+  ret.data_size = 0;
+  ret.header_size = 0;
+  ret.last_sdu_is_full = 1;
+
+  /* TX PDU - let's make the biggest PDU we can with the SDUs we have */
+  sdu_count = 0;
+  pdu_data_size = 0;
+  sdu = entity->tx_list;
+  while (sdu != NULL) {
+    int new_header_size = header_size(entity->sn_field_length, sdu_count+1);
+    /* if we cannot put new header + at least 1 byte of data then over */
+    if (new_header_size + pdu_data_size >= maxsize)
+      break;
+    sdu_count++;
+    /* only include the bytes of this SDU not included in PDUs already */
+    sdu_size = sdu->size - sdu->next_byte;
+    /* don't feed more than 'maxsize' bytes */
+    if (new_header_size + pdu_data_size + sdu_size > maxsize) {
+      sdu_size = maxsize - new_header_size - pdu_data_size;
+      ret.last_sdu_is_full = 0;
+    }
+    if (sdu_count == 1)
+      ret.first_sdu_length = sdu_size;
+    pdu_data_size += sdu_size;
+    /* if we put more than 2^11-1 bytes then the LI field cannot be used,
+     * so this is the last SDU we can put
+     */
+    if (sdu_size > 2047)
+      break;
+    sdu = sdu->next;
+  }
+
+  if (sdu_count) {
+    ret.sdu_count = sdu_count;
+    ret.data_size = pdu_data_size;
+    ret.header_size = header_size(entity->sn_field_length, sdu_count);
+  }
+
+  return ret;
+}
+
+rlc_entity_buffer_status_t rlc_entity_um_buffer_status(
+    rlc_entity_t *_entity, int maxsize)
+{
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+  rlc_entity_buffer_status_t ret;
+  tx_pdu_size_t tx_size;
+
+  ret.status_size = 0;
+
+  tx_size = tx_pdu_size(entity, maxsize);
+  ret.tx_size = tx_size.data_size + tx_size.header_size;
+
+  ret.retx_size = 0;
+
+  return ret;
+}
+
+int rlc_entity_um_generate_pdu(rlc_entity_t *_entity, char *buffer, int size)
+{
+  rlc_entity_um_t      *entity = (rlc_entity_um_t *)_entity;
+  tx_pdu_size_t        pdu_size;
+  rlc_sdu_t            *sdu;
+  int                  i;
+  int                  cursize;
+  int                  first_sdu_full;
+  int                  last_sdu_full;
+  rlc_pdu_encoder_t    encoder;
+  int                  fi;
+  int                  e;
+  int                  li;
+  char                 *out;
+  int                  outpos;
+  int                  first_sdu_start_byte;
+  int                  sdu_start_byte;
+
+  pdu_size = tx_pdu_size(entity, size);
+  if (pdu_size.sdu_count == 0)
+    return 0;
+
+  sdu = entity->tx_list;
+
+  first_sdu_start_byte = sdu->next_byte;
+
+  /* reserve SDU bytes */
+  cursize = 0;
+  for (i = 0; i < pdu_size.sdu_count; i++, sdu = sdu->next) {
+    int sdu_size = sdu->size - sdu->next_byte;
+    if (cursize + sdu_size > pdu_size.data_size)
+      sdu_size = pdu_size.data_size - cursize;
+    sdu->next_byte += sdu_size;
+    cursize += sdu_size;
+  }
+
+  first_sdu_full = first_sdu_start_byte == 0;
+  last_sdu_full = pdu_size.last_sdu_is_full;
+
+  /* generate header */
+  rlc_pdu_encoder_init(&encoder, buffer, size);
+
+  if (entity->sn_field_length == 10)
+    rlc_pdu_encoder_put_bits(&encoder, 0, 3);                         /* R1 */
+
+  fi = 0;
+  if (!first_sdu_full)
+    fi |= 0x02;
+  if (!last_sdu_full)
+    fi |= 0x01;
+  rlc_pdu_encoder_put_bits(&encoder, fi, 2);                          /* FI */
+
+  /* see the AM code to understand the logic for Es and LIs */
+  if (pdu_size.sdu_count >= 2)
+    e = 1;
+  else
+    e = 0;
+  rlc_pdu_encoder_put_bits(&encoder, e, 1);                            /* E */
+
+  if (entity->sn_field_length == 10)
+    rlc_pdu_encoder_put_bits(&encoder, entity->vt_us, 10);            /* SN */
+  else
+    rlc_pdu_encoder_put_bits(&encoder, entity->vt_us, 5);             /* SN */
+
+  /* put LIs */
+  sdu = entity->tx_list;
+  /* first SDU */
+  li = pdu_size.first_sdu_length;
+  /* put E+LI only if at least 2 SDUs */
+  if (pdu_size.sdu_count >= 2) {
+    /* E is 1 if at least 3 SDUs */
+    if (pdu_size.sdu_count >= 3)
+      e = 1;
+    else
+      e = 0;
+    rlc_pdu_encoder_put_bits(&encoder, e, 1);                          /* E */
+    rlc_pdu_encoder_put_bits(&encoder, li, 11);                       /* LI */
+  }
+  /* next SDUs, but not the last (no LI for the last) */
+  sdu = sdu->next;
+  for (i = 2; i < pdu_size.sdu_count; i++, sdu = sdu->next) {
+    if (i != pdu_size.sdu_count - 1)
+      e = 1;
+    else
+      e = 0;
+    li = sdu->size;
+    rlc_pdu_encoder_put_bits(&encoder, e, 1);                          /* E */
+    rlc_pdu_encoder_put_bits(&encoder, li, 11);                       /* LI */
+  }
+
+  rlc_pdu_encoder_align(&encoder);
+
+  /* generate data */
+  out = buffer + pdu_size.header_size;
+  sdu = entity->tx_list;
+  sdu_start_byte = first_sdu_start_byte;
+  outpos = 0;
+  for (i = 0; i < pdu_size.sdu_count; i++, sdu = sdu->next) {
+    li = sdu->size - sdu_start_byte;
+    if (outpos + li >= pdu_size.data_size)
+      li = pdu_size.data_size - outpos;
+    memcpy(out+outpos, sdu->data + sdu_start_byte, li);
+    outpos += li;
+    sdu_start_byte = 0;
+  }
+
+  /* cleanup sdu list */
+  while (entity->tx_list != NULL &&
+         entity->tx_list->size == entity->tx_list->next_byte) {
+    rlc_sdu_t *c = entity->tx_list;
+    /* release SDU bytes */
+    entity->tx_size -= c->size;
+    entity->tx_list = c->next;
+    rlc_free_sdu(c);
+  }
+  if (entity->tx_list == NULL)
+    entity->tx_end = NULL;
+
+  /* update VT(US) */
+  entity->vt_us = (entity->vt_us + 1) % entity->sn_modulus;
+
+  return pdu_size.header_size + pdu_size.data_size;
+}
+
+/*************************************************************************/
+/* SDU RX functions                                                      */
+/*************************************************************************/
+
+void rlc_entity_um_recv_sdu(rlc_entity_t *_entity, char *buffer, int size,
+                            int sdu_id)
+{
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+  rlc_sdu_t *sdu;
+
+  if (size > 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 = rlc_new_sdu(buffer, size, sdu_id);
+  rlc_sdu_list_add(&entity->tx_list, &entity->tx_end, sdu);
+}
+
+/*************************************************************************/
+/* time/timers                                                           */
+/*************************************************************************/
+
+static void check_t_reordering(rlc_entity_um_t *entity)
+{
+  int sn;
+
+  /* is t_reordering running and if yes has it expired? */
+  if (entity->t_reordering_start == 0 ||
+      entity->t_current <= entity->t_reordering_start + entity->t_reordering)
+    return;
+
+  /* stop timer */
+  entity->t_reordering_start = 0;
+
+  LOG_D(RLC, "%s:%d:%s: t_reordering expired\n", __FILE__, __LINE__, __FUNCTION__);
+
+  /* update VR(UR) to first SN >= VR(UX) of PDU not received
+   */
+  sn = entity->vr_ux;
+  while (rlc_um_pdu_received(entity, sn))
+    sn = (sn + 1) % entity->sn_modulus;
+  entity->vr_ur = sn;
+
+  rlc_um_reassemble(entity, less_than_vr_ur);
+
+  if (sn_compare_rx(entity, entity->vr_uh, entity->vr_ur) > 0) {
+    entity->t_reordering_start = entity->t_current;
+    entity->vr_ux = entity->vr_uh;
+  }
+}
+
+void rlc_entity_um_set_time(rlc_entity_t *_entity, uint64_t now)
+{
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+
+  entity->t_current = now;
+
+  check_t_reordering(entity);
+}
+
+/*************************************************************************/
+/* discard/re-establishment/delete                                       */
+/*************************************************************************/
+
+void rlc_entity_um_discard_sdu(rlc_entity_t *_entity, int sdu_id)
+{
+  /* implements 36.322 5.3 */
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+  rlc_sdu_t head;
+  rlc_sdu_t *cur;
+  rlc_sdu_t *prev;
+
+  head.next = entity->tx_list;
+  cur = entity->tx_list;
+  prev = &head;
+
+  while (cur != NULL && cur->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->next_byte != 0)
+    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;
+  }
+
+  rlc_free_sdu(cur);
+}
+
+static void clear_entity(rlc_entity_um_t *entity)
+{
+  rlc_rx_pdu_segment_t *cur_rx;
+  rlc_sdu_t            *cur_tx;
+
+  entity->vr_ur = 0;
+  entity->vr_ux = 0;
+  entity->vr_uh = 0;
+
+  entity->vt_us = 0;
+
+  entity->t_current = 0;
+
+  entity->t_reordering_start = 0;
+
+  cur_rx = entity->rx_list;
+  while (cur_rx != NULL) {
+    rlc_rx_pdu_segment_t *p = cur_rx;
+    cur_rx = cur_rx->next;
+    rlc_rx_free_pdu_segment(p);
+  }
+  entity->rx_list = NULL;
+  entity->rx_size = 0;
+
+  memset(&entity->reassemble, 0, sizeof(rlc_um_reassemble_t));
+
+  cur_tx = entity->tx_list;
+  while (cur_tx != NULL) {
+    rlc_sdu_t *p = cur_tx;
+    cur_tx = cur_tx->next;
+    rlc_free_sdu(p);
+  }
+  entity->tx_list = NULL;
+  entity->tx_end = NULL;
+  entity->tx_size = 0;
+}
+
+void rlc_entity_um_reestablishment(rlc_entity_t *_entity)
+{
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+
+  rlc_um_reassemble(entity, less_than_vr_uh);
+
+  clear_entity(entity);
+}
+
+void rlc_entity_um_delete(rlc_entity_t *_entity)
+{
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+  clear_entity(entity);
+  free(entity);
+}
diff --git a/openair2/LAYER2/rlc_v2/rlc_entity_um.h b/openair2/LAYER2/rlc_v2/rlc_entity_um.h
new file mode 100644
index 0000000000000000000000000000000000000000..02c5141a7a6613536728e2b81c75ca1b21b1db1f
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_entity_um.h
@@ -0,0 +1,90 @@
+/*
+ * 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 _RLC_ENTITY_UM_H_
+#define _RLC_ENTITY_UM_H_
+
+#include "rlc_entity.h"
+#include "rlc_pdu.h"
+#include "rlc_sdu.h"
+
+typedef struct {
+  char sdu[SDU_MAX];     /* sdu is reassembled here */
+  int  sdu_pos;          /* next byte to put in sdu */
+
+  /* decoder of current PDU */
+  rlc_pdu_decoder_t    dec;
+  int sn;
+
+  int sdu_head_missing;
+} rlc_um_reassemble_t;
+
+typedef struct {
+  rlc_entity_t common;
+
+  /* configuration */
+  int t_reordering;
+  int sn_field_length;
+
+  int sn_modulus;        /* 1024 for sn_field_length == 10, 32 for 5 */
+  int window_size;       /* 512 for sn_field_length == 10, 16 for 5 */
+
+  /* runtime rx */
+  int vr_ur;
+  int vr_ux;
+  int vr_uh;
+
+  /* runtime tx */
+  int vt_us;
+
+  /* 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_reordering_start;
+
+  /* rx management */
+  rlc_rx_pdu_segment_t *rx_list;
+  int                  rx_size;
+  int                  rx_maxsize;
+
+  /* reassembly management */
+  rlc_um_reassemble_t reassemble;
+
+  /* tx management */
+  rlc_sdu_t *tx_list;
+  rlc_sdu_t *tx_end;
+  int       tx_size;
+  int       tx_maxsize;
+} rlc_entity_um_t;
+
+void rlc_entity_um_recv_sdu(rlc_entity_t *_entity, char *buffer, int size,
+                            int sdu_id);
+void rlc_entity_um_recv_pdu(rlc_entity_t *entity, char *buffer, int size);
+rlc_entity_buffer_status_t rlc_entity_um_buffer_status(
+    rlc_entity_t *entity, int maxsize);
+int rlc_entity_um_generate_pdu(rlc_entity_t *_entity, char *buffer, int size);
+void rlc_entity_um_set_time(rlc_entity_t *entity, uint64_t now);
+void rlc_entity_um_discard_sdu(rlc_entity_t *entity, int sdu_id);
+void rlc_entity_um_reestablishment(rlc_entity_t *entity);
+void rlc_entity_um_delete(rlc_entity_t *entity);
+
+#endif /* _RLC_ENTITY_UM_H_ */
diff --git a/openair2/LAYER2/rlc_v2/rlc_oai_api.c b/openair2/LAYER2/rlc_v2/rlc_oai_api.c
new file mode 100644
index 0000000000000000000000000000000000000000..1be9c90a31ab7414ada92c82b08f5482f4f22480
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_oai_api.c
@@ -0,0 +1,821 @@
+/*
+ * 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 new rlc module */
+#include "asn1_utils.h"
+#include "rlc_ue_manager.h"
+#include "rlc_entity.h"
+
+#include <stdint.h>
+
+static rlc_ue_manager_t *rlc_ue_manager;
+
+/* TODO: handle time a bit more properly */
+static uint64_t rlc_current_time;
+static int      rlc_current_time_last_frame;
+static int      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)
+{
+  rlc_ue_t *ue;
+  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));
+
+  rlc_manager_lock(rlc_ue_manager);
+  ue = rlc_manager_get_ue(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, 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);
+  }
+
+  rlc_manager_unlock(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;
+  rlc_ue_t *ue;
+  rlc_entity_t *rb;
+
+  rlc_manager_lock(rlc_ue_manager);
+  ue = rlc_manager_get_ue(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, rlc_current_time);
+    ret = rb->generate_pdu(rb, buffer_pP, ue->saved_status_ind_tb_size[channel_idP - 1]);
+  } else {
+    LOG_E(RLC, "%s:%d:%s: fatal: data req for unknown RB\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+    ret = 0;
+  }
+
+  rlc_manager_unlock(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
+  )
+{
+  rlc_ue_t *ue;
+  mac_rlc_status_resp_t ret;
+  rlc_entity_t *rb;
+
+  /* TODO: handle time a bit more properly */
+  if (rlc_current_time_last_frame != frameP ||
+      rlc_current_time_last_subframe != subframeP) {
+    rlc_current_time++;
+    rlc_current_time_last_frame = frameP;
+    rlc_current_time_last_subframe = subframeP;
+  }
+
+  rlc_manager_lock(rlc_ue_manager);
+  ue = rlc_manager_get_ue(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) {
+    rlc_entity_buffer_status_t buf_stat;
+    rb->set_time(rb, rlc_current_time);
+    buf_stat = rb->buffer_status(rb, tb_sizeP ? tb_sizeP : 1000000);
+    if (buf_stat.status_size)
+      ret.bytes_in_buffer = buf_stat.status_size;
+    else if (buf_stat.retx_size)
+      ret.bytes_in_buffer = buf_stat.retx_size;
+    else
+      ret.bytes_in_buffer = buf_stat.tx_size;
+    ue->saved_status_ind_tb_size[channel_idP - 1] = tb_sizeP;
+  } else {
+    ret.bytes_in_buffer = 0;
+  }
+
+  rlc_manager_unlock(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;
+  rlc_ue_t *ue;
+  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));
+
+  rlc_manager_lock(rlc_ue_manager);
+  ue = rlc_manager_get_ue(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, 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);
+  }
+
+  rlc_manager_unlock(rlc_ue_manager);
+
+  free_mem_block(sdu_pP, __func__);
+
+  return RLC_OP_STATUS_OK;
+}
+
+int rlc_module_init(void)
+{
+  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;
+
+  rlc_ue_manager = new_rlc_ue_manager();
+
+  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, rlc_entity_t *entity, char *buf, int size)
+{
+  rlc_ue_t *ue = _ue;
+  int is_srb;
+  int rb_id;
+  protocol_ctxt_t ctx;
+  mem_block_t *memblock;
+  int i;
+
+  /* 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;
+  ctx.enb_flag = 1;
+
+  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__);
+    exit(1);
+  }
+}
+
+static void successful_delivery(void *_ue, rlc_entity_t *entity, int sdu_id)
+{
+  rlc_ue_t *ue = _ue;
+  int i;
+  int is_srb;
+  int rb_id;
+  MessageDef *msg;
+
+  /* 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;
+
+  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);
+}
+
+static void max_retx_reached(void *_ue, rlc_entity_t *entity)
+{
+  rlc_ue_t *ue = _ue;
+  int i;
+  int is_srb;
+  int rb_id;
+  MessageDef *msg;
+
+  /* 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;
+
+  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);
+}
+
+static void add_srb(int rnti, struct LTE_SRB_ToAddMod *s)
+{
+  rlc_entity_t            *rlc_am;
+  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;
+
+  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);
+  }
+
+  rlc_manager_lock(rlc_ue_manager);
+  ue = rlc_manager_get_ue(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 {
+    rlc_am = new_rlc_entity_am(100000,
+                               100000,
+                               deliver_sdu, ue,
+                               successful_delivery, ue,
+                               max_retx_reached, ue,
+                               t_reordering, t_status_prohibit,
+                               t_poll_retransmit,
+                               poll_pdu, poll_byte, max_retx_threshold);
+    rlc_ue_add_srb_rlc_entity(ue, srb_id, rlc_am);
+
+    LOG_D(RLC, "%s:%d:%s: added srb %d to ue %d\n",
+          __FILE__, __LINE__, __FUNCTION__, srb_id, rnti);
+  }
+  rlc_manager_unlock(rlc_ue_manager);
+}
+
+static void add_drb_am(int rnti, struct LTE_DRB_ToAddMod *s)
+{
+  rlc_entity_t            *rlc_am;
+  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;
+
+  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);
+  }
+
+  rlc_manager_lock(rlc_ue_manager);
+  ue = rlc_manager_get_ue(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 {
+    rlc_am = new_rlc_entity_am(1000000,
+                               1000000,
+                               deliver_sdu, ue,
+                               successful_delivery, ue,
+                               max_retx_reached, ue,
+                               t_reordering, t_status_prohibit,
+                               t_poll_retransmit,
+                               poll_pdu, poll_byte, max_retx_threshold);
+    rlc_ue_add_drb_rlc_entity(ue, drb_id, rlc_am);
+
+    LOG_D(RLC, "%s:%d:%s: added drb %d to ue %d\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id, rnti);
+  }
+  rlc_manager_unlock(rlc_ue_manager);
+}
+
+static void add_drb_um(int rnti, struct LTE_DRB_ToAddMod *s)
+{
+  rlc_entity_t            *rlc_um;
+  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;
+
+  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);
+  }
+
+  rlc_manager_lock(rlc_ue_manager);
+  ue = rlc_manager_get_ue(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 {
+    rlc_um = new_rlc_entity_um(1000000,
+                               1000000,
+                               deliver_sdu, ue,
+                               t_reordering,
+                               sn_field_length);
+    rlc_ue_add_drb_rlc_entity(ue, drb_id, rlc_um);
+
+    LOG_D(RLC, "%s:%d:%s: added drb %d to ue %d\n",
+          __FILE__, __LINE__, __FUNCTION__, drb_id, rnti);
+  }
+  rlc_manager_unlock(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)
+{
+  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 (!ctxt_pP->enb_flag) {
+    LOG_E(RLC, "%s:%d:%s: todo (only eNB supported, not UE)\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);
+  }
+  rlc_manager_lock(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 = rlc_manager_get_ue(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)
+      rlc_manager_remove_ue(rlc_ue_manager, ctxt_pP->rnti);
+  }
+  rlc_manager_unlock(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);
+  rlc_manager_lock(rlc_ue_manager);
+  rlc_manager_remove_ue(rlc_ue_manager, x->rnti);
+  rlc_manager_unlock(rlc_ue_manager);
+
+  return RLC_OP_STATUS_OK;
+}
+
diff --git a/openair2/LAYER2/rlc_v2/rlc_pdu.c b/openair2/LAYER2/rlc_v2/rlc_pdu.c
new file mode 100644
index 0000000000000000000000000000000000000000..c55e2d9c3c54bcd6b7415146f9688f8cc500699c
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_pdu.c
@@ -0,0 +1,266 @@
+/*
+ * 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_pdu.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+/**************************************************************************/
+/* RX PDU segment and segment list                                        */
+/**************************************************************************/
+
+rlc_rx_pdu_segment_t *rlc_rx_new_pdu_segment(int sn, int so, int size,
+    int is_last, char *data, int data_offset)
+{
+  rlc_rx_pdu_segment_t *ret = malloc(sizeof(rlc_rx_pdu_segment_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_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);
+
+  ret->data_offset = data_offset;
+
+  return ret;
+}
+
+void rlc_rx_free_pdu_segment(rlc_rx_pdu_segment_t *pdu_segment)
+{
+  free(pdu_segment->data);
+  free(pdu_segment);
+}
+
+rlc_rx_pdu_segment_t *rlc_rx_pdu_segment_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    rlc_rx_pdu_segment_t *list, rlc_rx_pdu_segment_t *pdu_segment)
+{
+  rlc_rx_pdu_segment_t head;
+  rlc_rx_pdu_segment_t *cur;
+  rlc_rx_pdu_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 'pdu_segment' is before 'cur' in the list */
+    if (sn_compare(sn_compare_data, cur->sn, pdu_segment->sn) > 0 ||
+        (cur->sn == pdu_segment->sn && cur->so > pdu_segment->so)) {
+      break;
+    }
+    prev = cur;
+    cur = cur->next;
+  }
+  prev->next = pdu_segment;
+  pdu_segment->next = cur;
+  return head.next;
+}
+
+/**************************************************************************/
+/* TX PDU management                                                      */
+/**************************************************************************/
+
+rlc_tx_pdu_segment_t *rlc_tx_new_pdu(void)
+{
+  rlc_tx_pdu_segment_t *ret = calloc(1, sizeof(rlc_tx_pdu_segment_t));
+  if (ret == NULL) {
+    LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
+    exit(1);
+  }
+  ret->retx_count = -1;
+  return ret;
+}
+
+void rlc_tx_free_pdu(rlc_tx_pdu_segment_t *pdu)
+{
+  free(pdu);
+}
+
+rlc_tx_pdu_segment_t *rlc_tx_pdu_list_append(rlc_tx_pdu_segment_t *list,
+    rlc_tx_pdu_segment_t *pdu)
+{
+  rlc_tx_pdu_segment_t head;
+  rlc_tx_pdu_segment_t *cur;
+
+  head.next = list;
+
+  cur = &head;
+  while (cur->next != NULL) {
+    cur = cur->next;
+  }
+  cur->next = pdu;
+
+  return head.next;
+}
+
+rlc_tx_pdu_segment_t *rlc_tx_pdu_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    rlc_tx_pdu_segment_t *list, rlc_tx_pdu_segment_t *pdu_segment)
+{
+  rlc_tx_pdu_segment_t head;
+  rlc_tx_pdu_segment_t *cur;
+  rlc_tx_pdu_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 'pdu_segment' is before 'cur' in the list */
+    if (sn_compare(sn_compare_data, cur->sn, pdu_segment->sn) > 0 ||
+        (cur->sn == pdu_segment->sn && cur->so > pdu_segment->so)) {
+      break;
+    }
+    prev = cur;
+    cur = cur->next;
+  }
+  prev->next = pdu_segment;
+  pdu_segment->next = cur;
+  return head.next;
+}
+
+/**************************************************************************/
+/* PDU decoder                                                            */
+/**************************************************************************/
+
+void rlc_pdu_decoder_init(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(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 rlc_pdu_decoder_get_bits(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;
+}
+
+void rlc_pdu_decoder_align(rlc_pdu_decoder_t *decoder)
+{
+  if (decoder->bit) {
+    decoder->bit = 0;
+    decoder->byte++;
+  }
+}
+
+/**************************************************************************/
+/* PDU encoder                                                            */
+/**************************************************************************/
+
+void rlc_pdu_encoder_init(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(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 rlc_pdu_encoder_put_bits(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);
+}
+
+void rlc_pdu_encoder_align(rlc_pdu_encoder_t *encoder)
+{
+  while (encoder->bit)
+    put_bit(encoder, 0);
+}
diff --git a/openair2/LAYER2/rlc_v2/rlc_pdu.h b/openair2/LAYER2/rlc_v2/rlc_pdu.h
new file mode 100644
index 0000000000000000000000000000000000000000..dbffe9f3cbff92fe706985af5bfcc5156b2f52b9
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_pdu.h
@@ -0,0 +1,109 @@
+/*
+ * 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 _RLC_PDU_H_
+#define _RLC_PDU_H_
+
+/**************************************************************************/
+/* RX PDU segment and segment list                                        */
+/**************************************************************************/
+
+typedef struct rlc_rx_pdu_segment_t {
+  int sn;
+  int so;
+  int size;
+  int is_last;
+  char *data;
+  int data_offset;
+  struct rlc_rx_pdu_segment_t *next;
+} rlc_rx_pdu_segment_t;
+
+rlc_rx_pdu_segment_t *rlc_rx_new_pdu_segment(int sn, int so, int size,
+    int is_last, char *data, int data_offset);
+
+void rlc_rx_free_pdu_segment(rlc_rx_pdu_segment_t *pdu_segment);
+
+rlc_rx_pdu_segment_t *rlc_rx_pdu_segment_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    rlc_rx_pdu_segment_t *list, rlc_rx_pdu_segment_t *pdu_segment);
+
+/**************************************************************************/
+/* TX PDU management                                                      */
+/**************************************************************************/
+
+typedef struct rlc_tx_pdu_segment_t {
+  int       sn;
+  void      *start_sdu;        /* real type is rlc_sdu_t * */
+  int       sdu_start_byte;    /* starting byte in 'start_sdu' */
+  int       so;                /* starting byte of the segment in full PDU */
+  int       data_size;         /* number of data bytes (exclude header) */
+  int       is_segment;
+  int       is_last;
+  int       retx_count;
+  struct rlc_tx_pdu_segment_t *next;
+} rlc_tx_pdu_segment_t;
+
+rlc_tx_pdu_segment_t *rlc_tx_new_pdu(void);
+void rlc_tx_free_pdu(rlc_tx_pdu_segment_t *pdu);
+rlc_tx_pdu_segment_t *rlc_tx_pdu_list_append(rlc_tx_pdu_segment_t *list,
+    rlc_tx_pdu_segment_t *pdu);
+rlc_tx_pdu_segment_t *rlc_tx_pdu_list_add(
+    int (*sn_compare)(void *, int, int), void *sn_compare_data,
+    rlc_tx_pdu_segment_t *list, rlc_tx_pdu_segment_t *pdu_segment);
+
+/**************************************************************************/
+/* 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;
+} rlc_pdu_decoder_t;
+
+void rlc_pdu_decoder_init(rlc_pdu_decoder_t *decoder, char *buffer, int size);
+
+#define rlc_pdu_decoder_in_error(d) ((d)->error == 1)
+
+int rlc_pdu_decoder_get_bits(rlc_pdu_decoder_t *decoder, int count);
+
+void rlc_pdu_decoder_align(rlc_pdu_decoder_t *decoder);
+
+/**************************************************************************/
+/* PDU encoder                                                            */
+/**************************************************************************/
+
+typedef struct {
+  int byte;           /* next byte to encode */
+  int bit;            /* next bit in next byte to encode */
+  char *buffer;
+  int size;
+} rlc_pdu_encoder_t;
+
+void rlc_pdu_encoder_init(rlc_pdu_encoder_t *encoder, char *buffer, int size);
+
+void rlc_pdu_encoder_put_bits(rlc_pdu_encoder_t *encoder, int value, int count);
+
+void rlc_pdu_encoder_align(rlc_pdu_encoder_t *encoder);
+
+#endif /* _RLC_PDU_H_ */
diff --git a/openair2/LAYER2/rlc_v2/rlc_sdu.c b/openair2/LAYER2/rlc_v2/rlc_sdu.c
new file mode 100644
index 0000000000000000000000000000000000000000..16465a9ff13bede7314c4ee0c9eef757242944c5
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_sdu.c
@@ -0,0 +1,68 @@
+/*
+ * 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_sdu.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+rlc_sdu_t *rlc_new_sdu(char *buffer, int size, int upper_layer_id)
+{
+  rlc_sdu_t *ret = calloc(1, sizeof(rlc_sdu_t));
+  if (ret == NULL)
+    goto oom;
+
+  ret->upper_layer_id = upper_layer_id;
+
+  ret->data = malloc(size);
+  if (ret->data == NULL)
+    goto oom;
+
+  memcpy(ret->data, buffer, size);
+
+  ret->size = size;
+
+  return ret;
+
+oom:
+  LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__,  __FUNCTION__);
+  exit(1);
+}
+
+void rlc_free_sdu(rlc_sdu_t *sdu)
+{
+  free(sdu->data);
+  free(sdu);
+}
+
+void rlc_sdu_list_add(rlc_sdu_t **list, rlc_sdu_t **end, rlc_sdu_t *sdu)
+{
+  if (*list == NULL) {
+    *list = sdu;
+    *end = sdu;
+    return;
+  }
+
+  (*end)->next = sdu;
+  *end = sdu;
+}
diff --git a/openair2/LAYER2/rlc_v2/rlc_sdu.h b/openair2/LAYER2/rlc_v2/rlc_sdu.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c678956ee47a1286db4a2838a1ac96cc1129e72
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_sdu.h
@@ -0,0 +1,39 @@
+/*
+ * 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 _RLC_SDU_H_
+#define _RLC_SDU_H_
+
+typedef struct rlc_sdu_t {
+  int upper_layer_id;
+  char *data;
+  int size;
+  /* next_byte indicates the starting byte to use to construct a new PDU */
+  int next_byte;
+  int acked_bytes;
+  struct rlc_sdu_t *next;
+} rlc_sdu_t;
+
+rlc_sdu_t *rlc_new_sdu(char *buffer, int size, int upper_layer_id);
+void rlc_free_sdu(rlc_sdu_t *sdu);
+void rlc_sdu_list_add(rlc_sdu_t **list, rlc_sdu_t **end, rlc_sdu_t *sdu);
+
+#endif /* _RLC_SDU_H_ */
diff --git a/openair2/LAYER2/rlc_v2/rlc_ue_manager.c b/openair2/LAYER2/rlc_v2/rlc_ue_manager.c
new file mode 100644
index 0000000000000000000000000000000000000000..1d0884bb9cea53f8e5b768a26f62a0c98a8a702e
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_ue_manager.c
@@ -0,0 +1,182 @@
+/*
+ * 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_ue_manager.h"
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "LOG/log.h"
+
+typedef struct {
+  pthread_mutex_t lock;
+  rlc_ue_t        **ue_list;
+  int             ue_count;
+} rlc_ue_manager_internal_t;
+
+rlc_ue_manager_t *new_rlc_ue_manager(void)
+{
+  rlc_ue_manager_internal_t *ret;
+
+  ret = calloc(1, sizeof(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();
+
+  return ret;
+}
+
+void rlc_manager_lock(rlc_ue_manager_t *_m)
+{
+  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 rlc_manager_unlock(rlc_ue_manager_t *_m)
+{
+  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 */
+rlc_ue_t *rlc_manager_get_ue(rlc_ue_manager_t *_m, int rnti)
+{
+  /* TODO: optimze */
+  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(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(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 rlc_manager_remove_ue(rlc_ue_manager_t *_m, int rnti)
+{
+  rlc_ue_manager_internal_t *m = _m;
+  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(rlc_ue_t *));
+  m->ue_list = realloc(m->ue_list, m->ue_count * sizeof(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 rlc_ue_add_srb_rlc_entity(rlc_ue_t *ue, int srb_id, 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 rlc_ue_add_drb_rlc_entity(rlc_ue_t *ue, int drb_id, 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/rlc_v2/rlc_ue_manager.h b/openair2/LAYER2/rlc_v2/rlc_ue_manager.h
new file mode 100644
index 0000000000000000000000000000000000000000..7d5ef18b1e7efaaa2ebb7a5e71b02f46f3769841
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/rlc_ue_manager.h
@@ -0,0 +1,58 @@
+/*
+ * 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 _RLC_UE_MANAGER_H_
+#define _RLC_UE_MANAGER_H_
+
+#include "rlc_entity.h"
+
+typedef void rlc_ue_manager_t;
+
+typedef struct 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];
+  rlc_entity_t *srb[2];
+  rlc_entity_t *drb[5];
+} rlc_ue_t;
+
+/***********************************************************************/
+/* manager functions                                                   */
+/***********************************************************************/
+
+rlc_ue_manager_t *new_rlc_ue_manager(void);
+
+void rlc_manager_lock(rlc_ue_manager_t *m);
+void rlc_manager_unlock(rlc_ue_manager_t *m);
+
+rlc_ue_t *rlc_manager_get_ue(rlc_ue_manager_t *m, int rnti);
+void rlc_manager_remove_ue(rlc_ue_manager_t *m, int rnti);
+
+/***********************************************************************/
+/* ue functions                                                        */
+/***********************************************************************/
+
+void rlc_ue_add_srb_rlc_entity(rlc_ue_t *ue, int srb_id, rlc_entity_t *entity);
+void rlc_ue_add_drb_rlc_entity(rlc_ue_t *ue, int drb_id, rlc_entity_t *entity);
+
+#endif /* _RLC_UE_MANAGER_H_ */
diff --git a/openair2/LAYER2/rlc_v2/tests/LOG/log.h b/openair2/LAYER2/rlc_v2/tests/LOG/log.h
new file mode 100644
index 0000000000000000000000000000000000000000..5c9fcd643cfca036cc81eca221f4a5e818aee685
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/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/rlc_v2/tests/Makefile b/openair2/LAYER2/rlc_v2/tests/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..14bb186d4c2cf78d1405f1afa9ab218e7461b6e3
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/Makefile
@@ -0,0 +1,32 @@
+CC=gcc
+CFLAGS=-Wall -g --coverage -I.
+
+LIB=rlc_entity.o rlc_entity_am.o rlc_entity_um.o rlc_pdu.o 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/rlc_v2/tests/README b/openair2/LAYER2/rlc_v2/tests/README
new file mode 100644
index 0000000000000000000000000000000000000000..db69cd4fa716be83bafe0422c601c7037268f2b4
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/README
@@ -0,0 +1,38 @@
+To run tests, simply type: make
+
+Each test is made of:
+  testXX.h         definition of the test
+  testXX.txt.gz    compressed expected output of the test
+
+At runtime, we generate:
+  testXX.run    actual output of the test
+
+test.c is the test driving program.
+
+Only the output lines of the test program starting with "TEST" are
+stored into testXX.txt and testXX.run.
+
+A test is considered a success if testXX.txt and testXX.run are equal.
+
+Only failed tests are reported.
+
+How to define a new test?
+
+1 - get the ID
+
+    Look in the file run_tests.sh, the variable test_count gives you
+    the number of existing tests. The ID of your test has to be
+    test_count + 1.
+
+2 - create files
+
+    Create the file test<ID>.h containing the test, then generate test<ID>.run
+    by running 'make all TEST=test<ID>' and copy test<ID>.run to test<ID>.txt.
+    Then compress this file (gzip -9 test<ID>.txt). Be sure that the output
+    is correct, of course.
+
+    For the file names, replace <ID> by the actual number of the test.
+    For example, if your test ID is 47, then name the files test47.h and
+    test47.txt. And run 'make all TEST=test47' to generate test47.run.
+
+The available instructions for a test are described at the top of test.c.
diff --git a/openair2/LAYER2/rlc_v2/tests/make_pdu.c b/openair2/LAYER2/rlc_v2/tests/make_pdu.c
new file mode 100644
index 0000000000000000000000000000000000000000..057cc3e36db2e06958969d9d79dc474ea9a9b7bf
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/make_pdu.c
@@ -0,0 +1,29 @@
+/* gcc -Wall make_pdu.c -I.. ../rlc_pdu.c */
+
+#include "rlc_pdu.h"
+#include <stdio.h>
+
+int main(void)
+{
+  char out[100];
+  rlc_pdu_encoder_t e;
+  int i;
+
+  rlc_pdu_encoder_init(&e, out, 100);
+
+  rlc_pdu_encoder_put_bits(&e, 0, 1);    // D/C
+  rlc_pdu_encoder_put_bits(&e, 0, 3);    // CPT
+  rlc_pdu_encoder_put_bits(&e, 0, 10);   // ack_sn
+  rlc_pdu_encoder_put_bits(&e, 1, 1);    // e1
+  rlc_pdu_encoder_put_bits(&e, 10, 10);  // nack_sn
+  rlc_pdu_encoder_put_bits(&e, 0, 1);    // e1
+  rlc_pdu_encoder_put_bits(&e, 0, 1);    // e2
+
+  rlc_pdu_encoder_align(&e);
+
+  for (i = 0; i < e.byte; i++) printf(" %2.2x", (unsigned char)e.buffer[i]);
+
+  printf("\n");
+
+  return 0;
+}
diff --git a/openair2/LAYER2/rlc_v2/tests/run_tests.sh b/openair2/LAYER2/rlc_v2/tests/run_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..72feff00363bf3e917a112b2cbbe76bd2b38dec9
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/run_tests.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+test_count=45
+
+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/rlc_v2/tests/test.c b/openair2/LAYER2/rlc_v2/tests/test.c
new file mode 100644
index 0000000000000000000000000000000000000000..734e85f1f56cc38abe6226d6e6865aadf0522d03
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test.c
@@ -0,0 +1,433 @@
+#include "../rlc_entity.h"
+#include "../rlc_entity_am.h"
+#include "../rlc_entity_um.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/*
+ * ENB_AM <rx_maxsize> <tx_maxsize> <t_reordering> <t_status_prohibit>
+ *       <t_poll_retransmit> <poll_pdu> <poll_byte> <max_retx_threshold>
+ *       create the eNB RLC AM entity with given parameters
+ *
+ * UE_AM <rx_maxsize> <tx_maxsize> <t_reordering> <t_status_prohibit>
+ *      <t_poll_retransmit> <poll_pdu> <poll_byte> <max_retx_threshold>
+ *       create the UE RLC AM entity with given parameters
+ *
+ * ENB_UM <rx_maxsize> <tx_maxsize> <t_reordering> <sn_field_length>
+ *     create the eNB RLC UM entity with given parameters
+ *
+ * UE_UM <rx_maxsize> <tx_maxsize> <t_reordering> <sn_field_length>
+ *     create the UE RLC UM 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'.
+ *
+ * ENB_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 ENB_SDU but the SDU is sent to the UE
+ *
+ * ENB_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)
+ *
+ * ENB_PDU_SIZE <size>
+ *     set 'enb_pdu_size'
+ *
+ * UE_PDU_SIZE <size>
+ *     set 'ue_pdu_size'
+ *
+ * ENB_RECV_FAILS <fails>
+ *     set the 'enb_recv_fails' flag to <fails>
+ *     (1: recv will fail, 0: recv will succeed)
+ *
+ * UE_RECV_FAILS <fails>
+ *     same as ENB_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)
+ *
+ * ENB_BUFFER_STATUS
+ *     call buffer_status for eNB and print result
+ *
+ * UE_BUFFER_STATUS
+ *     call buffer_status for UE and print result
+ *
+ * ENB_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 {
+  ENB_AM, UE_AM,
+  ENB_UM, UE_UM,
+  TIME, ENB_SDU, UE_SDU, ENB_PDU, UE_PDU,
+  ENB_PDU_SIZE, UE_PDU_SIZE,
+  ENB_RECV_FAILS, UE_RECV_FAILS,
+  MUST_FAIL,
+  ENB_BUFFER_STATUS, UE_BUFFER_STATUS,
+  ENB_DISCARD_SDU, UE_DISCARD_SDU,
+  RE_ESTABLISH
+};
+
+int test[] = {
+/* TEST is defined at compilation time */
+#include TEST
+};
+
+void deliver_sdu_enb_am(void *deliver_sdu_data, struct rlc_entity_t *_entity,
+                        char *buf, int size)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  printf("TEST: ENB: %"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_enb_um(void *deliver_sdu_data, struct rlc_entity_t *_entity,
+                        char *buf, int size)
+{
+  rlc_entity_um_t *entity = (rlc_entity_um_t *)_entity;
+  printf("TEST: ENB: %"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_enb(void *successful_delivery_data,
+                             rlc_entity_t *_entity, int sdu_id)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  printf("TEST: ENB: %"PRIu64": SDU %d was successfully delivered.\n",
+         entity->t_current, sdu_id);
+}
+
+void max_retx_reached_enb(void *max_retx_reached_data, rlc_entity_t *_entity)
+{
+  rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
+  printf("TEST: ENB: %"PRIu64": max RETX reached! radio link failure!\n",
+         entity->t_current);
+  exit(1);
+}
+
+void deliver_sdu_ue_am(void *deliver_sdu_data, struct rlc_entity_t *_entity,
+                       char *buf, int size)
+{
+  rlc_entity_am_t *entity = (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 rlc_entity_t *_entity,
+                       char *buf, int size)
+{
+  rlc_entity_um_t *entity = (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 successful_delivery_ue(void *successful_delivery_data,
+                            rlc_entity_t *_entity, int sdu_id)
+{
+  rlc_entity_am_t *entity = (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, rlc_entity_t *_entity)
+{
+  rlc_entity_am_t *entity = (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)
+{
+  rlc_entity_t *enb = NULL;
+  rlc_entity_t *ue = NULL;
+  int i;
+  int k;
+  char *sdu;
+  char *pdu;
+  rlc_entity_buffer_status_t buffer_status;
+  int enb_do_buffer_status = 0;
+  int ue_do_buffer_status = 0;
+  int size;
+  int pos;
+  int next_byte_enb = 0;
+  int next_byte_ue = 0;
+  int enb_recv_fails = 0;
+  int ue_recv_fails = 0;
+  int enb_pdu_size = 1000;
+  int ue_pdu_size = 1000;
+
+  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 ENB_AM:
+          enb = new_rlc_entity_am(test[pos+1], test[pos+2],
+                                  deliver_sdu_enb_am, NULL,
+                                  successful_delivery_enb, NULL,
+                                  max_retx_reached_enb, NULL,
+                                  test[pos+3], test[pos+4], test[pos+5],
+                                  test[pos+6], test[pos+7], test[pos+8]);
+          pos += 9;
+          break;
+        case UE_AM:
+          ue = new_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]);
+          pos += 9;
+          break;
+        case ENB_UM:
+          enb = new_rlc_entity_um(test[pos+1], test[pos+2],
+                                  deliver_sdu_enb_um, NULL,
+                                  test[pos+3], test[pos+4]);
+          pos += 5;
+          break;
+        case UE_UM:
+          ue = new_rlc_entity_um(test[pos+1], test[pos+2],
+                                 deliver_sdu_ue_um, NULL,
+                                 test[pos+3], test[pos+4]);
+          pos += 5;
+          break;
+        case ENB_SDU:
+          for (k = 0; k < test[pos+2]; k++, next_byte_enb++)
+            sdu[k] = next_byte_enb;
+          printf("TEST: ENB: %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");
+          enb->recv_sdu(enb, 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 ENB_PDU:
+          for (k = 0; k < test[pos+1]; k++)
+            pdu[k] = test[pos+2+k];
+          printf("TEST: ENB: %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 (!enb_recv_fails)
+            enb->recv_pdu(enb, pdu, test[pos+1]);
+          pos += 2 + test[pos+1];
+          break;
+        case ENB_PDU_SIZE:
+          enb_pdu_size = test[pos+1];
+          pos += 2;
+          break;
+        case UE_PDU_SIZE:
+          ue_pdu_size = test[pos+1];
+          pos += 2;
+          break;
+        case ENB_RECV_FAILS:
+          enb_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 ENB_BUFFER_STATUS:
+          enb_do_buffer_status = 1;
+          pos++;
+          break;
+        case UE_BUFFER_STATUS:
+          ue_do_buffer_status = 1;
+          pos++;
+          break;
+        case ENB_DISCARD_SDU:
+          printf("TEST: ENB: %d: discard SDU %d\n", i, test[pos+1]);
+          enb->discard_sdu(enb, 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);
+          enb->reestablishment(enb);
+          ue->reestablishment(ue);
+          pos++;
+          break;
+        }
+    }
+
+    enb->set_time(enb, i);
+    ue->set_time(ue, i);
+
+    if (enb_do_buffer_status) {
+      enb_do_buffer_status = 0;
+      buffer_status = enb->buffer_status(enb, enb_pdu_size);
+      printf("TEST: ENB: %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 = enb->generate_pdu(enb, pdu, enb_pdu_size);
+    if (size) {
+      printf("TEST: ENB: %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 (!enb_recv_fails)
+        enb->recv_pdu(enb, pdu, size);
+    }
+  }
+
+  enb->delete(enb);
+  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/rlc_v2/tests/test1.h b/openair2/LAYER2/rlc_v2/tests/test1.h
new file mode 100644
index 0000000000000000000000000000000000000000..c7744da55c28ed7012cadc71d698777351843b7f
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test1.h
@@ -0,0 +1,14 @@
+/*
+ * basic am test:
+ * at time 1, eNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+    UE_BUFFER_STATUS,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test1.txt.gz b/openair2/LAYER2/rlc_v2/tests/test1.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..1c6661e9ea1c43c854ecf24cdac718215bbd1f22
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test1.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test10.h b/openair2/LAYER2/rlc_v2/tests/test10.h
new file mode 100644
index 0000000000000000000000000000000000000000..c7aca15eb058d7371963f8f29f68e398fa7e1d0b
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test10.h
@@ -0,0 +1,23 @@
+/*
+ * rlc am test resegmentation of PDU segment with several SDUs
+ *   eNB sends 3 SDUs [1..10] [11.20] [21..30], not received
+ *   eNB retx with smaller PDUs, not received
+ *   eNB retx with still smaller PDUs, not received
+ *   then reception on, all passes
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 10,
+    ENB_SDU, 2, 10,
+TIME, 2,
+    ENB_PDU_SIZE, 25,
+TIME, 48,
+    ENB_PDU_SIZE, 15,
+TIME, 100,
+    UE_RECV_FAILS, 0,
+    ENB_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test10.txt.gz b/openair2/LAYER2/rlc_v2/tests/test10.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..68fd3fa0ba7ec7fad990101439087dcdf55693b4
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test10.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test11.h b/openair2/LAYER2/rlc_v2/tests/test11.h
new file mode 100644
index 0000000000000000000000000000000000000000..5801689aea498b2b350967df97de389eaa3481c8
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test11.h
@@ -0,0 +1,37 @@
+/*
+ * rlc am test function rlc_am_reassemble_next_segment
+ *        in r->pdu_byte >= r->so + (r->sdu_offset - r->start->data_offset)
+ *                                + r->sdu_len
+ *        when case 'if (r->e)' is false
+ *   eNB sends 3 SDUs [1..10] [11.20] [21..30], not received
+ *   eNB retx with smaller PDUs, not received
+ *   eNB retx with still smaller PDUs, not received
+ *   then UE reception on
+ *   then custom PDUs, first a small part of head of original PDU
+ *                     then a bigger part, covering the first part
+ *                     so that the beginning of this part triggers the 'while'
+ *   then eNB reception on, all passes
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 10,
+    ENB_SDU, 2, 10,
+TIME, 2,
+    ENB_PDU_SIZE, 25,
+TIME, 48,
+    ENB_PDU_SIZE, 15,
+TIME, 95,
+    ENB_BUFFER_STATUS,
+TIME, 99,
+    UE_RECV_FAILS, 0,
+    ENB_PDU, 14, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+    ENB_PDU, 25, 0xec, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
+TIME, 100,
+    ENB_RECV_FAILS, 0,
+TIME, 134,
+    UE_BUFFER_STATUS,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test11.txt.gz b/openair2/LAYER2/rlc_v2/tests/test11.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..ea435a666025ab5d90ca2992b91dd94e1551a654
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test11.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test12.h b/openair2/LAYER2/rlc_v2/tests/test12.h
new file mode 100644
index 0000000000000000000000000000000000000000..0387f0aa7f380b65224a4f71f97de78548bb5c59
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test12.h
@@ -0,0 +1,34 @@
+/*
+ * rlc am test function rlc_am_reassemble_next_segment
+ *        in r->pdu_byte >= r->so + (r->sdu_offset - r->start->data_offset)
+ *                                + r->sdu_len
+ *        when case 'if (r->e)' is true
+ *   eNB sends 4 SDUs [1..5] [6..10] [11.20] [21..30], not received
+ *   eNB retx with smaller PDUs, not received
+ *   eNB retx with still smaller PDUs, not received
+ *   then UE reception on
+ *   then custom PDUs, first a small part of head of original PDU
+ *                     then a bigger part, covering the first part
+ *                     so that the beginning of this part triggers the 'while'
+ *   then eNB reception on, all passes
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_SDU, 0, 5,
+    ENB_SDU, 1, 5,
+    ENB_SDU, 2, 10,
+    ENB_SDU, 3, 10,
+TIME, 2,
+    ENB_PDU_SIZE, 25,
+TIME, 48,
+    ENB_PDU_SIZE, 15,
+TIME, 99,
+    UE_RECV_FAILS, 0,
+    ENB_PDU, 15, 0xec, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    ENB_PDU, 25, 0xec, 0x00, 0x00, 0x00, 0x80, 0x50, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+TIME, 100,
+    ENB_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test12.txt.gz b/openair2/LAYER2/rlc_v2/tests/test12.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..988d7ae644c008cef2f34adce448d00a3a7ce4e1
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test12.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test13.h b/openair2/LAYER2/rlc_v2/tests/test13.h
new file mode 100644
index 0000000000000000000000000000000000000000..a57bd43a946e3c5fdd3d38a482ba6bdbcee85318
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test13.h
@@ -0,0 +1,30 @@
+/*
+ * rlc am test function process_received_ack with something in
+ *             the retransmit_list to put in the ack_list
+ * eNB sends 4 PDUs, not received
+ * eNB retransmits 4th PDU, received, ACKed with NACKs for PDU 1, 2, 3
+ * UE receives custom PDU for 1, 2, 3, 4 (they are not sent by eNB)
+ * (4 resent to have the P bit set)
+ * UE sends ACK for all, eNB puts from retransmit_list to ack_list
+ *
+ * Maybe not very realistic (custom PDUs).
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_PDU_SIZE, 12,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 10,
+    ENB_SDU, 2, 10,
+    ENB_SDU, 3, 10,
+TIME, 10,
+    UE_RECV_FAILS, 0,
+    ENB_RECV_FAILS, 0,
+TIME, 87,
+    ENB_PDU, 12, 0x80, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+    ENB_PDU, 12, 0x80, 0x01, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+    ENB_PDU, 12, 0x80, 0x02, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+    ENB_PDU, 12, 0xa0, 0x03, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test13.txt.gz b/openair2/LAYER2/rlc_v2/tests/test13.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..55a26712db1f9d1efb68b8a66a275d20b4867beb
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test13.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test14.h b/openair2/LAYER2/rlc_v2/tests/test14.h
new file mode 100644
index 0000000000000000000000000000000000000000..0a3a50179614faf31f9e6c3fc1e473fd75204c05
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test14.h
@@ -0,0 +1,12 @@
+/*
+ * rlc am test max_retx_reached
+ * eNB sends PDU, never received
+ */
+TIME, 1,
+    MUST_FAIL,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_SDU, 0, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test14.txt.gz b/openair2/LAYER2/rlc_v2/tests/test14.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..93aa5de81ea42ae69511552066c75ccf11febbea
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test14.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test15.h b/openair2/LAYER2/rlc_v2/tests/test15.h
new file mode 100644
index 0000000000000000000000000000000000000000..4adf93f81c045c26e8e6d3fab5013f7fc5071514
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test15.h
@@ -0,0 +1,42 @@
+/*
+ * rlc am test so_overlap
+ * eNB sends PDU, not received
+ * then PDU is segmented in 3 parts, part 1 & 3 not received,
+ * then we generate a fake control PDU from UE to eNB that
+ * contains NACK with so_start/so_end being inside part 2.
+ *
+ * code to generate fake control PDU:
+ *  rlc_pdu_encoder_init(&e, out, 100);
+ *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // D/C
+ *  rlc_pdu_encoder_put_bits(&e, 0, 3);    // CPT
+ *  rlc_pdu_encoder_put_bits(&e, 2, 10);   // ack_sn
+ *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // e1
+ *  rlc_pdu_encoder_put_bits(&e, 1, 10);   // nack_sn
+ *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // e1
+ *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // e2
+ *  rlc_pdu_encoder_put_bits(&e, 14, 15);  // so_start
+ *  rlc_pdu_encoder_put_bits(&e, 16, 15);  // so_end
+ *  rlc_pdu_encoder_align(&e);
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 8,
+    ENB_RECV_FAILS, 1,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+    ENB_SDU, 1, 30,
+TIME, 20,
+    ENB_PDU_SIZE, 14,
+TIME, 48,
+    UE_RECV_FAILS, 0,
+TIME, 49,
+    UE_RECV_FAILS, 1,
+TIME, 50,
+    UE_RECV_FAILS, 0,
+TIME, 60,
+    ENB_RECV_FAILS, 0,
+    UE_PDU, 8, 0x00, 0x0a, 0x00, 0xa0, 0x03, 0x80, 0x08, 0x00,
+TIME, 70,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test15.txt.gz b/openair2/LAYER2/rlc_v2/tests/test15.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..f6f25dac9857198c69c7a5c05bd468b9458d65f9
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test15.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test16.h b/openair2/LAYER2/rlc_v2/tests/test16.h
new file mode 100644
index 0000000000000000000000000000000000000000..862cecf344bdcea3978fd055b4c62242702c7bb6
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test16.h
@@ -0,0 +1,48 @@
+/*
+ * rlc am test process_received_nack
+ * Same events as for test15 except the fake control PDU
+ * does not ACK anything (ack_sn = 0) so that PDU in the
+ * wait_list are not transfered into the ack_list and
+ * we cover the case:
+ *    } else {
+ *      prev = cur;
+ *      cur = cur->next;
+ *    }
+ * for the wait_list case.
+ *
+ *  code to generate fake control PDU:
+ *  rlc_pdu_encoder_init(&e, out, 100);
+ *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // D/C
+ *  rlc_pdu_encoder_put_bits(&e, 0, 3);    // CPT
+ *  rlc_pdu_encoder_put_bits(&e, 0, 10);   // ack_sn
+ *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // e1
+ *  rlc_pdu_encoder_put_bits(&e, 1, 10);   // nack_sn
+ *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // e1
+ *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // e2
+ *  rlc_pdu_encoder_put_bits(&e, 14, 15);  // so_start
+ *  rlc_pdu_encoder_put_bits(&e, 16, 15);  // so_end
+ *  rlc_pdu_encoder_align(&e);
+ */
+
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 8,
+    ENB_RECV_FAILS, 1,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+    ENB_SDU, 1, 30,
+TIME, 20,
+    ENB_PDU_SIZE, 14,
+TIME, 48,
+    UE_RECV_FAILS, 0,
+TIME, 49,
+    UE_RECV_FAILS, 1,
+TIME, 50,
+    UE_RECV_FAILS, 0,
+TIME, 60,
+    ENB_RECV_FAILS, 0,
+    UE_PDU, 8, 0x00, 0x02, 0x00, 0xa0, 0x03, 0x80, 0x08, 0x00,
+TIME, 70,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test16.txt.gz b/openair2/LAYER2/rlc_v2/tests/test16.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..61f36c292ec8ec46edaa3de7d77b235d78322a06
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test16.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test17.h b/openair2/LAYER2/rlc_v2/tests/test17.h
new file mode 100644
index 0000000000000000000000000000000000000000..a2e6c237de9b8302744bb022ad22aa81025a2639
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test17.h
@@ -0,0 +1,30 @@
+/*
+ * rlc am test function process_received_nack
+ *             case 'check that VT(A) <= sn < VT(S)'
+ * eNB sends PDU, not received, resends segmented
+ * we generate a fake control PDU containing nack_sn == 10,
+ * to fail the 'check ...' and cover the return.
+ *
+ *  code to generate fake control PDU:
+ *  rlc_pdu_encoder_init(&e, out, 100);
+ *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // D/C
+ *  rlc_pdu_encoder_put_bits(&e, 0, 3);    // CPT
+ *  rlc_pdu_encoder_put_bits(&e, 0, 10);   // ack_sn
+ *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // e1
+ *  rlc_pdu_encoder_put_bits(&e, 10, 10);  // nack_sn
+ *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // e1
+ *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // e2
+ *  rlc_pdu_encoder_align(&e);
+ */
+
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 30,
+    ENB_RECV_FAILS, 1,
+TIME, 20,
+    ENB_PDU_SIZE, 14,
+TIME, 60,
+    ENB_RECV_FAILS, 0,
+    UE_PDU, 4, 0x00, 0x02, 0x05, 0x00,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test17.txt.gz b/openair2/LAYER2/rlc_v2/tests/test17.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..a35b5cecd18759c7c683afd4e83df2fc7ba38293
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test17.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test18.h b/openair2/LAYER2/rlc_v2/tests/test18.h
new file mode 100644
index 0000000000000000000000000000000000000000..0ac25d5c915ad4db43c8bf93fcbaeafae9619f0e
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test18.h
@@ -0,0 +1,10 @@
+/*
+ * test rlc am simulate rx pdu buffer full
+ * eNB sends too big PDU to UE, rejected because buffer full
+ */
+TIME, 1,
+    MUST_FAIL,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 10, 10, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test18.txt.gz b/openair2/LAYER2/rlc_v2/tests/test18.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..e119c2b018fcece7c4504135b4bf09c23d902590
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test18.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test19.h b/openair2/LAYER2/rlc_v2/tests/test19.h
new file mode 100644
index 0000000000000000000000000000000000000000..f28e7609f451a9becdb7f5c4737681c4a69d501a
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test19.h
@@ -0,0 +1,54 @@
+/*
+ * test rlc am bad PDU
+ * eNB sends custom PDUs to UE, all of them are wrong for a reason or another
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    /* data PDU, LI == 0
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // D/C
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // RF
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // P
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 10);   // SN
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 11);   // LI
+     */
+    ENB_PDU, 4, 0x84, 0x00, 0x00, 0x00,
+    /* data PDU, no data
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // D/C
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // RF
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // P
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 10);   // SN
+     */
+    ENB_PDU, 2, 0x80, 0x00,
+    /* data PDU, LI == 2 > data size == 1
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // D/C
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // RF
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // P
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 10);   // SN
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 2, 11);   // LI
+     *  rlc_pdu_encoder_align(&e);
+     *  rlc_pdu_encoder_put_bits(&e, 0, 8);    // 1 byte of data
+     */
+    ENB_PDU, 5, 0x84, 0x00, 0x00, 0x20, 0x00,
+    /* control PDU, CPT != 0
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // D/C
+     *  rlc_pdu_encoder_put_bits(&e, 2, 3);    // CPT
+     */
+    ENB_PDU, 1, 0x20,
+    /* data PDU, but only 1 byte
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // D/C
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // RF
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // P
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // E
+     */
+    ENB_PDU, 1, 0x84,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test19.txt.gz b/openair2/LAYER2/rlc_v2/tests/test19.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..a3c034e7298d5298cd54493622afdfca7c51b9be
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test19.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test2.h b/openair2/LAYER2/rlc_v2/tests/test2.h
new file mode 100644
index 0000000000000000000000000000000000000000..ba00920778b2821b5807cb9ecf4e5424df892df7
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test2.h
@@ -0,0 +1,10 @@
+/*
+ * basic am test:
+ * at time 1, eNB receives an SDU of 16000 bytes
+ */
+
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 16000,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test2.txt.gz b/openair2/LAYER2/rlc_v2/tests/test2.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..9961ff3a1020fe5ecf83b49b11ede590b229de6d
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test2.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test20.h b/openair2/LAYER2/rlc_v2/tests/test20.h
new file mode 100644
index 0000000000000000000000000000000000000000..54f4bec720ab5c6b28123d372f541ddfbc88772d
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test20.h
@@ -0,0 +1,28 @@
+/*
+ * rlc am test full tx window
+ * for that eNB sends a lot of small PDUs
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 513,
+    ENB_PDU_SIZE, 3,
+    ENB_RECV_FAILS, 1,
+    ENB_BUFFER_STATUS,
+TIME, 511,
+    UE_BUFFER_STATUS,
+TIME, 512,
+    UE_BUFFER_STATUS,
+TIME, 513,
+    UE_BUFFER_STATUS,
+TIME, 557,
+    ENB_BUFFER_STATUS,
+TIME, 558,
+    ENB_BUFFER_STATUS,
+TIME, 559,
+    ENB_BUFFER_STATUS,
+TIME, 600,
+    ENB_BUFFER_STATUS,
+    ENB_RECV_FAILS, 0,
+TIME, -1
+
diff --git a/openair2/LAYER2/rlc_v2/tests/test20.txt.gz b/openair2/LAYER2/rlc_v2/tests/test20.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..5fedad91a452500def6a850e2da72d99f68346d0
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test20.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test21.h b/openair2/LAYER2/rlc_v2/tests/test21.h
new file mode 100644
index 0000000000000000000000000000000000000000..ba2a2088e683df7682e62b8273ed044d5cfc1e31
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test21.h
@@ -0,0 +1,18 @@
+/*
+ * rlc am test big SDU (size > 2047)
+ * first generate SDU with exactly 2047 bytes
+ * later on generate SDU with exactly 2048 bytes
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 20,
+    ENB_SDU, 1, 2047,
+    ENB_SDU, 2, 20,
+    ENB_PDU_SIZE, 2200,
+TIME, 10,
+    ENB_SDU, 3, 20,
+    ENB_SDU, 4, 2048,
+    ENB_SDU, 5, 20,
+TIME, -1
+
diff --git a/openair2/LAYER2/rlc_v2/tests/test21.txt.gz b/openair2/LAYER2/rlc_v2/tests/test21.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..7fc8cbacdef75cc7a77684509989bc7d414e37d6
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test21.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test22.h b/openair2/LAYER2/rlc_v2/tests/test22.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e2e8cd410acd6e122fb39047d6438a2e33dfe85
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test22.h
@@ -0,0 +1,25 @@
+/*
+ * am test: ask for retx with TX buffer too small
+ * then ask for status with buffer too small
+ */
+
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 100,
+    UE_RECV_FAILS, 1,
+TIME, 47,
+    ENB_PDU_SIZE, 4,
+    ENB_BUFFER_STATUS,
+    UE_BUFFER_STATUS,
+TIME, 48,
+    ENB_PDU_SIZE, 1000,
+    UE_PDU_SIZE, 1,
+    UE_BUFFER_STATUS,
+    UE_RECV_FAILS, 0,
+TIME, 49,
+    UE_BUFFER_STATUS,
+TIME, 50,
+    UE_PDU_SIZE, 1000,
+    UE_BUFFER_STATUS,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test22.txt.gz b/openair2/LAYER2/rlc_v2/tests/test22.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..cdc7f51a162aae7cff631abeb0324a088ab48907
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test22.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test23.h b/openair2/LAYER2/rlc_v2/tests/test23.h
new file mode 100644
index 0000000000000000000000000000000000000000..5ad2d25b7defac794d7cfe9c71e3c440c2dd1070
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test23.h
@@ -0,0 +1,9 @@
+/*
+ * am test: basic test with poll_byte == 1
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, 1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, 1, 4,
+    ENB_SDU, 0, 30,
+    ENB_PDU_SIZE, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test23.txt.gz b/openair2/LAYER2/rlc_v2/tests/test23.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..3d66e6afa45fde0e6ebc6f9907c15b970f3a13d7
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test23.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test24.h b/openair2/LAYER2/rlc_v2/tests/test24.h
new file mode 100644
index 0000000000000000000000000000000000000000..2393f7a95a8249b0c33e93dc36ca65b5996a342d
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test24.h
@@ -0,0 +1,9 @@
+/*
+ * am test: basic test with poll_pdu == 2
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, 2, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, 2, -1, 4,
+    ENB_SDU, 0, 50,
+    ENB_PDU_SIZE, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test24.txt.gz b/openair2/LAYER2/rlc_v2/tests/test24.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..6c457987dcf297d3beb6a93f27aeddbac5fd58af
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test24.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test25.h b/openair2/LAYER2/rlc_v2/tests/test25.h
new file mode 100644
index 0000000000000000000000000000000000000000..ddb584cdf64af9a5eb359512a4fd8927e2e235a3
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test25.h
@@ -0,0 +1,8 @@
+/*
+ * am test: reject SDU because not enough room in rx buffer
+ */
+TIME, 1,
+    ENB_AM, 10, 10, 35, 0, 45, -1, -1, 4,
+    UE_AM, 10, 10, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 50,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test25.txt.gz b/openair2/LAYER2/rlc_v2/tests/test25.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..7ad895aaccc095103430cffdf324d3976077f0f2
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test25.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test26.h b/openair2/LAYER2/rlc_v2/tests/test26.h
new file mode 100644
index 0000000000000000000000000000000000000000..95d8367247a6a10ae5e880a8d34e0973d9936a11
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test26.h
@@ -0,0 +1,25 @@
+/*
+ * am test: test function check_t_poll_retransmit
+ *               case 'PDU with SN = VT(S)-1 not found?'
+ * eNB sends some PDUs, UE receives none
+ * then UE receives the first retransmitted PDU and nothing more
+ * until poll retransmit occurs again in the eNB to trigger the case
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+    UE_RECV_FAILS, 1,
+TIME, 2,
+    ENB_SDU, 1, 10,
+TIME, 3,
+    ENB_SDU, 2, 10,
+TIME, 4,
+    ENB_SDU, 3, 10,
+TIME, 50,
+    UE_RECV_FAILS, 0,
+TIME, 51,
+    UE_RECV_FAILS, 1,
+TIME, 100,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test26.txt.gz b/openair2/LAYER2/rlc_v2/tests/test26.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..85f1af55f691179defd6ab24bf8d4c0960d986dc
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test26.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test27.h b/openair2/LAYER2/rlc_v2/tests/test27.h
new file mode 100644
index 0000000000000000000000000000000000000000..224fd1218592c8cd5834ad7dccb736d295553e4e
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test27.h
@@ -0,0 +1,17 @@
+/*
+ * am test: test function check_t_poll_retransmit
+ *               case 'do we meet conditions of 36.322 5.2.2.3?'
+ * eNB sends one PDU, UE does not receive
+ * just before calling check_t_poll_retransmit, eNB receives a new SDU
+ * for the function 'check_poll_after_pdu_assembly' to fail
+ * then UE receives all what eNB sends
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+    UE_RECV_FAILS, 1,
+TIME, 47,
+    ENB_SDU, 1, 10,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test27.txt.gz b/openair2/LAYER2/rlc_v2/tests/test27.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..15fc41defe11b0d13f2b044a3e3b02eab4c133ad
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test27.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test28.h b/openair2/LAYER2/rlc_v2/tests/test28.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac768f36523f6afa52f7459146ec981c58aea2e1
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test28.h
@@ -0,0 +1,18 @@
+/*
+ * am test: test function check_t_reordering,
+ *               case 'update VR(MS) to first SN >= VR(X) for which not
+ *                     all PDU segments have been received'
+ * eNB sends 3 PDUs, first not received, two others received
+ * later on, everything is received
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+    UE_RECV_FAILS, 1,
+TIME, 2,
+    UE_RECV_FAILS, 0,
+    ENB_SDU, 1, 10,
+TIME, 3,
+    ENB_SDU, 2, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test28.txt.gz b/openair2/LAYER2/rlc_v2/tests/test28.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..760d1f2b84f0aa4849d987f157f258ab7d22b90b
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test28.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test29.h b/openair2/LAYER2/rlc_v2/tests/test29.h
new file mode 100644
index 0000000000000000000000000000000000000000..61bb183641d1251b26afa17055a6bc9b8fd611a3
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test29.h
@@ -0,0 +1,21 @@
+/*
+ * am test: test function check_t_reordering,
+ *               case 'VR(H) > VR(MS)'
+ * eNB sends 4 PDUs, only 1st and 3rd are received
+ * later on, everything is received
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+    UE_RECV_FAILS, 1,
+TIME, 2,
+    UE_RECV_FAILS, 0,
+    ENB_SDU, 1, 10,
+TIME, 3,
+    UE_RECV_FAILS, 1,
+    ENB_SDU, 2, 10,
+TIME, 4,
+    UE_RECV_FAILS, 0,
+    ENB_SDU, 3, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test29.txt.gz b/openair2/LAYER2/rlc_v2/tests/test29.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..265735edbceb54e2a54c0f2d7c171080262c95de
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test29.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test3.h b/openair2/LAYER2/rlc_v2/tests/test3.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a469d82e24a872af68c8e11c83414797acebc87
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test3.h
@@ -0,0 +1,11 @@
+/*
+ * basic am test:
+ * at time 1, eNB receives an SDU of 16001 bytes
+ */
+
+TIME, 1,
+    MUST_FAIL,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 16001,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test3.txt.gz b/openair2/LAYER2/rlc_v2/tests/test3.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..30a96e22781c5f1d3b4711f48fc337cef23a574e
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test3.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test30.h b/openair2/LAYER2/rlc_v2/tests/test30.h
new file mode 100644
index 0000000000000000000000000000000000000000..feeee977fd371854098c482e867a4912f5bf3576
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test30.h
@@ -0,0 +1,16 @@
+/*
+ * am test: test function generate_status
+ *               enter the while loop 'go to highest full sn+1 for ACK'
+ * eNB sends several PDUs, only the last is received
+ * UE sends status PDU of a chosen size that let the code enter the while
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 70,
+    ENB_PDU_SIZE, 12,
+    UE_RECV_FAILS, 1,
+TIME, 7,
+    UE_RECV_FAILS, 0,
+    UE_PDU_SIZE, 12,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test30.txt.gz b/openair2/LAYER2/rlc_v2/tests/test30.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..eeb856c3414ce973ac95ab4e3258f6e9aacd3ff1
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test30.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test31.h b/openair2/LAYER2/rlc_v2/tests/test31.h
new file mode 100644
index 0000000000000000000000000000000000000000..a978c69b39a056233c332724f155a24912709015
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test31.h
@@ -0,0 +1,10 @@
+/*
+ * um test: several SDUs in a PDU (field length 5 bits)
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 20,
+    ENB_SDU, 2, 30,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test31.txt.gz b/openair2/LAYER2/rlc_v2/tests/test31.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..2c5e6fcc3415544b1ca81a258e81eef6d190311b
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test31.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test32.h b/openair2/LAYER2/rlc_v2/tests/test32.h
new file mode 100644
index 0000000000000000000000000000000000000000..69d068cc836cd33d8541d76864d015bc9207ff99
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test32.h
@@ -0,0 +1,10 @@
+/*
+ * um test: several SDUs in a PDU (field length 10 bits)
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 10,
+    UE_UM, 100000, 100000, 35, 10,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 20,
+    ENB_SDU, 2, 30,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test32.txt.gz b/openair2/LAYER2/rlc_v2/tests/test32.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..0b4633045337017eb15e520e995f9754478fa423
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test32.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test33.h b/openair2/LAYER2/rlc_v2/tests/test33.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e907db577f80fe0f565a7d7ed86cef11b8c4638
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test33.h
@@ -0,0 +1,18 @@
+/*
+ * um test: test function rlc_um_reassemble_pdu, discard SDU
+ *               case '!(fi & 0x02'
+ * eNB sends 33 PDUs covering 1 SDU, only PDU 0 received (with SN=0 and FI=1)
+ * then eNB sends 1 PDU covering 1 SDU (so SN=1 and FI=0 for this one)
+ * received by UE
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    ENB_SDU, 0, 33,
+    ENB_PDU_SIZE, 2,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 34,
+    UE_RECV_FAILS, 0,
+    ENB_SDU, 1, 1,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test33.txt.gz b/openair2/LAYER2/rlc_v2/tests/test33.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..08cb366be415251d3c552da7b4f22615fa8f6138
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test33.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test34.h b/openair2/LAYER2/rlc_v2/tests/test34.h
new file mode 100644
index 0000000000000000000000000000000000000000..da119a6047fa5fc03e274b62cb330cf7ce21e925
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test34.h
@@ -0,0 +1,15 @@
+/*
+ * um test: trigger some cases in rlc_um_reception_actions
+ * eNB sends several PDUs, only the beginning PDUs and ending PDUs are
+ * received. Middle PDUs are not.
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    ENB_SDU, 0, 40,
+    ENB_PDU_SIZE, 2,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 8,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test34.txt.gz b/openair2/LAYER2/rlc_v2/tests/test34.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..aabbe570e56ea236be3853ee4f8f445dbde899fd
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test34.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test35.h b/openair2/LAYER2/rlc_v2/tests/test35.h
new file mode 100644
index 0000000000000000000000000000000000000000..35ccec1a42a4b0e7fbcf05a6d6742ffb44efa02f
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test35.h
@@ -0,0 +1,9 @@
+/*
+ * um: discard PDU because rx buffer full
+ * eNB sends a PDU too big
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 10, 10, 35, 5,
+    ENB_SDU, 0, 40,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test35.txt.gz b/openair2/LAYER2/rlc_v2/tests/test35.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..6581c390c73f05a34696e1effc3a7194e5f97f3c
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test35.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test36.h b/openair2/LAYER2/rlc_v2/tests/test36.h
new file mode 100644
index 0000000000000000000000000000000000000000..0a49527a923350ae87bab862bcb8d094818c0f15
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test36.h
@@ -0,0 +1,14 @@
+/*
+ * um: discard according to 36.322 5.1.2.2.2
+ * eNB sends many PDUs. 1st is received, then not, then again.
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    ENB_SDU, 0, 33,
+    ENB_PDU_SIZE, 2,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 22,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test36.txt.gz b/openair2/LAYER2/rlc_v2/tests/test36.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..6ad38454f9ba8cbe02dbb137842d91cab52bbcca
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test36.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test37.h b/openair2/LAYER2/rlc_v2/tests/test37.h
new file mode 100644
index 0000000000000000000000000000000000000000..b418e2c7151ac82ddae3c623b74feae3360e6502
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test37.h
@@ -0,0 +1,37 @@
+/*
+ * um: some wrong PDUs
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    /* LI == 0
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 5);    // SN
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 11);   // LI
+     */
+    ENB_PDU, 3, 0x20, 0x00, 0x00,
+    /* no data
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 5);    // SN
+     */
+    ENB_PDU, 1, 0x00,
+    /* LI == 2 >= data_size == 1
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 5);    // SN
+     *  rlc_pdu_encoder_put_bits(&e, 0, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 2, 11);   // LI
+     *  rlc_pdu_encoder_align(&e);
+     *  rlc_pdu_encoder_put_bits(&e, 0, 8);    // 1 byte of data
+     */
+    ENB_PDU, 4, 0x20, 0x00, 0x20, 0x00,
+    /* PDU with E == 1 but has size 1 byte only (truncated PDU)
+     *  rlc_pdu_encoder_put_bits(&e, 0, 2);    // FI
+     *  rlc_pdu_encoder_put_bits(&e, 1, 1);    // E
+     *  rlc_pdu_encoder_put_bits(&e, 0, 5);    // SN
+     */
+    ENB_PDU, 1, 0x20,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test37.txt.gz b/openair2/LAYER2/rlc_v2/tests/test37.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..2a1a837bf0329b2859fa9e71ac9a4bc460c55075
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test37.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test38.h b/openair2/LAYER2/rlc_v2/tests/test38.h
new file mode 100644
index 0000000000000000000000000000000000000000..66a37207e0274ddf93abfd7064908ec4a4c4b32e
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test38.h
@@ -0,0 +1,22 @@
+/*
+ * um: test some cases of functions tx_pdu_size and rlc_entity_um_generate_pdu
+ * eNB has too much data to fit in one PDU
+ * then later eNB wants to send an SDU of size > 2047
+ * then later eNB sends several SDUs in one PDU
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    ENB_PDU_SIZE, 2050,
+    ENB_SDU, 0, 1500,
+    ENB_SDU, 1, 1500,
+    ENB_SDU, 2, 10,
+TIME, 10,
+    ENB_SDU, 3, 2048,
+    ENB_SDU, 4, 10,
+TIME, 20,
+    ENB_SDU, 5, 10,
+    ENB_SDU, 6, 10,
+    ENB_SDU, 7, 10,
+    ENB_SDU, 8, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test38.txt.gz b/openair2/LAYER2/rlc_v2/tests/test38.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..57e4ed270acc00bd861eeb824e24cc0a154c3a60
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test38.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test39.h b/openair2/LAYER2/rlc_v2/tests/test39.h
new file mode 100644
index 0000000000000000000000000000000000000000..8c926b3745ff69d70dd75f0d421e763c3451283a
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test39.h
@@ -0,0 +1,9 @@
+/*
+ * um: SDU too big
+ */
+TIME, 1,
+    MUST_FAIL,
+    ENB_UM, 10, 10, 35, 5,
+    UE_UM, 100, 100, 35, 5,
+    ENB_SDU, 0, 16001,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test39.txt.gz b/openair2/LAYER2/rlc_v2/tests/test39.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..c4f6501d596f474dd77abbce265bd7ab4e7c9cd4
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test39.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test4.h b/openair2/LAYER2/rlc_v2/tests/test4.h
new file mode 100644
index 0000000000000000000000000000000000000000..8801096de117e51e9a0a07ccc6bd4c22114ef905
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test4.h
@@ -0,0 +1,13 @@
+/*
+ * basic um test: UE field length 5 bits
+ * at time 1, eNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    ENB_SDU, 0, 10,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test4.txt.gz b/openair2/LAYER2/rlc_v2/tests/test4.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..4339005cd60ae367d0f4bd3bf919253bcad82241
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test4.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test40.h b/openair2/LAYER2/rlc_v2/tests/test40.h
new file mode 100644
index 0000000000000000000000000000000000000000..478fe1af06536d88afe7c9c72abb4e91742119f7
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test40.h
@@ -0,0 +1,9 @@
+/*
+ * um: not enough room in SDU list
+ */
+TIME, 1,
+    ENB_UM, 10, 10, 35, 5,
+    UE_UM, 100, 100, 35, 5,
+    ENB_SDU, 0, 20,
+    ENB_BUFFER_STATUS,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test40.txt.gz b/openair2/LAYER2/rlc_v2/tests/test40.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..38d4b31cdaa43d1bac222a9d92ea9a615201cae6
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test40.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test41.h b/openair2/LAYER2/rlc_v2/tests/test41.h
new file mode 100644
index 0000000000000000000000000000000000000000..076d3e0d8c041f3310779967507157b91cea6eee
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test41.h
@@ -0,0 +1,45 @@
+/*
+ * um: test function check_t_reordering
+ * eNB sends PDUs, UE receives some and some not
+ */
+TIME, 1,
+    ENB_UM, 10000, 10000, 35, 5,
+    UE_UM, 10000, 10000, 35, 5,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 10,
+    ENB_SDU, 2, 10,
+    ENB_SDU, 3, 10,
+    ENB_SDU, 4, 10,
+    ENB_SDU, 5, 10,
+    ENB_SDU, 6, 10,
+    ENB_SDU, 7, 10,
+    ENB_SDU, 8, 10,
+    ENB_SDU, 9, 10,
+    ENB_SDU, 10, 10,
+    ENB_SDU, 11, 10,
+    ENB_SDU, 12, 10,
+    ENB_SDU, 13, 10,
+    ENB_SDU, 14, 10,
+    ENB_SDU, 15, 10,
+    ENB_SDU, 16, 10,
+    ENB_SDU, 17, 10,
+    ENB_SDU, 18, 10,
+    ENB_SDU, 19, 10,
+    ENB_SDU, 20, 10,
+    ENB_SDU, 21, 10,
+    ENB_SDU, 22, 10,
+    ENB_SDU, 23, 10,
+    ENB_SDU, 24, 10,
+    ENB_SDU, 25, 10,
+    ENB_PDU_SIZE, 40,
+TIME, 2,
+    UE_RECV_FAILS, 1,
+TIME, 3,
+    UE_RECV_FAILS, 0,
+TIME, 6,
+    UE_RECV_FAILS, 1,
+TIME, 7,
+    UE_RECV_FAILS, 0,
+TIME, 8,
+    UE_RECV_FAILS, 1,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test41.txt.gz b/openair2/LAYER2/rlc_v2/tests/test41.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..8b799ac084ca15f8c1914c93d71d2c25d6271e7d
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test41.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test42.h b/openair2/LAYER2/rlc_v2/tests/test42.h
new file mode 100644
index 0000000000000000000000000000000000000000..66f27b9dac46468006efea07f7e691db53a45ee1
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test42.h
@@ -0,0 +1,39 @@
+/*
+ * am test: test rlc_entity_am_discard_sdu
+ * eNB and UE get some SDU, later on some are discarded
+ */
+
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 10,
+    ENB_SDU, 2, 10,
+    ENB_SDU, 3, 10,
+    ENB_PDU_SIZE, 23,
+TIME, 2,
+    ENB_DISCARD_SDU, 0,
+    ENB_DISCARD_SDU, 2,
+    ENB_DISCARD_SDU, 3,
+    ENB_DISCARD_SDU, 1,
+TIME, 10,
+    UE_SDU, 0, 5,
+    UE_SDU, 1, 5,
+    UE_SDU, 2, 5,
+    UE_SDU, 3, 5,
+    UE_SDU, 4, 5,
+    UE_SDU, 5, 5,
+    UE_PDU_SIZE, 13,
+TIME, 12,
+    UE_DISCARD_SDU, 3,
+    UE_DISCARD_SDU, 1,
+    UE_DISCARD_SDU, 0,
+    UE_DISCARD_SDU, 5,
+    UE_DISCARD_SDU, 4,
+    UE_DISCARD_SDU, 2,
+TIME, 30,
+    UE_SDU, 6, 5,
+    UE_DISCARD_SDU, 6,
+TIME, 31,
+    UE_SDU, 7, 8,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test42.txt.gz b/openair2/LAYER2/rlc_v2/tests/test42.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..cf9f45c88268e0a986a41c335480dded4f33abbd
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test42.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test43.h b/openair2/LAYER2/rlc_v2/tests/test43.h
new file mode 100644
index 0000000000000000000000000000000000000000..e594437ae8869c8d4f08d975ccaf7ccf5591ee85
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test43.h
@@ -0,0 +1,39 @@
+/*
+ * um test: test rlc_entity_um_discard_sdu
+ * eNB and UE get some SDU, later on some are discarded
+ */
+
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 10,
+    UE_UM, 100000, 100000, 35, 10,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 10,
+    ENB_SDU, 2, 10,
+    ENB_SDU, 3, 10,
+    ENB_PDU_SIZE, 23,
+TIME, 2,
+    ENB_DISCARD_SDU, 0,
+    ENB_DISCARD_SDU, 2,
+    ENB_DISCARD_SDU, 3,
+    ENB_DISCARD_SDU, 1,
+TIME, 10,
+    UE_SDU, 0, 5,
+    UE_SDU, 1, 5,
+    UE_SDU, 2, 5,
+    UE_SDU, 3, 5,
+    UE_SDU, 4, 5,
+    UE_SDU, 5, 5,
+    UE_PDU_SIZE, 13,
+TIME, 12,
+    UE_DISCARD_SDU, 3,
+    UE_DISCARD_SDU, 1,
+    UE_DISCARD_SDU, 0,
+    UE_DISCARD_SDU, 5,
+    UE_DISCARD_SDU, 4,
+    UE_DISCARD_SDU, 2,
+TIME, 30,
+    UE_SDU, 6, 5,
+    UE_DISCARD_SDU, 6,
+TIME, 31,
+    UE_SDU, 7, 8,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test43.txt.gz b/openair2/LAYER2/rlc_v2/tests/test43.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..3387b6530e11728fbd180554a216f20c2b4ef2f8
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test43.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test44.h b/openair2/LAYER2/rlc_v2/tests/test44.h
new file mode 100644
index 0000000000000000000000000000000000000000..cc9873ac34b40f7c030a7bf16ea68860bd3bf808
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test44.h
@@ -0,0 +1,20 @@
+/*
+ * am: test function rlc_entity_am_reestablishment
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    RE_ESTABLISH,
+TIME, 2,
+    ENB_SDU, 0, 10,
+    RE_ESTABLISH,
+TIME, 3,
+    ENB_SDU, 0, 40,
+    ENB_PDU_SIZE, 14,
+    UE_RECV_FAILS, 1,
+TIME, 4,
+    UE_RECV_FAILS, 0,
+TIME, 10,
+    RE_ESTABLISH,
+TIME, -1
+
diff --git a/openair2/LAYER2/rlc_v2/tests/test44.txt.gz b/openair2/LAYER2/rlc_v2/tests/test44.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..bdad9e3fbc5ce1eb162b82c6ea82b0c8cf6fef86
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test44.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test45.h b/openair2/LAYER2/rlc_v2/tests/test45.h
new file mode 100644
index 0000000000000000000000000000000000000000..c27fd8e2f0641bef9abda06882ba332a85bce506
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test45.h
@@ -0,0 +1,30 @@
+/*
+ * um: test function rlc_entity_am_reestablishment
+ *     and also the function clear_entity, case 'while (cur_rx != NULL)'
+ */
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 5,
+    UE_UM, 100000, 100000, 35, 5,
+    RE_ESTABLISH,
+TIME, 2,
+    ENB_SDU, 0, 10,
+    RE_ESTABLISH,
+TIME, 3,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 0, 10,
+    ENB_PDU_SIZE, 14,
+TIME, 5,
+    UE_RECV_FAILS, 1,
+TIME, 6,
+    UE_RECV_FAILS, 0,
+TIME, 10,
+    RE_ESTABLISH,
+TIME, 998,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 0, 10,
+    UE_RECV_FAILS, 1,
+TIME, 999,
+    UE_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test45.txt.gz b/openair2/LAYER2/rlc_v2/tests/test45.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..c5e3e71d46e7f4a547b59fd5403557ac623e7d4f
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test45.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test5.h b/openair2/LAYER2/rlc_v2/tests/test5.h
new file mode 100644
index 0000000000000000000000000000000000000000..3224817c264296f8491a877f309ea62074064615
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test5.h
@@ -0,0 +1,13 @@
+/*
+ * basic um test: UE field length 10 bits
+ * at time 1, eNB receives an SDU of 10 bytes
+ * at time 10, UE receives an SDU of 5 bytes
+ */
+
+TIME, 1,
+    ENB_UM, 100000, 100000, 35, 10,
+    UE_UM, 100000, 100000, 35, 10,
+    ENB_SDU, 0, 10,
+TIME, 10,
+    UE_SDU, 0, 5,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test5.txt.gz b/openair2/LAYER2/rlc_v2/tests/test5.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..5a27d5260641878ac7e26cda1d77b8eeb7442154
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test5.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test6.h b/openair2/LAYER2/rlc_v2/tests/test6.h
new file mode 100644
index 0000000000000000000000000000000000000000..2115c8a328af4f490353e978be4e77961bf93035
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test6.h
@@ -0,0 +1,27 @@
+/*
+ * rlc am test function segment_already_received
+ *   eNB sends SDU [1..900], not received
+ *   eNB retx with smaller PDUs [1..600] [601..900]
+ *   [1..600] is received but ACK/NACK not
+ *   eNB retx with still smaller PDUs [1..400] [401..600] [601..900]
+ *   all is received, ACKs/NACKs go through
+ *
+ * this test will fail if NACK mechanism uses SOstart/SOend
+ * (not implemented for the moment)
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_SDU, 0, 900,
+TIME, 2,
+    ENB_PDU_SIZE, 600,
+    UE_RECV_FAILS, 0,
+TIME, 48,
+    UE_RECV_FAILS, 1,
+    ENB_PDU_SIZE, 400,
+TIME, 90,
+    UE_RECV_FAILS, 0,
+    ENB_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test6.txt.gz b/openair2/LAYER2/rlc_v2/tests/test6.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..54870821a619725938e1ea529ff533d748d9c7db
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test6.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test7.h b/openair2/LAYER2/rlc_v2/tests/test7.h
new file mode 100644
index 0000000000000000000000000000000000000000..081227a400dcfebf40bfc63b85cb244e17de1d81
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test7.h
@@ -0,0 +1,26 @@
+/*
+ * rlc am test function rlc_am_segment_full
+ *   eNB sends SDU [1..900], not received
+ *   eNB retx with smaller PDUs [1..600] [601..900]
+ *   nothing received
+ *   eNB retx with still smaller PDUs [1..400] [401..600] [601..900]
+ *   [401..600] received, ACK goes through
+ *   link clean, all goes through
+ *
+ * this test will fail if NACK mechanism uses SOstart/SOend
+ * (not implemented for the moment)
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_SDU, 0, 900,
+TIME, 2,
+    ENB_PDU_SIZE, 600,
+TIME, 48,
+    ENB_PDU_SIZE, 400,
+TIME, 95,
+    UE_RECV_FAILS, 0,
+    ENB_RECV_FAILS, 0,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test7.txt.gz b/openair2/LAYER2/rlc_v2/tests/test7.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..9976a6050779805882bbefb2dab98fa27fd789bc
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test7.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test8.h b/openair2/LAYER2/rlc_v2/tests/test8.h
new file mode 100644
index 0000000000000000000000000000000000000000..aa7f5bed5be78d0979df6a12f245da7e8e38bdfb
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test8.h
@@ -0,0 +1,19 @@
+/*
+ * basic am test:
+ * at time 1, eNB receives 10 SDUs of 10 bytes
+ */
+
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    ENB_SDU, 0, 10,
+    ENB_SDU, 1, 10,
+    ENB_SDU, 2, 10,
+    ENB_SDU, 3, 10,
+    ENB_SDU, 4, 10,
+    ENB_SDU, 5, 10,
+    ENB_SDU, 6, 10,
+    ENB_SDU, 7, 10,
+    ENB_SDU, 8, 10,
+    ENB_SDU, 9, 10,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test8.txt.gz b/openair2/LAYER2/rlc_v2/tests/test8.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..c8016878635a3f971d3c63298ac49ddefe6835b7
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test8.txt.gz differ
diff --git a/openair2/LAYER2/rlc_v2/tests/test9.h b/openair2/LAYER2/rlc_v2/tests/test9.h
new file mode 100644
index 0000000000000000000000000000000000000000..88e23d94e95891923a49a486445c119f4590b85f
--- /dev/null
+++ b/openair2/LAYER2/rlc_v2/tests/test9.h
@@ -0,0 +1,34 @@
+/*
+ * rlc am test function rlc_am_reassemble_next_segment
+ *        case 'if pdu_byte is not in [so .. so+len-1]'
+ *   eNB sends SDU [1..30], not received
+ *   eNB retx with smaller PDUs [1..21] [22..30], not received
+ *   eNB retx with still smaller PDUs [1..11] [12..21] [22..30], not received
+ *   custom PDU [12..21] sent to UE, received
+ *   custom PDU [1..21] sent to UE, received
+ *
+ * Not sure if in a real setup [12..21] is sent and then [1..21] is sent.
+ * In the current RLC implementation, this is impossible. If we send [12..21]
+ * it means [1..21] has been split and so we won't sent it later on.
+ * Maybe with HARQ retransmissions in PHY/MAC in bad radio conditions?
+ *
+ * this test will fail if NACK mechanism uses SOstart/SOend
+ * (not implemented for the moment)
+ */
+TIME, 1,
+    ENB_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_AM, 100000, 100000, 35, 0, 45, -1, -1, 4,
+    UE_RECV_FAILS, 1,
+    ENB_RECV_FAILS, 1,
+    ENB_SDU, 0, 30,
+TIME, 2,
+    ENB_PDU_SIZE, 25,
+TIME, 48,
+    ENB_PDU_SIZE, 15,
+TIME, 100,
+    UE_RECV_FAILS, 0,
+    ENB_RECV_FAILS, 0,
+    ENB_PDU, 14, 0xd8, 0x00, 0x00, 0x0b, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
+TIME, 101,
+    ENB_PDU, 25, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
+TIME, -1
diff --git a/openair2/LAYER2/rlc_v2/tests/test9.txt.gz b/openair2/LAYER2/rlc_v2/tests/test9.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..cc6d934e708e1ddad7286e7c3eec33cd23c91f94
Binary files /dev/null and b/openair2/LAYER2/rlc_v2/tests/test9.txt.gz differ
diff --git a/openair2/RRC/LTE/rrc_eNB.c b/openair2/RRC/LTE/rrc_eNB.c
index dc9183a87d5a05a9820b4722d0b20bc43372b32d..b9bf68282f408562d864deb2c4373b6da93450bd 100644
--- a/openair2/RRC/LTE/rrc_eNB.c
+++ b/openair2/RRC/LTE/rrc_eNB.c
@@ -1,3 +1,4 @@
+#define RRC_DEFAULT_RAB_IS_AM 1
 /*
  * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -2134,6 +2135,8 @@ rrc_eNB_generate_RRCConnectionRelease(
 {
   uint8_t buffer[RRC_BUF_SIZE];
   uint16_t size = 0;
+  int release_num;
+
   memset(buffer, 0, RRC_BUF_SIZE);
   T(T_ENB_RRC_CONNECTION_RELEASE, T_INT(ctxt_pP->module_id), T_INT(ctxt_pP->frame),
     T_INT(ctxt_pP->subframe), T_INT(ctxt_pP->rnti));
@@ -2161,12 +2164,9 @@ rrc_eNB_generate_RRCConnectionRelease(
     ue_context_pP->ue_context.rnti,
     rrc_eNB_mui,
     size);
+  pthread_mutex_lock(&rrc_release_freelist);
 
-  while (pthread_mutex_trylock(&rrc_release_freelist)) {
-    /* spin... */
-  }
-
-  for (uint16_t release_num = 0; release_num < NUMBER_OF_UE_MAX; release_num++) {
+  for (release_num = 0; release_num < NUMBER_OF_UE_MAX; release_num++) {
     if (rrc_release_info.RRC_release_ctrl[release_num].flag == 0) {
       if (ue_context_pP->ue_context.ue_release_timer_s1 > 0) {
         rrc_release_info.RRC_release_ctrl[release_num].flag = 1;
@@ -2186,6 +2186,12 @@ rrc_eNB_generate_RRCConnectionRelease(
     }
   }
 
+  /* TODO: what to do if RRC_release_ctrl is full? For now, exit. */
+  if (release_num == NUMBER_OF_UE_MAX) {
+    LOG_E(RRC, "fatal: rrc_release_info.RRC_release_ctrl is full\n");
+    exit(1);
+  }
+
   pthread_mutex_unlock(&rrc_release_freelist);
 
   if (NODE_IS_CU(RC.rrc[ctxt_pP->module_id]->node_type)) {
@@ -3512,6 +3518,33 @@ void rrc_eNB_generate_defaultRRCConnectionReconfiguration(const protocol_ctxt_t
                size,
                buffer,
                PDCP_TRANSMISSION_MODE_CONTROL);
+
+  /* Refresh SRBs/DRBs */
+  rrc_pdcp_config_asn1_req(ctxt_pP,
+                          *SRB_configList2, // NULL,
+                          *DRB_configList,
+                          NULL,
+                          0xff, // already configured during the securitymodecommand
+                          NULL,
+                          NULL,
+                          NULL
+#if (LTE_RRC_VERSION >= MAKE_VERSION(9, 0, 0))
+                          , (LTE_PMCH_InfoList_r9_t *) NULL
+#endif
+                          , NULL);
+
+  /* Refresh SRBs/DRBs */
+  rrc_rlc_config_asn1_req(ctxt_pP,
+                          *SRB_configList2, // NULL,
+                          *DRB_configList,
+                          NULL
+#if (LTE_RRC_VERSION >= MAKE_VERSION(9, 0, 0))
+                          , (LTE_PMCH_InfoList_r9_t *) NULL,
+                          0,
+                          0
+#endif
+                          );
+
   free(Sparams);
   Sparams = NULL;
   free(quantityConfig->quantityConfigEUTRA->filterCoefficientRSRP);
@@ -5192,13 +5225,13 @@ rrc_eNB_generate_HO_RRCConnectionReconfiguration(const protocol_ctxt_t *const ct
   DRB_rlc_config = CALLOC(1, sizeof(*DRB_rlc_config));
   DRB_config->rlc_Config = DRB_rlc_config;
 #ifdef RRC_DEFAULT_RAB_IS_AM
-  DRB_rlc_config->present = RLC_Config_PR_am;
-  DRB_rlc_config->choice.am.ul_AM_RLC.t_PollRetransmit = T_PollRetransmit_ms50;
-  DRB_rlc_config->choice.am.ul_AM_RLC.pollPDU = PollPDU_p16;
-  DRB_rlc_config->choice.am.ul_AM_RLC.pollByte = PollByte_kBinfinity;
-  DRB_rlc_config->choice.am.ul_AM_RLC.maxRetxThreshold = UL_AM_RLC__maxRetxThreshold_t8;
-  DRB_rlc_config->choice.am.dl_AM_RLC.t_Reordering = T_Reordering_ms35;
-  DRB_rlc_config->choice.am.dl_AM_RLC.t_StatusProhibit = T_StatusProhibit_ms25;
+  DRB_rlc_config->present = LTE_RLC_Config_PR_am;
+  DRB_rlc_config->choice.am.ul_AM_RLC.t_PollRetransmit = LTE_T_PollRetransmit_ms50;
+  DRB_rlc_config->choice.am.ul_AM_RLC.pollPDU = LTE_PollPDU_p16;
+  DRB_rlc_config->choice.am.ul_AM_RLC.pollByte = LTE_PollByte_kBinfinity;
+  DRB_rlc_config->choice.am.ul_AM_RLC.maxRetxThreshold = LTE_UL_AM_RLC__maxRetxThreshold_t8;
+  DRB_rlc_config->choice.am.dl_AM_RLC.t_Reordering = LTE_T_Reordering_ms35;
+  DRB_rlc_config->choice.am.dl_AM_RLC.t_StatusProhibit = LTE_T_StatusProhibit_ms25;
 #else
   DRB_rlc_config->present = LTE_RLC_Config_PR_um_Bi_Directional;
   DRB_rlc_config->choice.um_Bi_Directional.ul_UM_RLC.sn_FieldLength = LTE_SN_FieldLength_size10;
@@ -6029,6 +6062,33 @@ rrc_eNB_generate_HO_RRCConnectionReconfiguration(const protocol_ctxt_t *const ct
     ue_context_pP->ue_context.rnti,
     rrc_eNB_mui,
     size);
+
+  /* Refresh SRBs/DRBs */
+  rrc_pdcp_config_asn1_req(ctxt_pP,
+                          *SRB_configList2, // NULL,
+                          *DRB_configList,
+                          NULL,
+                          0xff, // already configured during the securitymodecommand
+                          NULL,
+                          NULL,
+                          NULL
+#if (LTE_RRC_VERSION >= MAKE_VERSION(9, 0, 0))
+                          , (LTE_PMCH_InfoList_r9_t *) NULL
+#endif
+                          , NULL);
+
+  /* Refresh SRBs/DRBs */
+  rrc_rlc_config_asn1_req(ctxt_pP,
+                          *SRB_configList2, // NULL,
+                          *DRB_configList,
+                          NULL
+#if (LTE_RRC_VERSION >= MAKE_VERSION(9, 0, 0))
+                          , (LTE_PMCH_InfoList_r9_t *) NULL,
+                          0,
+                          0
+#endif
+                          );
+
   free(quantityConfig->quantityConfigEUTRA->filterCoefficientRSRQ);
   quantityConfig->quantityConfigEUTRA->filterCoefficientRSRQ = NULL;
   free(quantityConfig->quantityConfigEUTRA->filterCoefficientRSRP);
@@ -8166,6 +8226,143 @@ void rrc_enb_init(void) {
   memset(&rrc_release_info,0,sizeof(RRC_release_list_t));
 }
 
+//-----------------------------------------------------------------------------
+void process_successful_rlc_sdu_indication(int instance,
+                                           int rnti,
+                                           int message_id)
+{
+  int release_num;
+  int release_total;
+  RRC_release_ctrl_t *release_ctrl;
+
+  /* Check if the message sent was RRC Connection Release.
+   * If yes then continue the release process.
+   */
+
+  pthread_mutex_lock(&rrc_release_freelist);
+
+  if (rrc_release_info.num_UEs > 0) {
+    release_total = 0;
+
+    for (release_num = 0, release_ctrl = &rrc_release_info.RRC_release_ctrl[0];
+         release_num < NUMBER_OF_UE_MAX;
+         release_num++, release_ctrl++) {
+      if(release_ctrl->flag > 0) {
+        release_total++;
+      } else {
+        continue;
+      }
+
+      if (release_ctrl->flag == 1 && release_ctrl->rnti == rnti && release_ctrl->rrc_eNB_mui == message_id) {
+        release_ctrl->flag = 3;
+        LOG_D(MAC,"DLSCH Release send:index %d rnti %x mui %d flag 1->3\n",
+              release_num,
+              rnti,
+              message_id);
+        break;
+      }
+
+      if (release_ctrl->flag == 2 && release_ctrl->rnti == rnti && release_ctrl->rrc_eNB_mui == message_id) {
+        release_ctrl->flag = 4;
+        LOG_D(MAC, "DLSCH Release send:index %d rnti %x mui %d flag 2->4\n",
+              release_num,
+              rnti,
+              message_id);
+        break;
+      }
+
+      if(release_total >= rrc_release_info.num_UEs)
+        break;
+    }
+  }
+
+  pthread_mutex_unlock(&rrc_release_freelist);
+}
+
+//-----------------------------------------------------------------------------
+void process_unsuccessful_rlc_sdu_indication(int instance, int rnti)
+{
+  int release_num;
+  int release_total;
+  RRC_release_ctrl_t *release_ctrl;
+
+  /* radio link failure detected by RLC layer, remove UE properly */
+
+  pthread_mutex_lock(&rrc_release_freelist);
+
+  /* first, check if the rnti is in the list rrc_release_info.RRC_release_ctrl */
+
+  if (rrc_release_info.num_UEs > 0) {
+    release_total = 0;
+
+    for (release_num = 0, release_ctrl = &rrc_release_info.RRC_release_ctrl[0];
+         release_num < NUMBER_OF_UE_MAX;
+         release_num++, release_ctrl++) {
+      if(release_ctrl->flag > 0) {
+        release_total++;
+      } else {
+        continue;
+      }
+
+      if (release_ctrl->flag == 1 && release_ctrl->rnti == rnti) {
+        release_ctrl->flag = 3;
+        LOG_D(MAC,"DLSCH Release send:index %d rnti %x flag 1->3\n",
+              release_num,
+              rnti);
+        goto done;
+      }
+
+      if (release_ctrl->flag == 2 && release_ctrl->rnti == rnti) {
+        release_ctrl->flag = 4;
+        LOG_D(MAC, "DLSCH Release send:index %d rnti %x flag 2->4\n",
+              release_num,
+              rnti);
+        goto done;
+      }
+
+      if(release_total >= rrc_release_info.num_UEs)
+        break;
+    }
+  }
+
+  /* it's not in the list, put it with flag = 4 */
+  for (release_num = 0; release_num < NUMBER_OF_UE_MAX; release_num++) {
+    if (rrc_release_info.RRC_release_ctrl[release_num].flag == 0) {
+      rrc_release_info.RRC_release_ctrl[release_num].flag = 4;
+      rrc_release_info.RRC_release_ctrl[release_num].rnti = rnti;
+      rrc_release_info.RRC_release_ctrl[release_num].rrc_eNB_mui = -1;     /* not defined */
+      rrc_release_info.num_UEs++;
+      LOG_D(RRC, "radio link failure detected: index %d rnti %x flag %d \n",
+            release_num,
+            rnti,
+            rrc_release_info.RRC_release_ctrl[release_num].flag);
+      break;
+    }
+  }
+
+  /* TODO: what to do if rrc_release_info.RRC_release_ctrl is full? */
+  if (release_num == NUMBER_OF_UE_MAX) {
+    LOG_E(RRC, "fatal: radio link failure: rrc_release_info.RRC_release_ctrl is full\n");
+    exit(1);
+  }
+
+done:
+  pthread_mutex_unlock(&rrc_release_freelist);
+}
+
+//-----------------------------------------------------------------------------
+void process_rlc_sdu_indication(int instance,
+                                int rnti,
+                                int is_successful,
+                                int srb_id,
+                                int message_id)
+{
+  if (is_successful)
+    process_successful_rlc_sdu_indication(instance, rnti, message_id);
+  else
+    process_unsuccessful_rlc_sdu_indication(instance, rnti);
+}
+
 //-----------------------------------------------------------------------------
 int add_ue_to_remove(struct rrc_eNB_ue_context_s **ue_to_be_removed,
                      int removed_ue_count,
@@ -8868,6 +9065,13 @@ void *rrc_enb_process_itti_msg(void *notUsed) {
        rrc_eNB_process_M2AP_MCE_CONFIGURATION_UPDATE(&ctxt,&M2AP_MCE_CONFIGURATION_UPDATE(msg_p));
        break;
 
+    case RLC_SDU_INDICATION:
+      process_rlc_sdu_indication(instance,
+                                 RLC_SDU_INDICATION(msg_p).rnti,
+                                 RLC_SDU_INDICATION(msg_p).is_successful,
+                                 RLC_SDU_INDICATION(msg_p).srb_id,
+                                 RLC_SDU_INDICATION(msg_p).message_id);
+      break;
 
     default:
       LOG_E(RRC, "[eNB %d] Received unexpected message %s\n", instance, msg_name_p);