/*
 * SPDX-License-Identifier: LicenseRef-CSSL-1.0
 */

/*!
 * \brief pdcp interface with linux IP interface, have a look at http://man7.org/linux/man-pages/man7/netlink.7.html for netlink
 */


extern int otg_enabled;

#include "pdcp.h"
#include "pdcp_primitives.h"

#include <pthread.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/ip.h>

#include "../MAC/mac_extern.h"
#include "RRC/L2_INTERFACE/openair_rrc_L2_interface.h"
//#include "SIMULATION/ETH_TRANSPORT/extern.h"
#include "common/utils/LOG/log.h"
#include "UTIL/OTG/otg_tx.h"
#include "nfapi/oai_integration/vendor_ext.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "common/platform_constants.h"
#include "pdcp.h"

#include "assertions.h"


#include <sys/socket.h>
#include <linux/netlink.h>

extern int nas_sock_fd[MAX_MOBILES_PER_ENB];

extern int nas_sock_mbms_fd;

static char nl_rx_buf[NL_MAX_PAYLOAD];

#  include "openair3/ocp-gtpu/gtp_itf.h"

extern int gtpv1u_new_data_req( uint8_t  enb_module_idP, rnti_t   ue_rntiP, uint8_t  rab_idP, uint8_t *buffer_pP, uint32_t buf_lenP, uint32_t buf_offsetP);
uint16_t ue_id_g; // Global variable to identify the ID for each UE. It is updated in main() of lte-uesoftmodem.c

void debug_pdcp_pc5s_sdu(sidelink_pc5s_element *sl_pc5s_msg, char *title) {
  LOG_I(PDCP,"%s: \nPC5S message, header traffic_type: %d)\n", title, sl_pc5s_msg->pc5s_header.traffic_type);
  LOG_I(PDCP,"PC5S message, header rb_id: %ld)\n", sl_pc5s_msg->pc5s_header.rb_id);
  LOG_I(PDCP,"PC5S message, header data_size: %d)\n", sl_pc5s_msg->pc5s_header.data_size);
  LOG_I(PDCP,"PC5S message, header inst: %d)\n", sl_pc5s_msg->pc5s_header.inst);
  LOG_I(PDCP,"PC5-S message, sourceL2Id: 0x%08x\n)\n", sl_pc5s_msg->pc5s_header.sourceL2Id);
  LOG_I(PDCP,"PC5-S message, destinationL1Id: 0x%08x\n)\n", sl_pc5s_msg->pc5s_header.destinationL2Id);
}
//-----------------------------------------------------------------------------
int pdcp_fifo_flush_sdus(const protocol_ctxt_t *const  ctxt_pP) {
  notifiedFIFO_elt_t  *sdu_p;
  int              pdcp_nb_sdu_sent = 0;
  int              ret=0;
  while ((sdu_p = pollNotifiedFIFO(&pdcp_sdu_list)) != NULL ) {
    pdcp_data_ind_header_t * pdcpHead=(pdcp_data_ind_header_t *)NotifiedFifoData(sdu_p);

    /* Note: we ignore the instance ID in the context and use the one in the
     * PDCP packet to pick the right socket below */

    int rb_id = pdcpHead->rb_id;
    void * pdcpData=(void*)(pdcpHead+1);
    if (rb_id == 10) { //hardcoded for PC5-Signaling
      if( LOG_DEBUGFLAG(DEBUG_PDCP) ) {
        debug_pdcp_pc5s_sdu((sidelink_pc5s_element *)pdcpData,
                            "pdcp_fifo_flush_sdus sends a aPC5S message");
      }

      ret = sendto(pdcp_pc5_sockfd, pdcpData,
                   sizeof(sidelink_pc5s_element), 0, (struct sockaddr *)&prose_pdcp_addr,sizeof(prose_pdcp_addr) );

    } else if (UE_NAS_USE_TUN) {
      //ret = write(nas_sock_fd[pdcpHead->inst], &(sdu_p->data[sizeof(pdcp_data_ind_header_t)]),sizeToWrite );
       if(rb_id == mbms_rab_id){
       ret = write(nas_sock_mbms_fd, pdcpData, pdcpHead->data_size );
       LOG_I(PDCP,"[PDCP_FIFOS] ret %d TRIED TO PUSH MBMS DATA TO rb_id %d handle %d sizeToWrite %d\n",
	     ret,rb_id,nas_sock_fd[pdcpHead->inst],pdcpHead->data_size);
        }
       else
       {
	 if( LOG_DEBUGFLAG(DEBUG_PDCP) ) 
	   log_dump(PDCP, pdcpData, pdcpHead->data_size, LOG_DUMP_CHAR,"PDCP output to be sent to TUN interface: \n");
	 ret = write(nas_sock_fd[pdcpHead->inst], pdcpData,pdcpHead->data_size );
	 LOG_T(PDCP,"[UE PDCP_FIFOS] ret %d TRIED TO PUSH DATA TO rb_id %d handle %d sizeToWrite %d\n",
	       ret,rb_id,nas_sock_fd[pdcpHead->inst],pdcpHead->data_size);
       }
    } else if (ENB_NAS_USE_TUN) {
      if( LOG_DEBUGFLAG(DEBUG_PDCP) ) 
	log_dump(PDCP, pdcpData, pdcpHead->data_size, LOG_DUMP_CHAR,"PDCP output to be sent to TUN interface: \n");
      ret = write(nas_sock_fd[0], pdcpData, pdcpHead->data_size);
       LOG_T(PDCP,"[NB PDCP_FIFOS] ret %d TRIED TO PUSH DATA TO rb_id %d handle %d sizeToWrite %d\n",ret,rb_id,nas_sock_fd[0],pdcpHead->data_size);
    }
    
    AssertFatal(ret >= 0,"[PDCP_FIFOS] pdcp_fifo_flush_sdus (errno: %d %s), nas_sock_fd[0]: %d\n", errno, strerror(errno), nas_sock_fd[0]);

    if( LOG_DEBUGFLAG(DEBUG_PDCP) )
      log_dump(PDCP, pdcpData, min(pdcpHead->data_size,30) , LOG_DUMP_CHAR,
	       "Printing first bytes of PDCP SDU before removing it from the list: \n");
    delNotifiedFIFO_elt (sdu_p);
    pdcp_nb_sdu_sent ++;
  }

  return pdcp_nb_sdu_sent;
}

int pdcp_fifo_flush_mbms_sdus(const protocol_ctxt_t *const  ctxt_pP) {
  notifiedFIFO_elt_t     *sdu_p;
  int              pdcp_nb_sdu_sent = 0;
  //int              ret=0;

  while ((sdu_p = pollNotifiedFIFO(&pdcp_sdu_list)) != NULL ) {
    pdcp_data_ind_header_t * pdcpHead=(pdcp_data_ind_header_t *)NotifiedFifoData(sdu_p);
    AssertFatal(pdcpHead->inst==ctxt_pP->module_id, "To implement correctly multi module id\n");
    //int rb_id = ((pdcp_data_ind_header_t *)(sdu_p->data))->rb_id;
    //int sizeToWrite= sizeof (pdcp_data_ind_header_t) +
                     //((pdcp_data_ind_header_t *) sdu_p->data)->data_size;

    //if (rb_id == 10) { //hardcoded for PC5-Signaling
    //  if( LOG_DEBUGFLAG(DEBUG_PDCP) ) {
    //    debug_pdcp_pc5s_sdu((sidelink_pc5s_element *)&(sdu_p->data[sizeof(pdcp_data_ind_header_t)]),
    //                        "pdcp_fifo_flush_sdus sends a aPC5S message");
    //  }

    //  ret = sendto(pdcp_pc5_sockfd, &(sdu_p->data[sizeof(pdcp_data_ind_header_t)]),
    //               sizeof(sidelink_pc5s_element), 0, (struct sockaddr *)&prose_pdcp_addr,sizeof(prose_pdcp_addr) );
    //} else if (UE_NAS_USE_TUN) {
    //  ret = write(nas_sock_fd[ctxt_pP->module_id], &(sdu_p->data[sizeof(pdcp_data_ind_header_t)]),sizeToWrite );
    //  LOG_I(PDCP,"[PDCP_FIFOS] ret %d TRIED TO PUSH DATA TO rb_id %d handle %d sizeToWrite %d\n",ret,rb_id,nas_sock_fd[ctxt_pP->module_id],sizeToWrite);

    //} else if (ENB_NAS_USE_TUN) {
    //  ret = write(nas_sock_fd[0], &(sdu_p->data[sizeof(pdcp_data_ind_header_t)]),sizeToWrite );
    //} else if (PDCP_USE_NETLINK) {
    //  memcpy(NLMSG_DATA(nas_nlh_tx), (uint8_t *) sdu_p->data,  sizeToWrite);
    //  nas_nlh_tx->nlmsg_len = sizeToWrite;
    //  ret = sendmsg(nas_sock_fd[0],&nas_msg_tx,0);
    //}  //  PDCP_USE_NETLINK

    //AssertFatal(ret >= 0,"[PDCP_FIFOS] pdcp_fifo_flush_sdus (errno: %d %s)\n", errno, strerror(errno));

    //AssertFatal(ret >= 0,"[PDCP_FIFOS] pdcp_fifo_flush_sdus (errno: %d %s)\n", errno, strerror(errno));
    delNotifiedFIFO_elt (sdu_p);
    pdcp_nb_sdu_sent ++;
  }

  return pdcp_nb_sdu_sent;
}


int pdcp_fifo_read_input_sdus_fromtun (const protocol_ctxt_t *const  ctxt_pP) {
  protocol_ctxt_t ctxt = *ctxt_pP;
  hash_key_t key = HASHTABLE_NOT_A_KEY_VALUE;
  hashtable_rc_t h_rc;
  pdcp_t *pdcp_p = NULL;
  int len;
  rb_id_t rab_id = DEFAULT_RAB_ID;
  int sockd;

  if (UE_NAS_USE_TUN) {
    if (ue_id_g == 0) {
      sockd = nas_sock_fd[ctxt_pP->module_id];
    }
    else {
      sockd = nas_sock_fd[ue_id_g];
    }
  }
  else {
    sockd = nas_sock_fd[0];
  }

  for (;;) {
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ, 1 );
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ_BUFFER, 1 );
    len = read(sockd, &nl_rx_buf, NL_MAX_PAYLOAD);

    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ_BUFFER, 0 );
    if (len == -1) {
      if (errno == EAGAIN) {
        LOG_D(PDCP, "Error reading NAS socket: %s\n", strerror(errno));
      }
      else {
        LOG_E(PDCP, "Error reading NAS socket: %s\n", strerror(errno));
      }
      break;
    }
    /* Check for message truncation. Strictly speaking if the packet is exactly sizeof(nl_rx_buf) bytes
       that would not be an error. But we cannot distinguish that from a packet > sizeof(nl_rx_buf) */
    if (len == sizeof(nl_rx_buf))
    {
      LOG_E(PDCP, "%s(%d). Message truncated %d\n", __FUNCTION__, __LINE__, len);
      break;
    }
    if (len == 0) {
      LOG_E(PDCP, "EOF Reading NAS socket\n");
      break;
    }

    if (UE_NAS_USE_TUN) {
      key = PDCP_COLL_KEY_DEFAULT_DRB_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);
      h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
    } else { // => ENB_NAS_USE_TUN
      /* Get the IP from a packet */
      struct ip *ip_pack = (struct ip *) nl_rx_buf;
      /* Use last octet of destination IP to get index of UE */
      int ue_indx = ((ip_pack->ip_dst.s_addr >> 24) -  2) % MAX_MOBILES_PER_ENB;
      ctxt.rntiMaybeUEid = pdcp_eNB_UE_instance_to_rnti[ue_indx];
      ctxt.enb_flag=ENB_FLAG_YES;
      ctxt.module_id=0;
      key = PDCP_COLL_KEY_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id, SRB_FLAG_NO);
      h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
    }

    LOG_D(PDCP, "PDCP_COLL_KEY_DEFAULT_DRB_VALUE(module_id=%d, rnti=%lx, enb_flag=%d)\n", ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);

    if (h_rc == HASH_TABLE_OK) {
      LOG_D(PDCP, "[FRAME %5u][UE][NETLINK][IP->PDCP] INST %ld: Received socket with length %d on Rab %ld \n",
            ctxt.frame, ctxt.instance, len, rab_id);
      LOG_D(PDCP,
            "[FRAME %5u][UE][IP][INSTANCE %ld][RB %ld][--- PDCP_DATA_REQ / %d Bytes --->][PDCP][MOD %u][UE %04lx][RB %ld]\n",
            ctxt.frame,
            ctxt.instance,
            rab_id,
            len,
            ctxt.module_id,
            ctxt.rntiMaybeUEid,
            rab_id);

#if defined  ENABLE_PDCP_PAYLOAD_DEBUG
      LOG_I(PHY, "TUN interface output received from PDCP: \n");
      for (int i = 0; i < 128; i++) {
    	  printf("%02x ",(unsigned char)nl_rx_buf[i]);
      }
      printf("\n");
#endif

      pdcp_data_req(&ctxt, SRB_FLAG_NO, rab_id, RLC_MUI_UNDEFINED,
                    RLC_SDU_CONFIRM_NO, len, (unsigned char *)nl_rx_buf,
                    PDCP_TRANSMISSION_MODE_DATA
                    , NULL, NULL
                   );
    } else {
      LOG_D(PDCP,
            "[FRAME %5u][UE][IP][INSTANCE %ld][RB %ld][--- PDCP_DATA_REQ / %d Bytes ---X][PDCP][MOD %u][UE %04lx][RB %ld] TUN NON INSTANCIATED INSTANCE key 0x%" PRIx64 ", DROPPED\n",
            ctxt.frame,
            ctxt.instance,
            rab_id,
            len,
            ctxt.module_id,
            ctxt.rntiMaybeUEid,
            rab_id,
            key);
    }
  }

  VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ, 0 );
  return len;
}

int pdcp_fifo_read_input_mbms_sdus_fromtun (const protocol_ctxt_t *const  ctxt_pP) {
  protocol_ctxt_t ctxt = *ctxt_pP;
  hash_key_t key = HASHTABLE_NOT_A_KEY_VALUE;
  hashtable_rc_t h_rc = HASH_TABLE_OK;
  //pdcp_t *pdcp_p = NULL;
  int len;
  rb_id_t rab_id = mbms_rab_id;//DEFAULT_RAB_ID;
  if(mbms_rab_id > 9 || mbms_rab_id < 4)
       h_rc = 2;

  if(UE_NAS_USE_TUN)
       return 0;

  do {
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_MBMS_FIFO_READ, 1 );
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_MBMS_FIFO_READ_BUFFER, 1 );
    len = read(nas_sock_mbms_fd, &nl_rx_buf, NL_MAX_PAYLOAD);
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_MBMS_FIFO_READ_BUFFER, 0 );

    if (len<=0) continue;

    if (UE_NAS_USE_TUN) {
      // key = PDCP_COLL_KEY_DEFAULT_DRB_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);
      // h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
    } else { // => ENB_NAS_USE_TUN
    //ctxt.rnti=pdcp_eNB_UE_instance_to_rnti[0];
     // ctxt.enb_flag=ENB_FLAG_YES;
     // ctxt.module_id=0;
     // key = PDCP_COLL_KEY_VALUE(ctxt.module_id, /*ctxt.rnti*/0, ctxt.enb_flag, /*mbms_rab_id*/8, SRB_FLAG_YES);
     // h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
     // LOG_W(PDCP,"h_rc %d %d\n",h_rc,rab_id);
    }

    LOG_D(PDCP, "PDCP_COLL_KEY_DEFAULT_DRB_VALUE(module_id=%d, rnti=%lx, enb_flag=%d)\n", ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);

    if (h_rc == HASH_TABLE_OK) {
      LOG_D(PDCP, "[FRAME %5u][UE][NETLINK][IP->PDCP] INST %ld: Received socket with length %d on Rab %ld \n",
            ctxt.frame, ctxt.instance, len, rab_id);
      LOG_D(PDCP,
            "[FRAME %5u][UE][IP][INSTANCE %ld][RB %ld][--- PDCP_DATA_REQ / %d Bytes --->][PDCP][MOD %u][UE %04lx][RB %ld]\n",
            ctxt.frame,
            ctxt.instance,
            rab_id,
            len,
            ctxt.module_id,
            ctxt.rntiMaybeUEid,
            rab_id);
      pdcp_data_req(
                &ctxt,
                SRB_FLAG_NO,
                //DEFAULT_RAB_ID,
               rab_id,
                RLC_MUI_UNDEFINED,
                RLC_SDU_CONFIRM_NO,
                len,
                (unsigned char *)nl_rx_buf,
                    PDCP_TRANSMISSION_MODE_TRANSPARENT
                    , NULL, NULL
                );

      //pdcp_data_req(&ctxt, SRB_FLAG_NO, rab_id, RLC_MUI_UNDEFINED,
      //              RLC_SDU_CONFIRM_NO, len, (unsigned char *)nl_rx_buf,
      //              PDCP_TRANSMISSION_MODE_DATA
      //              , NULL, NULL
      //             );
    } else {
      LOG_D(PDCP,
            "[FRAME %5u][UE][IP][INSTANCE %ld][RB %ld][--- PDCP_DATA_REQ / %d Bytes ---X][PDCP][MOD %u][UE %04lx][RB %ld] NON INSTANCIATED INSTANCE key 0x%" PRIx64 ", DROPPED\n",
            ctxt.frame,
            ctxt.instance,
            rab_id,
            len,
            ctxt.module_id,
            ctxt.rntiMaybeUEid,
            rab_id,
            key);
      // if (!UE_NAS_USE_TUN) {
      //     pdcp_data_req(
      //         &ctxt,
      //         SRB_FLAG_NO,
      //         DEFAULT_RAB_ID,
      //         RLC_MUI_UNDEFINED,
      //         RLC_SDU_CONFIRM_NO,
      //         len,
      //         (unsigned char *)nl_rx_buf,
      //             PDCP_TRANSMISSION_MODE_TRANSPARENT
      //             , NULL, NULL
      //         );
      // }
    }
  } while (len > 0);

  VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_MBMS_FIFO_READ, 0 );
  return len;
}

int pdcp_fifo_read_input_sdus_fromnetlinksock (const protocol_ctxt_t *const  ctxt_pP) {
  int  len = 1;
  int rlc_data_req_flag = 3;

  while ((len > 0) && (rlc_data_req_flag !=0))  {
    pdcp_data_req_header_t pdcp_read_header_g;
    protocol_ctxt_t                ctxt_cpy = *ctxt_pP;
    protocol_ctxt_t                ctxt;
    hash_key_t                     key       = HASHTABLE_NOT_A_KEY_VALUE;
    hashtable_rc_t                 h_rc;
    pdcp_t                        *pdcp_p    = NULL;
    static unsigned char pdcp_read_state_g =0;
    rb_id_t          rab_id  = 0;
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ, 1 );
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ_BUFFER, 1 );
    struct msghdr nas_msg_rx;
    len = recvmsg(nas_sock_fd[0], &nas_msg_rx, 0);
    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ_BUFFER, 0 );

    if (len > 0) {
      for (struct nlmsghdr *nas_nlh_rx = (struct nlmsghdr *)nl_rx_buf;
           NLMSG_OK(nas_nlh_rx, len);
           nas_nlh_rx = NLMSG_NEXT(nas_nlh_rx, len)) {
        if (nas_nlh_rx->nlmsg_type == NLMSG_DONE) {
          LOG_D(PDCP, "[PDCP][NETLINK] RX NLMSG_DONE\n");
          //return;
        }

        if (nas_nlh_rx->nlmsg_type == NLMSG_ERROR) {
          LOG_D(PDCP, "[PDCP][NETLINK] RX NLMSG_ERROR\n");
        }

        if (pdcp_read_state_g == 0) {
          if (nas_nlh_rx->nlmsg_len == sizeof (pdcp_data_req_header_t) + sizeof(struct nlmsghdr)) {
            pdcp_read_state_g = 1;  //get
            memcpy((void *)&pdcp_read_header_g, (void *)NLMSG_DATA(nas_nlh_rx), sizeof(pdcp_data_req_header_t));
            LOG_D(PDCP, "[PDCP][NETLINK] RX pdcp_data_req_header_t inst %u, rb_id %ld data_size %d, source L2Id 0x%08x, destination L2Id 0x%08x\n",
                  pdcp_read_header_g.inst, pdcp_read_header_g.rb_id, pdcp_read_header_g.data_size,pdcp_read_header_g.sourceL2Id, pdcp_read_header_g.destinationL2Id );
          } else {
            LOG_E(PDCP, "[PDCP][NETLINK] WRONG size %d should be sizeof (pdcp_data_req_header_t) + sizeof(struct nlmsghdr)\n",
                  nas_nlh_rx->nlmsg_len);
          }
        } else {
          pdcp_read_state_g = 0;
          // print_active_requests()
          LOG_D(PDCP, "[PDCP][NETLINK] Something in socket, length %zu\n",
                nas_nlh_rx->nlmsg_len - sizeof(struct nlmsghdr));
          /* TODO: do we have to reset to 0 or not? not for a scenario with 1 UE at least */
          //          pdcp_read_header_g.inst = 0;
          //#warning "TO DO CORRCT VALUES FOR ue mod id, enb mod id"
          ctxt.frame         = ctxt_cpy.frame;
          ctxt.enb_flag      = ctxt_cpy.enb_flag;
          LOG_D(PDCP, "[PDCP][NETLINK] pdcp_read_header_g.rb_id = %ld, source L2Id = 0x%08x, destination L2Id = 0x%08x \n", pdcp_read_header_g.rb_id, pdcp_read_header_g.sourceL2Id,
                pdcp_read_header_g.destinationL2Id);

          if (ctxt.enb_flag) {
            ctxt.module_id = 0;
            rab_id      = pdcp_read_header_g.rb_id % LTE_maxDRB;
            ctxt.rntiMaybeUEid = pdcp_eNB_UE_instance_to_rnti[pdcp_read_header_g.rb_id / LTE_maxDRB];

            if (rab_id != 0) {
              rab_id = rab_id % LTE_maxDRB;
              key = PDCP_COLL_KEY_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id, SRB_FLAG_NO);
              h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);

              if (h_rc == HASH_TABLE_OK) {
                LOG_D(PDCP,
                      "[FRAME %5u][eNB][NETLINK][IP->PDCP] INST %d: Received socket with length %d (nlmsg_len = %zu) on Rab %ld for rnti: %ld \n",
                      ctxt.frame,
                      pdcp_read_header_g.inst,
                      len,
                      nas_nlh_rx->nlmsg_len - sizeof(struct nlmsghdr),
                      pdcp_read_header_g.rb_id,
                      ctxt.rntiMaybeUEid);
                LOG_D(PDCP,
                      "[FRAME %5u][eNB][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes --->][PDCP][MOD %u]UE %lu][RB %ld]\n",
                      ctxt_cpy.frame,
                      pdcp_read_header_g.inst,
                      pdcp_read_header_g.rb_id,
                      pdcp_read_header_g.data_size,
                      ctxt.module_id,
                      ctxt.rntiMaybeUEid,
                      rab_id);
                pdcp_data_req(&ctxt,
                              SRB_FLAG_NO,
                              rab_id,
                              RLC_MUI_UNDEFINED,
                              RLC_SDU_CONFIRM_NO,
                              pdcp_read_header_g.data_size,
                              (unsigned char *)NLMSG_DATA(nas_nlh_rx),
                              PDCP_TRANSMISSION_MODE_DATA
                              ,NULL, NULL
                             );
              } else {
                LOG_D(PDCP,
                      "[FRAME %5u][eNB][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes ---X][PDCP][MOD %u][UE %lu][RB %ld] NON INSTANCIATED INSTANCE, DROPPED\n",
                      ctxt.frame,
                      pdcp_read_header_g.inst,
                      pdcp_read_header_g.rb_id,
                      pdcp_read_header_g.data_size,
                      ctxt.module_id,
                      ctxt.rntiMaybeUEid,
                      rab_id);
              }
            } else  { // rb_id =0, thus interpreated as broadcast and transported as multiple unicast
              // is a broadcast packet, we have to send this packet on all default RABS of all connected UEs
              //#warning CODE TO BE REVIEWED, ONLY WORK FOR SIMPLE TOPOLOGY CASES
              // never finished code, dropped
            }
          } else { // ctxt.enb_flag => UE
            if (NFAPI_MODE == NFAPI_UE_STUB_PNF || NFAPI_MODE == NFAPI_MODE_STANDALONE_PNF) {
              ctxt.module_id = pdcp_read_header_g.inst;
            } else {
              ctxt.module_id = 0;
            }

            rab_id      = pdcp_read_header_g.rb_id % LTE_maxDRB;
            ctxt.rntiMaybeUEid = pdcp_UE_UE_module_id_to_rnti[ctxt.module_id];

            if (rab_id != 0) {
              if (rab_id == 1) {
                LOG_D(PDCP, "PDCP_COLL_KEY_DEFAULT_DRB_VALUE(module_id=%d, rnti=%lx, enb_flag=%d)\n", ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);
                key = PDCP_COLL_KEY_DEFAULT_DRB_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);
                h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
                LOG_D(PDCP, "request key %x : (%d,%lx,%d,%ld)\n", (uint8_t)key, ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id);
              } else {
                rab_id = rab_id % LTE_maxDRB;
                LOG_D(PDCP, "PDCP_COLL_KEY_VALUE(module_id=%d, rnti=%lx, enb_flag=%d, rab_id=%ld, SRB_FLAG=%d)\n", ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id, SRB_FLAG_NO);
                key = PDCP_COLL_KEY_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id, SRB_FLAG_NO);
                h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
                LOG_D(PDCP, "request key %x : (%d,%lx,%d,%ld)\n", (uint8_t)key, ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id);
              }

              if (h_rc == HASH_TABLE_OK) {
                rab_id = pdcp_p->rb_id;
                LOG_D(PDCP, "[FRAME %5u][UE][NETLINK][IP->PDCP] INST %d: Received socket with length %d (nlmsg_len = %zu) on Rab %ld \n",
                      ctxt.frame,
                      pdcp_read_header_g.inst,
                      len,
                      nas_nlh_rx->nlmsg_len-sizeof(struct nlmsghdr),
                      pdcp_read_header_g.rb_id);
                LOG_D(PDCP,
                      "[FRAME %5u][UE][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes --->][PDCP][MOD %u][UE %lu][RB %ld]\n",
                      ctxt.frame,
                      pdcp_read_header_g.inst,
                      pdcp_read_header_g.rb_id,
                      pdcp_read_header_g.data_size,
                      ctxt.module_id,
                      ctxt.rntiMaybeUEid,
                      rab_id);
                pdcp_data_req(
                  &ctxt,
                  SRB_FLAG_NO,
                  rab_id,
                  RLC_MUI_UNDEFINED,
                  RLC_SDU_CONFIRM_NO,
                  pdcp_read_header_g.data_size,
                  (unsigned char *)NLMSG_DATA(nas_nlh_rx),
                  PDCP_TRANSMISSION_MODE_DATA,
                  (NFAPI_MODE == NFAPI_UE_STUB_PNF || NFAPI_MODE == NFAPI_MODE_STANDALONE_PNF) ? NULL : &pdcp_read_header_g.sourceL2Id,
                  (NFAPI_MODE == NFAPI_UE_STUB_PNF || NFAPI_MODE == NFAPI_MODE_STANDALONE_PNF) ? NULL : &pdcp_read_header_g.destinationL2Id
                );
              } else { /* else of h_rc == HASH_TABLE_OK */
                LOG_D(PDCP,
                      "[FRAME %5u][UE][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes ---X][PDCP][MOD %u][UE %lu][RB %ld] NON INSTANCIATED INSTANCE key 0x%" PRIx64 ", DROPPED\n",
                      ctxt.frame,
                      pdcp_read_header_g.inst,
                      pdcp_read_header_g.rb_id,
                      pdcp_read_header_g.data_size,
                      ctxt.module_id,
                      ctxt.rntiMaybeUEid,
                      rab_id,
                      key);
              } /* h_rc != HASH_TABLE_OK */
            }  else {/* else of rab_id != 0 */
              LOG_D(PDCP, "Forcing send on DEFAULT_RAB_ID\n");
              LOG_D(PDCP,
                    "[FRAME %5u][eNB][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes --->][PDCP][MOD %u][UE %lu][RB DEFAULT_RAB_ID %u]\n",
                    ctxt.frame,
                    pdcp_read_header_g.inst,
                    pdcp_read_header_g.rb_id,
                    pdcp_read_header_g.data_size,
                    ctxt.module_id,
                    ctxt.rntiMaybeUEid,
                    DEFAULT_RAB_ID);
              pdcp_data_req (
                &ctxt,
                SRB_FLAG_NO,
                DEFAULT_RAB_ID,
                RLC_MUI_UNDEFINED,
                RLC_SDU_CONFIRM_NO,
                pdcp_read_header_g.data_size,
                (unsigned char *)NLMSG_DATA(nas_nlh_rx),
                PDCP_TRANSMISSION_MODE_DATA,
                (NFAPI_MODE == NFAPI_UE_STUB_PNF|| NFAPI_MODE == NFAPI_MODE_STANDALONE_PNF) ? NULL : &pdcp_read_header_g.sourceL2Id,
                (NFAPI_MODE == NFAPI_UE_STUB_PNF|| NFAPI_MODE == NFAPI_MODE_STANDALONE_PNF) ? NULL : &pdcp_read_header_g.destinationL2Id
              );
            } /* rab_id == 0 */
          } /*pdcp_read_state_g != 0 */
        } /* UE */
      } /* for loop on netlink buffers */

      return len;
    } /* len > 0 */

    VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_FIFO_READ, 0 );
  } /* while loop on reading from netlink socket */

  return 0;
} /* pdcp_fifo_read_input_sdus_fromnetlinksock */

void pdcp_fifo_read_input_sdus_frompc5s (const protocol_ctxt_t *const  ctxt_pP) {
  protocol_ctxt_t                ctxt_cpy = *ctxt_pP;
  protocol_ctxt_t                ctxt;
  hash_key_t                     key       = HASHTABLE_NOT_A_KEY_VALUE;
  hashtable_rc_t                 h_rc;
  struct pdcp_netlink_element_s *data_p    = NULL;
  /* avoid gcc warnings */
  (void)data_p;
  pdcp_t                        *pdcp_p    = NULL;
  int bytes_received;
  sidelink_pc5s_element *sl_pc5s_msg_send = NULL;
  pc5s_header_t *pc5s_header  = NULL;
  rb_id_t          rab_id  = 0;
  //TTN for D2D (PC5S)
  // receive a message from ProSe App
  char receive_buf[MAX_MESSAGE_SIZE];
  memset(receive_buf, 0, sizeof(receive_buf));
  socklen_t prose_addr_len = sizeof(prose_pdcp_addr);
  bytes_received = recvfrom(pdcp_pc5_sockfd, receive_buf, sizeof(receive_buf), MSG_TRUNC,
                            (struct sockaddr *) &prose_pdcp_addr, &prose_addr_len);
  if (bytes_received == -1) {
    LOG_E(PDCP, "%s(%d). recvfrom failed. %s\n", __FUNCTION__, __LINE__, strerror(errno));
    return;
  }
  if (bytes_received == 0) {
    LOG_E(PDCP, "%s(%d). EOF pdcp_pc5_sockfd.\n", __FUNCTION__, __LINE__);
  }
  if (bytes_received > sizeof(receive_buf)) {
    LOG_E(PDCP, "%s(%d). Message truncated. %d\n", __FUNCTION__, __LINE__, bytes_received);
    return;
  }
  if (bytes_received > 0) {
    pc5s_header = calloc(1, sizeof(pc5s_header_t));
    memcpy((void *)pc5s_header, (void *)receive_buf, sizeof(pc5s_header_t));

    char send_buf[MAX_MESSAGE_SIZE];
    switch(pc5s_header->traffic_type) {
      case TRAFFIC_PC5S_SESSION_INIT :
        //send reply to ProSe app
        LOG_D(PDCP,"Received a request to open PDCP socket and establish a new PDCP session ... send response to ProSe App \n");
        memset(send_buf, 0, sizeof(send_buf));
        sl_pc5s_msg_send = calloc(1, sizeof(sidelink_pc5s_element));
        sl_pc5s_msg_send->pc5s_header.traffic_type = TRAFFIC_PC5S_SESSION_INIT;
        sl_pc5s_msg_send->pc5sPrimitive.status = 1;
        memcpy(send_buf, sl_pc5s_msg_send, sizeof(sidelink_pc5s_element));
        int prose_addr_len = sizeof(prose_pdcp_addr);
        int bytes_sent = sendto(pdcp_pc5_sockfd, send_buf, sizeof(sidelink_pc5s_element), 0,
                                (struct sockaddr *) &prose_pdcp_addr, prose_addr_len);
        free (sl_pc5s_msg_send);

        if (bytes_sent < 0) {
          LOG_E(PDCP, "ERROR: Failed to send to ProSe App\n");
          exit(EXIT_FAILURE);
        }

        break;

      case TRAFFIC_PC5S_SIGNALLING:  /* PC5-S message -> send to other UE */
        if( LOG_DEBUGFLAG(DEBUG_PDCP) ) {
          debug_pdcp_pc5s_sdu((sidelink_pc5s_element *)send_buf,
                              "pdcp_fifo_read_input_sdus received aPC5S message");
        }

        /* TODO: do we have to reset to 0 or not? not for a scenario with 1 UE at least */
        //          pc5s_header.inst = 0;
        //#warning "TO DO CORRCT VALUES FOR ue mod id, enb mod id"
        ctxt.frame         = ctxt_cpy.frame;
        ctxt.enb_flag      = ctxt_cpy.enb_flag;
        LOG_I(PDCP, "[PDCP] pc5s_header->rb_id = %ld\n", pc5s_header->rb_id);

        if (ctxt_cpy.enb_flag) {
          ctxt.module_id = 0;
          rab_id      = pc5s_header->rb_id % LTE_maxDRB;
          ctxt.rntiMaybeUEid = pdcp_eNB_UE_instance_to_rnti[pdcp_eNB_UE_instance_to_rnti_index];
        } else {
          ctxt.module_id = 0;
          rab_id      = pc5s_header->rb_id % LTE_maxDRB;
          ctxt.rntiMaybeUEid = pdcp_UE_UE_module_id_to_rnti[ctxt.module_id];
        }

        //UE
        if (!ctxt.enb_flag) {
          if (rab_id != 0) {
            if (rab_id == 1) {
              LOG_D(PDCP, "PDCP_COLL_KEY_DEFAULT_DRB_VALUE(module_id=%d, rnti=%lx, enb_flag=%d)\n", ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);
              key = PDCP_COLL_KEY_DEFAULT_DRB_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag);
              h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
              LOG_D(PDCP, "request key %x : (%d,%lx,%d,%ld)\n", (uint8_t)key, ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id);
            } else {
              rab_id = rab_id % LTE_maxDRB;
              LOG_I(PDCP, "PDCP_COLL_KEY_VALUE(module_id=%d, rnti=%lx, enb_flag=%d, rab_id=%ld, SRB_FLAG=%d)\n", ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id, SRB_FLAG_NO);
              key = PDCP_COLL_KEY_VALUE(ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id, SRB_FLAG_NO);
              h_rc = hashtable_get(pdcp_coll_p, key, (void **)&pdcp_p);
              LOG_I(PDCP, "request key %x : (%d,%lx,%d,%ld)\n", (uint8_t)key, ctxt.module_id, ctxt.rntiMaybeUEid, ctxt.enb_flag, rab_id);
            }

            if (h_rc == HASH_TABLE_OK) {
              rab_id = pdcp_p->rb_id;
              LOG_I(PDCP, "[FRAME %5u][UE][NETLINK][IP->PDCP] INST %d: Received socket with length %d  on Rab %ld \n",
                    ctxt.frame,
                    pc5s_header->inst,
                    bytes_received,
                    pc5s_header->rb_id);
              LOG_I(PDCP,
                    "[FRAME %5u][UE][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes --->][PDCP][MOD %u][UE %lu][RB %ld]\n",
                    ctxt.frame,
                    pc5s_header->inst,
                    pc5s_header->rb_id,
                    pc5s_header->data_size,
                    ctxt.module_id,
                    ctxt.rntiMaybeUEid,
                    rab_id);
              /* pointers to pc5s_header fields possibly not aligned because pc5s_header points to a packed structure
               * Using these possibly unaligned pointers in a function call may trigger alignment errors at run time and
               * gcc, from v9,  now warns about it. fix these warnings by using  local variables
               */
              uint32_t sourceL2Id = pc5s_header->sourceL2Id;
              uint32_t destinationL2Id = pc5s_header->destinationL2Id;
              pdcp_data_req(
                &ctxt,
                SRB_FLAG_NO,
                rab_id,
                RLC_MUI_UNDEFINED,
                RLC_SDU_CONFIRM_NO,
                pc5s_header->data_size,
                (unsigned char *)receive_buf,
                PDCP_TRANSMISSION_MODE_DATA,
                &sourceL2Id,
                &destinationL2Id
              );
              pc5s_header->sourceL2Id = sourceL2Id;
              pc5s_header->destinationL2Id=destinationL2Id;             
            } else { /* else of h_rc == HASH_TABLE_OK */
              LOG_D(PDCP,
                    "[FRAME %5u][UE][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes ---X][PDCP][MOD %u][UE %lu][RB %ld] NON INSTANCIATED INSTANCE key 0x%" PRIx64 ", DROPPED\n",
                    ctxt.frame,
                    pc5s_header->inst,
                    pc5s_header->rb_id,
                    pc5s_header->data_size,
                    ctxt.module_id,
                    ctxt.rntiMaybeUEid,
                    rab_id,
                    key);
            }
          }  else { /* else of if (rab_id == 0) */
            LOG_D(PDCP, "Forcing send on DEFAULT_RAB_ID\n");
            LOG_D(PDCP,
                  "[FRAME %5u][eNB][IP][INSTANCE %u][RB %ld][--- PDCP_DATA_REQ / %d Bytes --->][PDCP][MOD %u][UE %lu][RB DEFAULT_RAB_ID %u]\n",
                  ctxt.frame,
                  pc5s_header->inst,
                  pc5s_header->rb_id,
                  pc5s_header->data_size,
                  ctxt.module_id,
                  ctxt.rntiMaybeUEid,
                  DEFAULT_RAB_ID);
            /* pointers to pc5s_header fields possibly not aligned because pc5s_header points to a packed structure
             * Using these possibly unaligned pointers in a function call may trigger alignment errors at run time and
             * gcc, from v9,  now warns about it. fix these warnings by using  local variables
             */
            uint32_t sourceL2Id = pc5s_header->sourceL2Id;
            uint32_t destinationL2Id = pc5s_header->destinationL2Id;
            pdcp_data_req (
              &ctxt,
              SRB_FLAG_NO,
              DEFAULT_RAB_ID,
              RLC_MUI_UNDEFINED,
              RLC_SDU_CONFIRM_NO,
              pc5s_header->data_size,
              (unsigned char *)receive_buf,
              PDCP_TRANSMISSION_MODE_DATA,
              &sourceL2Id,
              &destinationL2Id
            );
            pc5s_header->sourceL2Id = sourceL2Id;
            pc5s_header->destinationL2Id=destinationL2Id; 
          }
        } /* end of !ctxt.enb_flag */
        break;
      default:
        LOG_D(PDCP, "pc5s message type %d, unknown...\n", pc5s_header->traffic_type);
        break;
    } /* end of switch */
  }/* end of bytes_received > 0 */
  if (pc5s_header != NULL) {
    free(pc5s_header);
    pc5s_header = NULL;
  }
} /* pdcp_fifo_read_input_sdus_frompc5s */

//-----------------------------------------------------------------------------
int pdcp_fifo_read_input_sdus(const protocol_ctxt_t *const ctxt_pP)
{
  DevAssert(UE_NAS_USE_TUN || ENB_NAS_USE_TUN);
  return pdcp_fifo_read_input_sdus_fromtun(ctxt_pP);
}

//TTN for D2D (PC5S)

void
pdcp_pc5_socket_init() {
  //pthread_attr_t     attr;
  //struct sched_param sched_param;
  int optval; // flag value for setsockopt
  //int n; // message byte size
  //create PDCP socket
  pdcp_pc5_sockfd = socket(AF_INET, SOCK_DGRAM, 0);

  if (pdcp_pc5_sockfd < 0) {
    LOG_E(PDCP,"[pdcp_pc5_socket_init] Error opening socket %d (%d:%s)\n",pdcp_pc5_sockfd,errno, strerror(errno));
    exit(EXIT_FAILURE);
  }

  optval = 1;
  setsockopt(pdcp_pc5_sockfd, SOL_SOCKET, SO_REUSEADDR,
             (const void *)&optval, sizeof(int));
  fcntl(pdcp_pc5_sockfd,F_SETFL,O_NONBLOCK);
  bzero((char *) &pdcp_sin, sizeof(pdcp_sin));
  pdcp_sin.sin_family = AF_INET;
  pdcp_sin.sin_addr.s_addr = htonl(INADDR_ANY);
  pdcp_sin.sin_port = htons(PDCP_SOCKET_PORT_NO);

  // associate the parent socket with a port
  if (bind(pdcp_pc5_sockfd, (struct sockaddr *) &pdcp_sin,
           sizeof(pdcp_sin)) < 0) {
    LOG_E(PDCP,"[pdcp_pc5_socket_init] ERROR: Failed on binding the socket\n");
    exit(1);
  }
}

