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

    OpenAirInterface is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.


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

    You should have received a copy of the GNU General Public License
    along with OpenAirInterface.The full GNU General Public License is
   included in this distribution in the file called "COPYING". If not,
   see <http://www.gnu.org/licenses/>.

  Contact Information
  OpenAirInterface Admin: openair_admin@eurecom.fr
  OpenAirInterface Tech : openair_tech@eurecom.fr
  OpenAirInterface Dev  : openair4g-devel@eurecom.fr

  Address      : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.

 *******************************************************************************/

#include <stdio.h>
#include <stdint.h>


#include "s1ap_common.h"
#include "s1ap_ies_defs.h"
#include "s1ap_mme_encoder.h"
#include "s1ap_mme_handlers.h"
#include "s1ap_mme_nas_procedures.h"
#include "s1ap_mme_retransmission.h"

#include "s1ap_mme_itti_messaging.h"

#include "s1ap_mme.h"

#include "intertask_interface.h"
#include "timer.h"

#include "assertions.h"
#include "conversions.h"
#include "msc.h"

/* Every time a new UE is associated, increment this variable.
 * But care if it wraps to increment also the mme_ue_s1ap_id_has_wrapped
 * variable. Limit: UINT32_MAX (in stdint.h).
 */
static uint32_t mme_ue_s1ap_id = 0;
static uint8_t  mme_ue_s1ap_id_has_wrapped = 0;

extern const char *s1ap_direction2String[];


int s1ap_mme_handle_initial_ue_message(uint32_t assoc_id, uint32_t stream,
                                       struct s1ap_message_s *message)
{
  S1ap_InitialUEMessageIEs_t *initialUEMessage_p;
  ue_description_t      *ue_ref;
  eNB_description_t     *eNB_ref;
  uint32_t               eNB_ue_s1ap_id;

  initialUEMessage_p = &message->msg.s1ap_InitialUEMessageIEs;

  MSC_LOG_RX_MESSAGE(
  		MSC_S1AP_MME,
  		MSC_S1AP_ENB,
  		NULL,0,
  		"0 initialUEMessage/%s assoc_id %u stream %u "S1AP_UE_ID_FMT" ",
  		s1ap_direction2String[message->direction],
  		assoc_id, stream, initialUEMessage_p->eNB_UE_S1AP_ID);

  if ((eNB_ref = s1ap_is_eNB_assoc_id_in_list(assoc_id)) == NULL) {
    S1AP_DEBUG("Unkwnon eNB on assoc_id %d\n", assoc_id);
    return -1;
  }

  // eNB UE S1AP ID is limited to 24 bits
  eNB_ue_s1ap_id = (uint32_t)(initialUEMessage_p->eNB_UE_S1AP_ID & 0x00ffffff);

  S1AP_DEBUG("New Initial UE message received with eNB UE S1AP ID: "S1AP_UE_ID_FMT"\n",
             eNB_ue_s1ap_id);

  ue_ref = s1ap_is_ue_eNB_id_in_list(eNB_ref, eNB_ue_s1ap_id);

  if (ue_ref == NULL) {
    uint16_t tac = 0;

    /* This UE eNB Id has currently no known s1 association.
     * Create new UE context by associating new mme_ue_s1ap_id.
     * Update eNB UE list.
     * Forward message to NAS.
     */
    if ((ue_ref = s1ap_new_ue(assoc_id)) == NULL)
      // If we failed to allocate a new UE return -1
    {
      return -1;
    }

    ue_ref->s1_ue_state = S1AP_UE_WAITING_CSR;
    ue_ref->eNB_ue_s1ap_id = eNB_ue_s1ap_id;
    ue_ref->mme_ue_s1ap_id = (uint32_t)ue_ref;
    // On which stream we received the message
    ue_ref->sctp_stream_recv = stream;
    ue_ref->sctp_stream_send = ue_ref->eNB->next_sctp_stream;

    /* Increment the sctp stream for the eNB association.
     * If the next sctp stream is >= outstream negociated between eNB and MME,
     * wrap to first stream.
     * TODO: search for the first available stream instead.
     */
    if (ue_ref->eNB->next_sctp_stream++ >= ue_ref->eNB->outstreams) {
      ue_ref->eNB->next_sctp_stream = 1;
    }

    /* Increment the unique UE mme s1ap id and
     * take care about overflow case.
     */
    if (mme_ue_s1ap_id_has_wrapped == 0) {
      mme_ue_s1ap_id++;

      if (mme_ue_s1ap_id == 0) {
        mme_ue_s1ap_id_has_wrapped = 1;
      }
    } else {
      /* TODO: should take the first available mme_ue_s1ap_id instead of
       * the mme_ue_s1ap_id variable.
       */
      DevMessage("mme ue s1ap id has wrapped -> case not handled\n");
    }

    s1ap_dump_eNB(ue_ref->eNB);

    OCTET_STRING_TO_TAC(&initialUEMessage_p->tai.tAC, tac);

    /* We received the first NAS transport message: initial UE message.
     * Send a NAS ESTABLISH IND to NAS layer
     */
#if defined(ORIGINAL_CODE)
    s1ap_mme_itti_nas_establish_ind(
      ue_ref->mme_ue_s1ap_id,
      initialUEMessage_p->nas_pdu.buf,
      initialUEMessage_p->nas_pdu.size,
      initialUEMessage_p->rrC_Establishment_Cause,
      tac);
#else
    s1ap_mme_itti_mme_app_establish_ind(
      ue_ref->mme_ue_s1ap_id,
      initialUEMessage_p->nas_pdu.buf,
      initialUEMessage_p->nas_pdu.size,
      initialUEMessage_p->rrC_Establishment_Cause,
      tac);
#endif
  }

  return 0;
}



int s1ap_mme_handle_uplink_nas_transport(uint32_t assoc_id, uint32_t stream,
    struct s1ap_message_s *message)
{
  S1ap_UplinkNASTransportIEs_t *uplinkNASTransport_p;
  ue_description_t *ue_ref;

  uplinkNASTransport_p = &message->msg.s1ap_UplinkNASTransportIEs;

  MSC_LOG_RX_MESSAGE(
	  	MSC_S1AP_MME,
	    MSC_S1AP_ENB,
  		NULL,0,
  		"0 uplinkNASTransport/%s mme_ue_s1ap_id "S1AP_UE_ID_FMT" eNB_ue_s1ap_id "S1AP_UE_ID_FMT" nas len %u",
  		s1ap_direction2String[message->direction],
  		uplinkNASTransport_p->mme_ue_s1ap_id,
  		uplinkNASTransport_p->eNB_UE_S1AP_ID,
  		uplinkNASTransport_p->nas_pdu.size);

  if ((ue_ref = s1ap_is_ue_mme_id_in_list(uplinkNASTransport_p->mme_ue_s1ap_id))
      == NULL) {
    S1AP_DEBUG("No UE is attached to this mme UE s1ap id: "S1AP_UE_ID_FMT"\n",
               (int)uplinkNASTransport_p->mme_ue_s1ap_id);
    return -1;
  }

  if (ue_ref->s1_ue_state != S1AP_UE_CONNECTED) {
    S1AP_DEBUG("Received uplink NAS transport while UE in state != S1AP_UE_CONNECTED\n");
    return -1;
  }

  //TODO: forward NAS PDU to NAS
#if defined(DISABLE_USE_NAS)
  DevMessage("Can't go further (TODO)\n");
#else
  s1ap_mme_itti_nas_uplink_ind(uplinkNASTransport_p->mme_ue_s1ap_id, uplinkNASTransport_p->nas_pdu.buf,
                               uplinkNASTransport_p->nas_pdu.size);
#endif
  return 0;
}



int s1ap_mme_handle_nas_non_delivery(uint32_t assoc_id, uint32_t stream,
                                     struct s1ap_message_s *message)
{
  S1ap_NASNonDeliveryIndication_IEs_t *nasNonDeliveryIndication_p = NULL;
  ue_description_t *ue_ref = NULL;

  /* UE associated signalling on stream == 0 is not valid. */
  if (stream == 0) {
    S1AP_DEBUG("Received new nas non delivery on stream == 0\n");
    return -1;
  }

  nasNonDeliveryIndication_p = &message->msg.s1ap_NASNonDeliveryIndication_IEs;

  MSC_LOG_RX_MESSAGE(
	  	MSC_S1AP_MME,
	    MSC_S1AP_ENB,
  		NULL,0,
  		"0 NASNonDeliveryIndication/%s mme_ue_s1ap_id "S1AP_UE_ID_FMT" eNB_ue_s1ap_id "S1AP_UE_ID_FMT" cause %u nas len %u",
  		s1ap_direction2String[message->direction],
  		nasNonDeliveryIndication_p->mme_ue_s1ap_id,
  		nasNonDeliveryIndication_p->eNB_UE_S1AP_ID,
  		nasNonDeliveryIndication_p->cause,
  		nasNonDeliveryIndication_p->nas_pdu.size);

  if ((ue_ref = s1ap_is_ue_mme_id_in_list(nasNonDeliveryIndication_p->mme_ue_s1ap_id))
      == NULL) {
    S1AP_DEBUG("No UE is attached to this mme UE s1ap id: "S1AP_UE_ID_FMT"\n",
               (int)nasNonDeliveryIndication_p->mme_ue_s1ap_id);
    return -1;
  }

  if (ue_ref->s1_ue_state != S1AP_UE_CONNECTED) {
    S1AP_DEBUG("Received uplink NASNonDeliveryIndication while UE in state != S1AP_UE_CONNECTED\n");
    return -1;
  }

  //TODO: forward NAS PDU to NAS
  s1ap_mme_itti_nas_non_delivery_ind(
		  nasNonDeliveryIndication_p->mme_ue_s1ap_id,
		  nasNonDeliveryIndication_p->nas_pdu.buf,
		  nasNonDeliveryIndication_p->nas_pdu.size);
  return 0;
}

int s1ap_generate_downlink_nas_transport(const uint32_t ue_id, void * const data,
    const uint32_t size)
{
  ue_description_t *ue_ref;
  uint8_t          *buffer_p;
  uint32_t          length;

  if ((ue_ref = s1ap_is_ue_mme_id_in_list(ue_id)) == NULL) {
    /* If the UE-associated logical S1-connection is not established,
     * the MME shall allocate a unique MME UE S1AP ID to be used for the UE.
     */
    S1AP_DEBUG("Unknown UE MME ID "S1AP_UE_ID_FMT", This case is not handled right now\n", ue_id);

    return -1;
  } else {
    /* We have fount the UE in the list.
     * Create new IE list message and encode it.
     */
    S1ap_DownlinkNASTransportIEs_t *downlinkNasTransport = NULL;

    s1ap_message message;

    memset(&message, 0, sizeof(s1ap_message));

    message.procedureCode = S1ap_ProcedureCode_id_downlinkNASTransport;
    message.direction     = S1AP_PDU_PR_initiatingMessage;

    ue_ref->s1_ue_state = S1AP_UE_CONNECTED;

    downlinkNasTransport = &message.msg.s1ap_DownlinkNASTransportIEs;

    /* Setting UE informations with the ones fount in ue_ref */
    downlinkNasTransport->mme_ue_s1ap_id = ue_ref->mme_ue_s1ap_id;
    downlinkNasTransport->eNB_UE_S1AP_ID = ue_ref->eNB_ue_s1ap_id;

    /* Fill in the NAS pdu */
    OCTET_STRING_fromBuf(&downlinkNasTransport->nas_pdu, (char*)data, size);

    if (s1ap_mme_encode_pdu(&message, &buffer_p, &length) < 0) {
      // TODO: handle something
      return -1;
    }

    S1AP_DEBUG("Send S1ap_ProcedureCode_id_downlinkNASTransport ue_id = "S1AP_UE_ID_FMT" mme_ue_s1ap_id = "S1AP_UE_ID_FMT" eNB_UE_S1AP_ID = "S1AP_UE_ID_FMT"\n",
               ue_id,
               downlinkNasTransport->mme_ue_s1ap_id,
               downlinkNasTransport->eNB_UE_S1AP_ID);

    MSC_LOG_TX_MESSAGE(
    		MSC_S1AP_MME,
    		MSC_S1AP_ENB,
    		NULL,0,
    		"0 downlinkNASTransport/initiatingMessage ue_id "S1AP_UE_ID_FMT" mme_ue_s1ap_id "S1AP_UE_ID_FMT" eNB_ue_s1ap_id"S1AP_UE_ID_FMT" nas length %u",
    		ue_id,
    		downlinkNasTransport->mme_ue_s1ap_id,
    		downlinkNasTransport->eNB_UE_S1AP_ID,
    		size);

    s1ap_mme_itti_send_sctp_request(buffer_p, length,
                                    ue_ref->eNB->sctp_assoc_id,
                                    ue_ref->sctp_stream_send);
  }

  return 0;
}

#if defined(DISABLE_USE_NAS)
int s1ap_handle_attach_accepted(nas_attach_accept_t *attach_accept_p)
{
  /* We received create session response from S-GW on S11 interface abstraction.
   * At least one bearer has been established. We can now send s1ap initial context setup request
   * message to eNB.
   */
  uint8_t supportedAlgorithms[] = { 0x00, 0x02 };
  uint8_t offset = 0;
  uint8_t *buffer_p;
  uint32_t length;

  ue_description_t *ue_ref = NULL;
  s1ap_message message;
  s1ap_initial_ctxt_setup_req_t *initial_p;

  S1ap_InitialContextSetupRequestIEs_t *initialContextSetupRequest_p;
  S1ap_E_RABToBeSetupItemCtxtSUReq_t    e_RABToBeSetup;

  DevAssert(attach_accept_p != NULL);

  initial_p = &attach_accept_p->transparent;

  if ((ue_ref = s1ap_is_ue_mme_id_in_list(initial_p->mme_ue_s1ap_id)) == NULL) {
    S1AP_DEBUG("This mme ue s1ap id ("S1AP_UE_ID_FMT") is not attached to any UE context\n",
               initial_p->mme_ue_s1ap_id);
    return -1;
  }

  /* Start the outcome response timer.
   * When time is reached, MME consider that procedure outcome has failed.
   */
  //     timer_setup(mme_config.s1ap_config.outcome_drop_timer_sec, 0, TASK_S1AP, INSTANCE_DEFAULT,
  //                 TIMER_ONE_SHOT,
  //                 NULL,
  //                 &ue_ref->outcome_response_timer_id);
  /* Insert the timer in the MAP of mme_ue_s1ap_id <-> timer_id */
  //     s1ap_timer_insert(ue_ref->mme_ue_s1ap_id, ue_ref->outcome_response_timer_id);

  memset(&message, 0, sizeof(s1ap_message));
  memset(&e_RABToBeSetup, 0, sizeof(S1ap_E_RABToBeSetupItemCtxtSUReq_t));

  message.procedureCode = S1ap_ProcedureCode_id_InitialContextSetup;
  message.direction     = S1AP_PDU_PR_initiatingMessage;

  initialContextSetupRequest_p = &message.msg.s1ap_InitialContextSetupRequestIEs;

  initialContextSetupRequest_p->mme_ue_s1ap_id = (unsigned long)ue_ref->mme_ue_s1ap_id;
  initialContextSetupRequest_p->eNB_UE_S1AP_ID = (unsigned long)ue_ref->eNB_ue_s1ap_id;

  /* uEaggregateMaximumBitrateDL and uEaggregateMaximumBitrateUL expressed in term of bits/sec */
  initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateDL = initial_p->ambr.br_dl;
  initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateUL = initial_p->ambr.br_ul;

  e_RABToBeSetup.e_RAB_ID = initial_p->ebi;
  e_RABToBeSetup.e_RABlevelQoSParameters.qCI = initial_p->qci;
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.priorityLevel
    = initial_p->prio_level; //No priority
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionCapability
    = initial_p->pre_emp_capability;
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionVulnerability
    = initial_p->pre_emp_vulnerability;

  /* Set the GTP-TEID. This is the S1-U S-GW TEID */
  INT32_TO_OCTET_STRING(initial_p->teid, &e_RABToBeSetup.gTP_TEID);

  /* S-GW IP address(es) for user-plane */
  if ((initial_p->s_gw_address.pdn_type == IPv4) ||
      (initial_p->s_gw_address.pdn_type == IPv4_AND_v6)) {
    e_RABToBeSetup.transportLayerAddress.buf = calloc(4, sizeof(uint8_t));
    /* Only IPv4 supported */
    memcpy(e_RABToBeSetup.transportLayerAddress.buf,
           initial_p->s_gw_address.address.ipv4_address,
           4);
    offset += 4;
    e_RABToBeSetup.transportLayerAddress.size = 4;
    e_RABToBeSetup.transportLayerAddress.bits_unused = 0;
  }

  if ((initial_p->s_gw_address.pdn_type == IPv6) ||
      (initial_p->s_gw_address.pdn_type == IPv4_AND_v6)) {
    if (offset == 0) {
      /* Both IPv4 and IPv6 provided */
      /* TODO: check memory allocation */
      e_RABToBeSetup.transportLayerAddress.buf = calloc(16, sizeof(uint8_t));
    } else {
      /* Only IPv6 supported */
      /* TODO: check memory allocation */
      e_RABToBeSetup.transportLayerAddress.buf
        = realloc(e_RABToBeSetup.transportLayerAddress.buf, (16 + offset) * sizeof(uint8_t));
    }

    memcpy(&e_RABToBeSetup.transportLayerAddress.buf[offset],
           initial_p->s_gw_address.address.ipv6_address,
           16);
    e_RABToBeSetup.transportLayerAddress.size = 16 + offset;
    e_RABToBeSetup.transportLayerAddress.bits_unused = 0;
  }

  ASN_SEQUENCE_ADD(&initialContextSetupRequest_p->e_RABToBeSetupListCtxtSUReq,
                   &e_RABToBeSetup);

  initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.buf =
    (uint8_t *)supportedAlgorithms;
  initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.size = 2;
  initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.bits_unused
    = 0;

  initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.buf
    = (uint8_t *)supportedAlgorithms;
  initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.size
    = 2;
  initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.bits_unused
    = 0;

  initialContextSetupRequest_p->securityKey.buf  = initial_p->keNB; /* 256 bits length */
  initialContextSetupRequest_p->securityKey.size = 32;
  initialContextSetupRequest_p->securityKey.bits_unused = 0;

  if (s1ap_mme_encode_pdu(&message, &buffer_p, &length) < 0) {
    // TODO: handle something
    return -1;
  }

  return s1ap_mme_itti_send_sctp_request(buffer_p, length, ue_ref->eNB->sctp_assoc_id,
                                         ue_ref->sctp_stream_send);
}
#else
//void s1ap_handle_conn_est_cnf(nas_conn_est_cnf_t *nas_conn_est_cnf_p)
//{
//    /* We received create session response from S-GW on S11 interface abstraction.
//     * At least one bearer has been established. We can now send s1ap initial context setup request
//     * message to eNB.
//     */
//    uint8_t supportedAlgorithms[] = { 0x00, 0x00 };
//    uint8_t offset = 0;
//    uint8_t *buffer_p;
//    uint32_t length, teid = 12;
//
//    ue_description_t *ue_ref = NULL;
//    s1ap_message message;
//
//    S1ap_InitialContextSetupRequestIEs_t *initialContextSetupRequest_p;
//    S1ap_E_RABToBeSetupItemCtxtSUReq_t    e_RABToBeSetup;
//    S1ap_NAS_PDU_t                        nas_pdu;
//
//    DevAssert(nas_conn_est_cnf_p != NULL);
//
//    if ((ue_ref = s1ap_is_ue_mme_id_in_list(nas_conn_est_cnf_p->UEid)) == NULL) {
//        S1AP_DEBUG("This mme ue s1ap id (%08x) is not attached to any UE context\n",
//                nas_conn_est_cnf_p->UEid);
//        DevParam(nas_conn_est_cnf_p->UEid, 0, 0);
//    }
//
//    /* Start the outcome response timer.
//     * When time is reached, MME consider that procedure outcome has failed.
//     */
////     timer_setup(mme_config.s1ap_config.outcome_drop_timer_sec, 0, TASK_S1AP, INSTANCE_DEFAULT,
////                 TIMER_ONE_SHOT,
////                 NULL,
////                 &ue_ref->outcome_response_timer_id);
//    /* Insert the timer in the MAP of mme_ue_s1ap_id <-> timer_id */
////     s1ap_timer_insert(ue_ref->mme_ue_s1ap_id, ue_ref->outcome_response_timer_id);
//
//    memset(&message, 0, sizeof(s1ap_message));
//    memset(&e_RABToBeSetup, 0, sizeof(S1ap_E_RABToBeSetupItemCtxtSUReq_t));
//
//    message.procedureCode = S1ap_ProcedureCode_id_InitialContextSetup;
//    message.direction     = S1AP_PDU_PR_initiatingMessage;
//
//    initialContextSetupRequest_p = &message.msg.s1ap_InitialContextSetupRequestIEs;
//
//    initialContextSetupRequest_p->mme_ue_s1ap_id = (unsigned long)ue_ref->mme_ue_s1ap_id;
//    initialContextSetupRequest_p->eNB_UE_S1AP_ID = (unsigned long)ue_ref->eNB_ue_s1ap_id;
//
//    /* uEaggregateMaximumBitrateDL and uEaggregateMaximumBitrateUL expressed in term of bits/sec */
////    initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateDL = initial_p->ambr.br_dl;
////    initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateUL = initial_p->ambr.br_ul;
//
//    initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateDL = 1024 * 1024;
//    initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateDL = 512 * 1024;
//
////    e_RABToBeSetup.e_RAB_ID = initial_p->ebi;
//    e_RABToBeSetup.e_RAB_ID = 5;
////    e_RABToBeSetup.e_RABlevelQoSParameters.qCI = initial_p->qci;
//    e_RABToBeSetup.e_RABlevelQoSParameters.qCI = 0;
//
//    memset(&nas_pdu, 0, sizeof(nas_pdu));
//
//    nas_pdu.size = nas_conn_est_cnf_p->nasMsg.length;
//    nas_pdu.buf  = nas_conn_est_cnf_p->nasMsg.data;
//
//    e_RABToBeSetup.nAS_PDU = &nas_pdu;
//    e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.priorityLevel =
//            S1ap_PriorityLevel_lowest;
//    e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionCapability =
//            S1ap_Pre_emptionCapability_shall_not_trigger_pre_emption;
//    e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionVulnerability =
//            S1ap_Pre_emptionVulnerability_not_pre_emptable;
//    INT32_TO_OCTET_STRING(teid, &e_RABToBeSetup.gTP_TEID);
////    e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.priorityLevel
////    = initial_p->prio_level; //No priority
////    e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionCapability
////    = initial_p->pre_emp_capability;
////    e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionVulnerability
////    = initial_p->pre_emp_vulnerability;
//
//    /* Set the GTP-TEID. This is the S1-U S-GW TEID */
////    INT32_TO_OCTET_STRING(initial_p->teid, &e_RABToBeSetup.gTP_TEID);
//
//    /* S-GW IP address(es) for user-plane */
////    if ((initial_p->s_gw_address.pdn_type == IPv4) ||
////        (initial_p->s_gw_address.pdn_type == IPv4_AND_v6))
////    {
////        e_RABToBeSetup.transportLayerAddress.buf = calloc(4, sizeof(uint8_t));
////        /* Only IPv4 supported */
////        memcpy(e_RABToBeSetup.transportLayerAddress.buf,
////               initial_p->s_gw_address.address.ipv4_address,
////               4);
////        offset += 4;
////        e_RABToBeSetup.transportLayerAddress.size = 4;
////        e_RABToBeSetup.transportLayerAddress.bits_unused = 0;
////    }
////    if ((initial_p->s_gw_address.pdn_type == IPv6) ||
////        (initial_p->s_gw_address.pdn_type == IPv4_AND_v6))
////    {
////        if (offset == 0) {
////            /* Both IPv4 and IPv6 provided */
////            /* TODO: check memory allocation */
////            e_RABToBeSetup.transportLayerAddress.buf = calloc(16, sizeof(uint8_t));
////        } else {
////            /* Only IPv6 supported */
////            /* TODO: check memory allocation */
////            e_RABToBeSetup.transportLayerAddress.buf
////            = realloc(e_RABToBeSetup.transportLayerAddress.buf, (16 + offset) * sizeof(uint8_t));
////        }
////        memcpy(&e_RABToBeSetup.transportLayerAddress.buf[offset],
////               initial_p->s_gw_address.address.ipv6_address,
////               16);
////        e_RABToBeSetup.transportLayerAddress.size = 16 + offset;
////        e_RABToBeSetup.transportLayerAddress.bits_unused = 0;
////    }
//
//    ASN_SEQUENCE_ADD(&initialContextSetupRequest_p->e_RABToBeSetupListCtxtSUReq,
//                     &e_RABToBeSetup);
//
//    initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.buf =
//        (uint8_t *)supportedAlgorithms;
//    initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.size = 2;
//    initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.bits_unused
//        = 0;
//
//    initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.buf
//        = (uint8_t *)supportedAlgorithms;
//    initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.size
//        = 2;
//    initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.bits_unused
//        = 0;
//
////    initialContextSetupRequest_p->securityKey.buf  = initial_p->keNB; /* 256 bits length */
//    uint8_t keNB[32];
//    memset(keNB, 0, sizeof(keNB));
//
//    initialContextSetupRequest_p->securityKey.buf = keNB;
//    initialContextSetupRequest_p->securityKey.size = 32;
//    initialContextSetupRequest_p->securityKey.bits_unused = 0;
//
//    if (s1ap_mme_encode_pdu(&message, &buffer_p, &length) < 0) {
//        // TODO: handle something
//        DevMessage("Failed to encode initial context setup request message\n");
//    }
//
//    free(nas_conn_est_cnf_p->nasMsg.data);
//
//    s1ap_mme_itti_send_sctp_request(buffer_p, length, ue_ref->eNB->sctp_assoc_id,
//                                    ue_ref->sctp_stream_send);
//}

void s1ap_handle_conn_est_cnf(const mme_app_connection_establishment_cnf_t * const conn_est_cnf_pP)
{
  /* We received create session response from S-GW on S11 interface abstraction.
   * At least one bearer has been established. We can now send s1ap initial context setup request
   * message to eNB.
   */
  uint8_t                               offset                = 0;
  uint8_t                              *buffer_p              = NULL;
  uint32_t                              length                = 0;
  ue_description_t                     *ue_ref                = NULL;
  S1ap_InitialContextSetupRequestIEs_t *initialContextSetupRequest_p = NULL;
  S1ap_E_RABToBeSetupItemCtxtSUReq_t    e_RABToBeSetup;
  S1ap_NAS_PDU_t                        nas_pdu;
  s1ap_message                          message;

  DevAssert(conn_est_cnf_pP != NULL);

  if ((ue_ref = s1ap_is_ue_mme_id_in_list(conn_est_cnf_pP->nas_conn_est_cnf.UEid)) == NULL) {
    S1AP_DEBUG("This mme ue s1ap id ("S1AP_UE_ID_FMT") is not attached to any UE context\n",
               conn_est_cnf_pP->nas_conn_est_cnf.UEid);

    DevParam(conn_est_cnf_pP->nas_conn_est_cnf.UEid, 0, 0);
  }

  /* Start the outcome response timer.
   * When time is reached, MME consider that procedure outcome has failed.
   */
  //     timer_setup(mme_config.s1ap_config.outcome_drop_timer_sec, 0, TASK_S1AP, INSTANCE_DEFAULT,
  //                 TIMER_ONE_SHOT,
  //                 NULL,
  //                 &ue_ref->outcome_response_timer_id);
  /* Insert the timer in the MAP of mme_ue_s1ap_id <-> timer_id */
  //     s1ap_timer_insert(ue_ref->mme_ue_s1ap_id, ue_ref->outcome_response_timer_id);

  memset(&message, 0, sizeof(s1ap_message));
  memset(&e_RABToBeSetup, 0, sizeof(S1ap_E_RABToBeSetupItemCtxtSUReq_t));

  message.procedureCode = S1ap_ProcedureCode_id_InitialContextSetup;
  message.direction     = S1AP_PDU_PR_initiatingMessage;

  initialContextSetupRequest_p = &message.msg.s1ap_InitialContextSetupRequestIEs;

  initialContextSetupRequest_p->mme_ue_s1ap_id = (unsigned long)ue_ref->mme_ue_s1ap_id;
  initialContextSetupRequest_p->eNB_UE_S1AP_ID = (unsigned long)ue_ref->eNB_ue_s1ap_id;

  /* uEaggregateMaximumBitrateDL and uEaggregateMaximumBitrateUL expressed in term of bits/sec */
  asn_uint642INTEGER(&initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateDL, conn_est_cnf_pP->ambr.br_dl);
  asn_uint642INTEGER(&initialContextSetupRequest_p->uEaggregateMaximumBitrate.uEaggregateMaximumBitRateUL, conn_est_cnf_pP->ambr.br_ul);

  e_RABToBeSetup.e_RAB_ID                    = conn_est_cnf_pP->eps_bearer_id; //5;
  e_RABToBeSetup.e_RABlevelQoSParameters.qCI = conn_est_cnf_pP->bearer_qos_qci;


  memset(&nas_pdu, 0, sizeof(nas_pdu));
  nas_pdu.size = conn_est_cnf_pP->nas_conn_est_cnf.nasMsg.length;
  nas_pdu.buf  = conn_est_cnf_pP->nas_conn_est_cnf.nasMsg.data;
  e_RABToBeSetup.nAS_PDU = &nas_pdu;

#if defined(ORIGINAL_S1AP_CODE)
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.priorityLevel =
    S1ap_PriorityLevel_lowest;
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionCapability =
    S1ap_Pre_emptionCapability_shall_not_trigger_pre_emption;
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionVulnerability =
    S1ap_Pre_emptionVulnerability_not_pre_emptable;
#else
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.priorityLevel =
    conn_est_cnf_pP->bearer_qos_prio_level;
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionCapability =
    conn_est_cnf_pP->bearer_qos_pre_emp_capability;
  e_RABToBeSetup.e_RABlevelQoSParameters.allocationRetentionPriority.pre_emptionVulnerability =
    conn_est_cnf_pP->bearer_qos_pre_emp_vulnerability;
#endif
  /* Set the GTP-TEID. This is the S1-U S-GW TEID */
  INT32_TO_OCTET_STRING(conn_est_cnf_pP->bearer_s1u_sgw_fteid.teid, &e_RABToBeSetup.gTP_TEID);

  /* S-GW IP address(es) for user-plane */
  if (conn_est_cnf_pP->bearer_s1u_sgw_fteid.ipv4) {
    e_RABToBeSetup.transportLayerAddress.buf = calloc(4, sizeof(uint8_t));
    /* Only IPv4 supported */
    memcpy(e_RABToBeSetup.transportLayerAddress.buf,
           &conn_est_cnf_pP->bearer_s1u_sgw_fteid.ipv4_address,
           4);
    offset += 4;
    e_RABToBeSetup.transportLayerAddress.size = 4;
    e_RABToBeSetup.transportLayerAddress.bits_unused = 0;
  }

  if (conn_est_cnf_pP->bearer_s1u_sgw_fteid.ipv6) {
    if (offset == 0) {
      /* Both IPv4 and IPv6 provided */
      /* TODO: check memory allocation */
      e_RABToBeSetup.transportLayerAddress.buf = calloc(16, sizeof(uint8_t));
    } else {
      /* Only IPv6 supported */
      /* TODO: check memory allocation */
      e_RABToBeSetup.transportLayerAddress.buf
        = realloc(e_RABToBeSetup.transportLayerAddress.buf, (16 + offset) * sizeof(uint8_t));
    }

    memcpy(&e_RABToBeSetup.transportLayerAddress.buf[offset],
           conn_est_cnf_pP->bearer_s1u_sgw_fteid.ipv6_address,
           16);
    e_RABToBeSetup.transportLayerAddress.size = 16 + offset;
    e_RABToBeSetup.transportLayerAddress.bits_unused = 0;
  }

  ASN_SEQUENCE_ADD(&initialContextSetupRequest_p->e_RABToBeSetupListCtxtSUReq,
                   &e_RABToBeSetup);

  initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.buf =
    (uint8_t *)&conn_est_cnf_pP->security_capabilities_encryption_algorithms;
  initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.size = 2;
  initialContextSetupRequest_p->ueSecurityCapabilities.encryptionAlgorithms.bits_unused
    = 0;

  initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.buf =
    (uint8_t *)&conn_est_cnf_pP->security_capabilities_integrity_algorithms;
  initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.size
    = 2;
  initialContextSetupRequest_p->ueSecurityCapabilities.integrityProtectionAlgorithms.bits_unused
    = 0;

  S1AP_DEBUG("security_capabilities_encryption_algorithms 0x%04X\n",
             conn_est_cnf_pP->security_capabilities_encryption_algorithms);
  S1AP_DEBUG("security_capabilities_integrity_algorithms 0x%04X\n",
             conn_est_cnf_pP->security_capabilities_integrity_algorithms);

  if (conn_est_cnf_pP->keNB) {
    initialContextSetupRequest_p->securityKey.buf = malloc(32);
    memcpy(initialContextSetupRequest_p->securityKey.buf,
           conn_est_cnf_pP->keNB,
           32);

    initialContextSetupRequest_p->securityKey.size = 32;
  } else {
    S1AP_DEBUG("No keNB\n");
    initialContextSetupRequest_p->securityKey.buf = NULL;
    initialContextSetupRequest_p->securityKey.size = 0;
  }

  initialContextSetupRequest_p->securityKey.bits_unused = 0;

  if (s1ap_mme_encode_pdu(&message, &buffer_p, &length) < 0) {
    // TODO: handle something
    DevMessage("Failed to encode initial context setup request message\n");
  }

  free(conn_est_cnf_pP->nas_conn_est_cnf.nasMsg.data);

  MSC_LOG_TX_MESSAGE(
  		MSC_S1AP_MME,
  		MSC_S1AP_ENB,
  		NULL,0,
  		"0 InitialContextSetup/initiatingMessage mme_ue_s1ap_id "S1AP_UE_ID_FMT" eNB_ue_s1ap_id "S1AP_UE_ID_FMT" nas length %u",
  		initialContextSetupRequest_p->mme_ue_s1ap_id,
  		initialContextSetupRequest_p->eNB_UE_S1AP_ID,
  		nas_pdu.size);

  s1ap_mme_itti_send_sctp_request(
    buffer_p,
    length,
    ue_ref->eNB->sctp_assoc_id,
    ue_ref->sctp_stream_send);
}

#endif
