/*******************************************************************************
Eurecom OpenAirInterface 2
Copyright(c) 1999 - 2014 Eurecom

This program is free software; you can redistribute it and/or modify it
under the terms and conditions of the GNU General Public License,
version 2, as published by the Free Software Foundation.

This program is distributed in the hope it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.

The full GNU General Public License is included in this distribution in
the file called "COPYING".

Contact Information
Openair Admin: openair_admin@eurecom.fr
Openair Tech : openair_tech@eurecom.fr
Forums       : http://forums.eurecom.fsr/openairinterface
Address      : EURECOM,
               Campus SophiaTech,
               450 Route des Chappes,
               CS 50193
               06904 Biot Sophia Antipolis cedex,
               FRANCE
*******************************************************************************/
#define RLC_AM_MODULE
#define RLC_AM_REASSEMBLY_C
#include "platform_types.h"
//-----------------------------------------------------------------------------
#include "assertions.h"
#include "rlc.h"
#include "rlc_am.h"
#include "list.h"
#include "LAYER2/MAC/extern.h"
#include "UTIL/LOG/log.h"

//#define TRACE_RLC_AM_RX_DECODE
//-----------------------------------------------------------------------------
inline void rlc_am_clear_rx_sdu (rlc_am_entity_t *rlc_pP)
//-----------------------------------------------------------------------------
{
  rlc_pP->output_sdu_size_to_write = 0;
}
//-----------------------------------------------------------------------------
void rlc_am_reassembly (uint8_t * src_pP, int32_t lengthP, rlc_am_entity_t *rlc_pP, frame_t frameP)
//-----------------------------------------------------------------------------
{
  LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PAYLOAD] reassembly()  %d bytes\n",
      frameP,
      (rlc_pP->is_enb) ? "eNB" : "UE",
      rlc_pP->enb_module_id,
      rlc_pP->ue_module_id,
      rlc_pP->rb_id,
      lengthP);

  if (rlc_pP->output_sdu_in_construction == NULL) {
      rlc_pP->output_sdu_in_construction = get_free_mem_block (RLC_SDU_MAX_SIZE);
      rlc_pP->output_sdu_size_to_write = 0;
      assert(rlc_pP->output_sdu_in_construction != NULL);
  }
  if (rlc_pP->output_sdu_in_construction != NULL) {

      // check if no overflow in size
      if ((rlc_pP->output_sdu_size_to_write + lengthP) <= RLC_SDU_MAX_SIZE) {
          memcpy (&rlc_pP->output_sdu_in_construction->data[rlc_pP->output_sdu_size_to_write], src_pP, lengthP);

          rlc_pP->output_sdu_size_to_write += lengthP;
      } else {
          LOG_E(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PAYLOAD] ERROR  SDU SIZE OVERFLOW SDU GARBAGED\n",
              frameP,
              (rlc_pP->is_enb) ? "eNB" : "UE",
                  rlc_pP->enb_module_id,
                  rlc_pP->ue_module_id,
                  rlc_pP->rb_id);
#if defined(STOP_ON_IP_TRAFFIC_OVERLOAD)
      AssertFatal(0, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u] RLC_AM_DATA_IND, SDU SIZE OVERFLOW SDU GARBAGED\n",
          frameP,
          (rlc_pP->is_enb) ? "eNB" : "UE",
          rlc_pP->enb_module_id,
          rlc_pP->ue_module_id,
          rlc_pP->rb_id);
#endif
          // erase  SDU
          rlc_pP->output_sdu_size_to_write = 0;
      }
  } else {
      LOG_E(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PAYLOAD] ERROR  OUTPUT SDU IS NULL\n",
          frameP,
          (rlc_pP->is_enb) ? "eNB" : "UE",
           rlc_pP->enb_module_id,
           rlc_pP->ue_module_id,
           rlc_pP->rb_id);
#if defined(STOP_ON_IP_TRAFFIC_OVERLOAD)
      AssertFatal(0, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u] RLC_AM_DATA_IND, SDU DROPPED, OUT OF MEMORY\n",
          frameP,
          (rlc_pP->is_enb) ? "eNB" : "UE",
          rlc_pP->enb_module_id,
          rlc_pP->ue_module_id,
          rlc_pP->rb_id);
#endif
  }
}
//-----------------------------------------------------------------------------
void rlc_am_send_sdu (rlc_am_entity_t *rlc_pP,frame_t frameP, eNB_flag_t eNB_flagP)
//-----------------------------------------------------------------------------
{
  if ((rlc_pP->output_sdu_in_construction)) {
      LOG_D(RLC, "\n\n\n[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][SEND_SDU] %d bytes sdu %p\n",
          frameP,
          (rlc_pP->is_enb) ? "eNB" : "UE",
              rlc_pP->enb_module_id,
              rlc_pP->ue_module_id,
              rlc_pP->rb_id,
              rlc_pP->output_sdu_size_to_write,
              rlc_pP->output_sdu_in_construction);
      if (rlc_pP->output_sdu_size_to_write > 0) {
          rlc_pP->stat_rx_pdcp_sdu   += 1;
          rlc_pP->stat_rx_pdcp_bytes += rlc_pP->output_sdu_size_to_write;
#ifdef TEST_RLC_AM
          rlc_am_v9_3_0_test_data_ind (rlc_pP->module_id,
              rlc_pP->rb_id,
              rlc_pP->output_sdu_size_to_write,
              rlc_pP->output_sdu_in_construction);
#else
          rlc_data_ind (
              rlc_pP->enb_module_id,
              rlc_pP->ue_module_id,
              frameP,
              eNB_flagP,
              BOOL_NOT(rlc_pP->is_data_plane),
              MBMS_FLAG_NO,
              rlc_pP->rb_id,
              rlc_pP->output_sdu_size_to_write,
              rlc_pP->output_sdu_in_construction);
#endif
          rlc_pP->output_sdu_in_construction = NULL;
      } else {
          LOG_E(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][SEND_SDU] ERROR SIZE <= 0 ... DO NOTHING, SET SDU SIZE TO 0\n",
              frameP,
              (rlc_pP->is_enb) ? "eNB" : "UE",
                  rlc_pP->enb_module_id,
                  rlc_pP->ue_module_id,
                  rlc_pP->rb_id);
          //msg("[RLC_AM][MOD %d] Freeing mem_block ...\n", rlc_pP->module_id);
          //free_mem_block (rlc_pP->output_sdu_in_construction);
          assert(3==4);
      }
      rlc_pP->output_sdu_size_to_write = 0;
  }
}
//-----------------------------------------------------------------------------
void rlc_am_reassemble_pdu(rlc_am_entity_t* rlc_pP, frame_t frameP, eNB_flag_t eNB_flagP, mem_block_t* tb_pP) {
  //-----------------------------------------------------------------------------
  int i,j;

  rlc_am_pdu_info_t* pdu_info        = &((rlc_am_rx_pdu_management_t*)(tb_pP->data))->pdu_info;
  LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU SN=%03d\n",
      frameP,
      (rlc_pP->is_enb) ? "eNB" : "UE",
          rlc_pP->enb_module_id,
          rlc_pP->ue_module_id,
          rlc_pP->rb_id,
          pdu_info->sn);
#ifdef TRACE_RLC_AM_RX_DECODE
  rlc_am_display_data_pdu_infos(rlc_pP, frameP, pdu_info);
#endif

  if (pdu_info->e == RLC_E_FIXED_PART_DATA_FIELD_FOLLOW) {
      switch (pdu_info->fi) {
      case RLC_FI_1ST_BYTE_DATA_IS_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU NO E_LI FI=11 (00)\n",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        // one complete SDU
        rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP); // may be not necessary
        rlc_am_reassembly (pdu_info->payload, pdu_info->payload_size, rlc_pP,frameP);
        rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP); // may be not necessary
        //rlc_pP->reassembly_missing_sn_detected = 0;
        break;
      case RLC_FI_1ST_BYTE_DATA_IS_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_NOT_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU NO E_LI FI=10 (01)\n",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        // one beginning segment of SDU in PDU
        rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP); // may be not necessary
        rlc_am_reassembly (pdu_info->payload, pdu_info->payload_size, rlc_pP,frameP);
        //rlc_pP->reassembly_missing_sn_detected = 0;
        break;
      case RLC_FI_1ST_BYTE_DATA_IS_NOT_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU NO E_LI FI=01 (10)\n",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        // one last segment of SDU
        //if (rlc_pP->reassembly_missing_sn_detected == 0) {
        rlc_am_reassembly (pdu_info->payload, pdu_info->payload_size, rlc_pP,frameP);
        rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
        //} // else { clear sdu already done
        //rlc_pP->reassembly_missing_sn_detected = 0;
        break;
      case RLC_FI_1ST_BYTE_DATA_IS_NOT_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_NOT_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU NO E_LI FI=00 (11)\n",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        //if (rlc_pP->reassembly_missing_sn_detected == 0) {
        // one whole segment of SDU in PDU
        rlc_am_reassembly (pdu_info->payload, pdu_info->payload_size, rlc_pP,frameP);
        //} else {
        //    rlc_pP->reassembly_missing_sn_detected = 1; // not necessary but for readability of the code
        //}

        break;
#ifdef USER_MODE
      default:
        assert(0 != 0);
#endif
      }
  } else {
      switch (pdu_info->fi) {
      case RLC_FI_1ST_BYTE_DATA_IS_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU FI=11 (00) Li=",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        for (i=0; i < pdu_info->num_li; i++) {
            LOG_D(RLC, "%d ",pdu_info->li_list[i]);
        }
        LOG_D(RLC, "\n");
        //msg(" remaining size %d\n",size);
        // N complete SDUs
        rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
        j = 0;
        for (i = 0; i < pdu_info->num_li; i++) {
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->li_list[i], rlc_pP,frameP);
            rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
            j = j + pdu_info->li_list[i];
        }
        if (pdu_info->hidden_size > 0) { // normally should always be > 0 but just for help debug
            // data is already ok, done by last loop above
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->hidden_size, rlc_pP,frameP);
            rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
        }
        //rlc_pP->reassembly_missing_sn_detected = 0;
        break;
      case RLC_FI_1ST_BYTE_DATA_IS_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_NOT_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU FI=10 (01) Li=",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        for (i=0; i < pdu_info->num_li; i++) {
            LOG_D(RLC, "%d ",pdu_info->li_list[i]);
        }
        LOG_D(RLC, "\n");
        //msg(" remaining size %d\n",size);
        // N complete SDUs + one segment of SDU in PDU
        rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
        j = 0;
        for (i = 0; i < pdu_info->num_li; i++) {
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->li_list[i], rlc_pP,frameP);
            rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
            j = j + pdu_info->li_list[i];
        }
        if (pdu_info->hidden_size > 0) { // normally should always be > 0 but just for help debug
            // data is already ok, done by last loop above
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->hidden_size, rlc_pP, frameP);
        }
        //rlc_pP->reassembly_missing_sn_detected = 0;
        break;
      case RLC_FI_1ST_BYTE_DATA_IS_NOT_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU FI=01 (10) Li=",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        for (i=0; i < pdu_info->num_li; i++) {
            LOG_D(RLC, "%d ",pdu_info->li_list[i]);
        }
        LOG_D(RLC, "\n");
        //msg(" remaining size %d\n",size);
        // one last segment of SDU + N complete SDUs in PDU
        j = 0;
        for (i = 0; i < pdu_info->num_li; i++) {
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->li_list[i], rlc_pP,frameP);
            rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
            j = j + pdu_info->li_list[i];
        }
        if (pdu_info->hidden_size > 0) { // normally should always be > 0 but just for help debug
            // data is already ok, done by last loop above
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->hidden_size, rlc_pP,frameP);
            rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
        }
        //rlc_pP->reassembly_missing_sn_detected = 0;
        break;
      case RLC_FI_1ST_BYTE_DATA_IS_NOT_1ST_BYTE_SDU_LAST_BYTE_DATA_IS_NOT_LAST_BYTE_SDU:
        LOG_D(RLC, "[FRAME %5u][%s][RLC_AM][MOD %u/%u][RB %u][REASSEMBLY PDU] TRY REASSEMBLY PDU FI=00 (11) Li=",
            frameP,
            (rlc_pP->is_enb) ? "eNB" : "UE",
                rlc_pP->enb_module_id,
                rlc_pP->ue_module_id,
                rlc_pP->rb_id);
        for (i=0; i < pdu_info->num_li; i++) {
            LOG_D(RLC, "%d ",pdu_info->li_list[i]);
        }
        LOG_D(RLC, "\n");
        //msg(" remaining size %d\n",size);
        j = 0;
        for (i = 0; i < pdu_info->num_li; i++) {
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->li_list[i], rlc_pP,frameP);
            rlc_am_send_sdu(rlc_pP,frameP,eNB_flagP);
            j = j + pdu_info->li_list[i];
        }
        if (pdu_info->hidden_size > 0) { // normally should always be > 0 but just for help debug
            // data is already ok, done by last loop above
            rlc_am_reassembly (&pdu_info->payload[j], pdu_info->hidden_size, rlc_pP,frameP);
        } else {
#ifdef USER_MODE
            //assert (5!=5);
#endif
        }
        //rlc_pP->reassembly_missing_sn_detected = 0;
        break;
#ifdef USER_MODE
      default:
        assert(1 != 1);
#endif
      }
  }
  free_mem_block(tb_pP);
}