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

    Copyright 2014-2015 Ben Wojtowicz

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

    This program 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*******************************************************************************

    File: liblte_mme.cc

    Description: Contains all the implementations for the LTE Mobility
                 Management Entity library.

    Revision History
    ----------    -------------    --------------------------------------------
    06/15/2014    Ben Wojtowicz    Created file.
    08/03/2014    Ben Wojtowicz    Added more decoding/encoding.
    09/03/2014    Ben Wojtowicz    Added more decoding/encoding and fixed MCC
                                   and MNC packing.
    11/01/2014    Ben Wojtowicz    Added more decoding/encoding.
    11/29/2014    Ben Wojtowicz    Added more decoding/encoding.
    12/16/2014    Ben Wojtowicz    Added more decoding/encoding.
    12/24/2014    Ben Wojtowicz    Cleaned up the Time Zone and Time IE.
    02/15/2015    Ben Wojtowicz    Added more decoding/encoding.

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

/*******************************************************************************
                              INCLUDES
*******************************************************************************/

#include "srslte/asn1/liblte_mme.h"
#include "srslte/common/liblte_security.h"

/*******************************************************************************
                              DEFINES
*******************************************************************************/

/*******************************************************************************
                              TYPEDEFS
*******************************************************************************/

/*******************************************************************************
                              GLOBAL VARIABLES
*******************************************************************************/

/*******************************************************************************
                              INFORMATION ELEMENT FUNCTIONS
*******************************************************************************/

/*********************************************************************
    IE Name: Additional Information

    Description: Provides additional information to upper layers in
                 relation to the generic NAS message transport
                 mechanism.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.0
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_additional_information_ie(LIBLTE_MME_ADDITIONAL_INFORMATION_STRUCT* add_info,
                                                            uint8**                                   ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (add_info != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = add_info->N_octets;
    for (i = 0; i < add_info->N_octets; i++) {
      (*ie_ptr)[1 + i] = add_info->info[i];
    }
    *ie_ptr += add_info->N_octets + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_additional_information_ie(uint8**                                   ie_ptr,
                                                              LIBLTE_MME_ADDITIONAL_INFORMATION_STRUCT* add_info)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && add_info != NULL) {
    add_info->N_octets = (*ie_ptr)[0];
    for (i = 0; i < add_info->N_octets; i++) {
      add_info->info[i] = (*ie_ptr)[1 + i];
    }
    *ie_ptr += add_info->N_octets + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Device Properties

    Description: Indicates if the UE is configured for NAS signalling
                 low priority.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.0A
                        24.008 v10.2.0 Section 10.5.7.8
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_device_properties_ie(LIBLTE_MME_DEVICE_PROPERTIES_ENUM device_props, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= device_props << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_device_properties_ie(uint8**                            ie_ptr,
                                                         uint8                              bit_offset,
                                                         LIBLTE_MME_DEVICE_PROPERTIES_ENUM* device_props)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && device_props != NULL) {
    *device_props = (LIBLTE_MME_DEVICE_PROPERTIES_ENUM)((**ie_ptr >> bit_offset) & 0x01);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Bearer Context Status

    Description: Indicates the state of each EPS bearer context that
                 can be identified by an EPS bearer identity.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.1
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_eps_bearer_context_status_ie(LIBLTE_MME_EPS_BEARER_CONTEXT_STATUS_STRUCT* ebcs,
                                                               uint8**                                      ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ebcs != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = 2;
    (*ie_ptr)[1] = (ebcs->ebi[7] << 7);
    (*ie_ptr)[1] |= (ebcs->ebi[6] << 6);
    (*ie_ptr)[1] |= (ebcs->ebi[5] << 5);
    (*ie_ptr)[2] = (ebcs->ebi[15] << 7);
    (*ie_ptr)[2] |= (ebcs->ebi[14] << 6);
    (*ie_ptr)[2] |= (ebcs->ebi[13] << 5);
    (*ie_ptr)[2] |= (ebcs->ebi[12] << 4);
    (*ie_ptr)[2] |= (ebcs->ebi[11] << 3);
    (*ie_ptr)[2] |= (ebcs->ebi[10] << 2);
    (*ie_ptr)[2] |= (ebcs->ebi[9] << 1);
    (*ie_ptr)[2] |= ebcs->ebi[8];
    *ie_ptr += 3;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_eps_bearer_context_status_ie(uint8**                                      ie_ptr,
                                                                 LIBLTE_MME_EPS_BEARER_CONTEXT_STATUS_STRUCT* ebcs)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && ebcs != NULL) {
    ebcs->ebi[5]  = ((*ie_ptr)[1] >> 5) & 0x01;
    ebcs->ebi[6]  = ((*ie_ptr)[1] >> 6) & 0x01;
    ebcs->ebi[7]  = ((*ie_ptr)[1] >> 7) & 0x01;
    ebcs->ebi[8]  = (*ie_ptr)[2] & 0x01;
    ebcs->ebi[9]  = ((*ie_ptr)[2] >> 1) & 0x01;
    ebcs->ebi[10] = ((*ie_ptr)[2] >> 2) & 0x01;
    ebcs->ebi[11] = ((*ie_ptr)[2] >> 3) & 0x01;
    ebcs->ebi[12] = ((*ie_ptr)[2] >> 4) & 0x01;
    ebcs->ebi[13] = ((*ie_ptr)[2] >> 5) & 0x01;
    ebcs->ebi[14] = ((*ie_ptr)[2] >> 6) & 0x01;
    ebcs->ebi[15] = ((*ie_ptr)[2] >> 7) & 0x01;
    *ie_ptr += 3;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Location Area Identification

    Description: Provides an unambiguous identification of location
                 areas within the area covered by the 3GPP system.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.2
                        24.008 v10.2.0 Section 10.5.1.3
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_location_area_id_ie(LIBLTE_MME_LOCATION_AREA_ID_STRUCT* lai, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (lai != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = (((lai->mcc / 10) % 10) << 4) | ((lai->mcc / 100) % 10);
    if (lai->mnc < 100) {
      (*ie_ptr)[1] = 0xF0 | (lai->mcc % 10);
      (*ie_ptr)[2] = ((lai->mnc % 10) << 4) | ((lai->mnc / 10) % 10);
    } else {
      (*ie_ptr)[1] = ((lai->mnc % 10) << 4) | (lai->mcc % 10);
      (*ie_ptr)[2] = (((lai->mnc / 10) % 10) << 4) | ((lai->mnc / 100) % 10);
    }
    (*ie_ptr)[3] = (lai->lac >> 8) & 0xFF;
    (*ie_ptr)[4] = lai->lac & 0xFF;
    *ie_ptr += 5;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_location_area_id_ie(uint8** ie_ptr, LIBLTE_MME_LOCATION_AREA_ID_STRUCT* lai)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && lai != NULL) {
    lai->mcc = ((*ie_ptr)[0] & 0x0F) * 100;
    lai->mcc += (((*ie_ptr)[0] >> 4) & 0x0F) * 10;
    lai->mcc += (*ie_ptr)[1] & 0x0F;
    if ((((*ie_ptr)[1] >> 4) & 0x0F) == 0x0F) {
      lai->mnc = ((*ie_ptr)[2] & 0x0F) * 10;
      lai->mnc += ((*ie_ptr)[2] >> 4) & 0x0F;
    } else {
      lai->mnc = ((*ie_ptr)[1] >> 4) & 0x0F;
      lai->mnc += ((*ie_ptr)[2] & 0x0F) * 100;
      lai->mnc += (((*ie_ptr)[2] >> 4) & 0x0F) * 10;
    }
    lai->lac = (*ie_ptr)[3] << 8;
    lai->lac |= (*ie_ptr)[4];
    *ie_ptr += 5;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Mobile Identity

    Description: Provides either the IMSI, TMSI/P-TMSI/M-TMSI, IMEI,
                 IMEISV, or TMGI, associated with the optional MBMS
                 session identity.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.3
                        24.008 v10.2.0 Section 10.5.1.4
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_mobile_id_ie(LIBLTE_MME_MOBILE_ID_STRUCT* mobile_id, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err  = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            id   = NULL;
  uint32            id32 = 0;
  uint32            i;
  uint8             length = 0;
  bool              odd    = false;

  if (mobile_id != NULL && ie_ptr != NULL) {
    if (LIBLTE_MME_MOBILE_ID_TYPE_IMSI == mobile_id->type_of_id) {
      id     = mobile_id->imsi;
      length = 8;
      odd    = true;
    } else if (LIBLTE_MME_MOBILE_ID_TYPE_IMEI == mobile_id->type_of_id) {
      id     = mobile_id->imei;
      length = 8;
      odd    = true;
    } else if (LIBLTE_MME_MOBILE_ID_TYPE_IMEISV == mobile_id->type_of_id) {
      id     = mobile_id->imeisv;
      length = 9;
      odd    = false;
    } else if (LIBLTE_MME_MOBILE_ID_TYPE_TMSI == mobile_id->type_of_id) {
      id32   = mobile_id->tmsi;
      length = 5;
      odd    = false;
    }
  } else {
    // TODO: Not handling these IDs
    return (err);
  }

  // Length
  **ie_ptr = length;
  *ie_ptr += 1;
  if (LIBLTE_MME_MOBILE_ID_TYPE_TMSI != mobile_id->type_of_id) {
    // | Identity digit 1 | odd/even | Id type |
    if (odd) {
      **ie_ptr = (id[0] << 4) | (1 << 3) | mobile_id->type_of_id;
    } else {
      **ie_ptr = (id[0] << 4) | (0 << 3) | mobile_id->type_of_id;
    }
    *ie_ptr += 1;

    // | Identity digit p+1 | Identity digit p |
    for (i = 0; i < 7; i++) {
      (*ie_ptr)[i] = (id[i * 2 + 2] << 4) | id[i * 2 + 1];
    }
    *ie_ptr += 7;
    if (!odd) {
      **ie_ptr = 0xF0 | id[15];
      *ie_ptr += 1;
    }

    err = LIBLTE_SUCCESS;
  } else {

    **ie_ptr = (0xFF << 4) | (0 << 3) | mobile_id->type_of_id;
    *ie_ptr += 1;
    // 4-Byte based ids
    **ie_ptr = (id32 >> 24) & 0xFF;
    *ie_ptr += 1;
    **ie_ptr = (id32 >> 16) & 0xFF;
    *ie_ptr += 1;
    **ie_ptr = (id32 >> 8) & 0xFF;
    *ie_ptr += 1;
    **ie_ptr = id32 & 0xFF;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_mobile_id_ie(uint8** ie_ptr, LIBLTE_MME_MOBILE_ID_STRUCT* mobile_id)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            id;
  uint32            length;
  uint32            i;
  bool              odd = false;

  if (ie_ptr != NULL && mobile_id != NULL) {
    length = **ie_ptr;
    *ie_ptr += 1;

    mobile_id->type_of_id = **ie_ptr & 0x07;

    if (LIBLTE_MME_MOBILE_ID_TYPE_IMSI == mobile_id->type_of_id) {
      id  = mobile_id->imsi;
      odd = true;
    } else if (LIBLTE_MME_MOBILE_ID_TYPE_IMEI == mobile_id->type_of_id) {
      id  = mobile_id->imei;
      odd = true;
    } else if (LIBLTE_MME_MOBILE_ID_TYPE_IMEISV == mobile_id->type_of_id) {
      id  = mobile_id->imeisv;
      odd = false;
    } else {
      // TODO: Not handling these IDs
      return (err);
    }

    id[0] = **ie_ptr >> 4;
    *ie_ptr += 1;
    for (i = 0; i < 7; i++) {
      id[i * 2 + 1] = (*ie_ptr)[i] & 0x0F;
      id[i * 2 + 2] = (*ie_ptr)[i] >> 4;
    }
    if (odd) {
      *ie_ptr += 7;
    } else {
      id[i * 2 + 1] = (*ie_ptr)[i] & 0xF;
      *ie_ptr += 8;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Mobile Station Classmark 2

    Description: Provides the network with information concerning
                 aspects of both high and low priority of the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.4
                        24.008 v10.2.0 Section 10.5.1.6
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_mobile_station_classmark_2_ie(LIBLTE_MME_MOBILE_STATION_CLASSMARK_2_STRUCT* ms_cm2,
                                                                uint8**                                       ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ms_cm2 != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = 3;
    (*ie_ptr)[1] = (ms_cm2->rev_lev & 0x03) << 5;
    (*ie_ptr)[1] |= ms_cm2->es_ind << 4;
    (*ie_ptr)[1] |= ms_cm2->a5_1 << 3;
    (*ie_ptr)[1] |= ms_cm2->rf_power_cap & 0x07;
    (*ie_ptr)[2] = ms_cm2->ps_cap << 6;
    (*ie_ptr)[2] |= (ms_cm2->ss_screen_ind & 0x03) << 4;
    (*ie_ptr)[2] |= ms_cm2->sm_cap << 3;
    (*ie_ptr)[2] |= ms_cm2->vbs << 2;
    (*ie_ptr)[2] |= ms_cm2->vgcs << 1;
    (*ie_ptr)[2] |= ms_cm2->fc;
    (*ie_ptr)[3] = ms_cm2->cm3 << 7;
    (*ie_ptr)[3] |= ms_cm2->lcsva_cap << 5;
    (*ie_ptr)[3] |= ms_cm2->ucs2 << 4;
    (*ie_ptr)[3] |= ms_cm2->solsa << 3;
    (*ie_ptr)[3] |= ms_cm2->cmsp << 2;
    (*ie_ptr)[3] |= ms_cm2->a5_3 << 1;
    (*ie_ptr)[3] |= ms_cm2->a5_2;
    *ie_ptr += 4;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_mobile_station_classmark_2_ie(uint8**                                       ie_ptr,
                                                                  LIBLTE_MME_MOBILE_STATION_CLASSMARK_2_STRUCT* ms_cm2)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && ms_cm2 != NULL) {
    ms_cm2->rev_lev       = (LIBLTE_MME_REVISION_LEVEL_ENUM)(((*ie_ptr)[1] >> 5) & 0x03);
    ms_cm2->es_ind        = ((*ie_ptr)[1] >> 4) & 0x01;
    ms_cm2->a5_1          = ((*ie_ptr)[1] >> 3) & 0x01;
    ms_cm2->rf_power_cap  = (LIBLTE_MME_RF_POWER_CAPABILITY_ENUM)((*ie_ptr)[1] & 0x07);
    ms_cm2->ps_cap        = ((*ie_ptr)[2] >> 6) & 0x01;
    ms_cm2->ss_screen_ind = (LIBLTE_MME_SS_SCREEN_INDICATOR_ENUM)(((*ie_ptr)[2] >> 4) & 0x03);
    ms_cm2->sm_cap        = ((*ie_ptr)[2] >> 3) & 0x01;
    ms_cm2->vbs           = ((*ie_ptr)[2] >> 2) & 0x01;
    ms_cm2->vgcs          = ((*ie_ptr)[2] >> 1) & 0x01;
    ms_cm2->fc            = (*ie_ptr)[2] & 0x01;
    ms_cm2->cm3           = ((*ie_ptr)[3] >> 7) & 0x01;
    ms_cm2->lcsva_cap     = ((*ie_ptr)[3] >> 5) & 0x01;
    ms_cm2->ucs2          = ((*ie_ptr)[3] >> 4) & 0x01;
    ms_cm2->solsa         = ((*ie_ptr)[3] >> 3) & 0x01;
    ms_cm2->cmsp          = ((*ie_ptr)[3] >> 2) & 0x01;
    ms_cm2->a5_3          = ((*ie_ptr)[3] >> 1) & 0x01;
    ms_cm2->a5_2          = (*ie_ptr)[3] & 0x01;
    *ie_ptr += 4;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Mobile Station Classmark 3

    Description: Provides the network with information concerning
                 aspects of the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.5
                        24.008 v10.2.0 Section 10.5.1.7
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_mobile_station_classmark_3_ie(LIBLTE_MME_MOBILE_STATION_CLASSMARK_3_STRUCT* ms_cm3,
                                                                uint8**                                       ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ms_cm3 != NULL && ie_ptr != NULL) {
    // TODO

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_mobile_station_classmark_3_ie(uint8**                                       ie_ptr,
                                                                  LIBLTE_MME_MOBILE_STATION_CLASSMARK_3_STRUCT* ms_cm3)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && ms_cm3 != NULL) {
    // TODO

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: NAS Security Parameters From E-UTRA

    Description: Provides the UE with information that enables the UE
                 to create a mapped UMTS security context.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.6
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_nas_security_parameters_from_eutra_ie(uint8 dl_nas_count, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = dl_nas_count & 0x0F;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_nas_security_parameters_from_eutra_ie(uint8** ie_ptr, uint8* dl_nas_count)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && dl_nas_count != NULL) {
    *dl_nas_count = (*ie_ptr)[0];
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: NAS Security Parameters To E-UTRA

    Description: Provides the UE with parameters that enables the UE
                 to create a mapped EPS security context and take
                 this context into use after inter-system handover to
                 S1 mode.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.7
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_nas_security_parameters_to_eutra_ie(LIBLTE_MME_NAS_SECURITY_PARAMETERS_TO_EUTRA_STRUCT* sec_params,
                                                    uint8**                                             ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (sec_params != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = (sec_params->nonce_mme >> 24) & 0xFF;
    (*ie_ptr)[1] = (sec_params->nonce_mme >> 16) & 0xFF;
    (*ie_ptr)[2] = (sec_params->nonce_mme >> 8) & 0xFF;
    (*ie_ptr)[3] = sec_params->nonce_mme & 0xFF;
    (*ie_ptr)[4] = (sec_params->eea & 0x07) << 4;
    (*ie_ptr)[4] |= sec_params->eia & 0x07;
    (*ie_ptr)[5] = (sec_params->tsc_flag & 0x01) << 3;
    (*ie_ptr)[5] |= sec_params->nas_ksi & 0x07;
    *ie_ptr += 6;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_nas_security_parameters_to_eutra_ie(uint8**                                             ie_ptr,
                                                      LIBLTE_MME_NAS_SECURITY_PARAMETERS_TO_EUTRA_STRUCT* sec_params)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && sec_params != NULL) {
    sec_params->nonce_mme = (*ie_ptr)[0] << 24;
    sec_params->nonce_mme |= (*ie_ptr)[1] << 16;
    sec_params->nonce_mme |= (*ie_ptr)[2] << 8;
    sec_params->nonce_mme |= (*ie_ptr)[3];
    sec_params->eea      = (LIBLTE_MME_TYPE_OF_CIPHERING_ALGORITHM_ENUM)(((*ie_ptr)[4] >> 4) & 0x07);
    sec_params->eia      = (LIBLTE_MME_TYPE_OF_INTEGRITY_ALGORITHM_ENUM)((*ie_ptr)[4] & 0x07);
    sec_params->tsc_flag = (LIBLTE_MME_TYPE_OF_SECURITY_CONTEXT_FLAG_ENUM)(((*ie_ptr)[5] >> 3) & 0x01);
    sec_params->nas_ksi  = (*ie_ptr)[5] & 0x07;
    *ie_ptr += 6;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: PLMN List

    Description: Provides a list of PLMN codes to the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.8
                        24.008 v10.2.0 Section 10.5.1.13
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_plmn_list_ie(LIBLTE_MME_PLMN_LIST_STRUCT* plmn_list, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (plmn_list != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = plmn_list->N_plmns * 3;
    for (i = 0; i < plmn_list->N_plmns; i++) {
      (*ie_ptr)[i * 3 + 0] = (((plmn_list->mcc[i] / 10) % 10) << 4) | ((plmn_list->mcc[i] / 100) % 10);
      if (plmn_list->mnc[i] < 100) {
        (*ie_ptr)[i * 3 + 1] = 0xF0 | (plmn_list->mcc[i] % 10);
        (*ie_ptr)[i * 3 + 2] = ((plmn_list->mnc[i] % 10) << 4) | ((plmn_list->mnc[i] / 10) % 10);
      } else {
        (*ie_ptr)[i * 3 + 1] = ((plmn_list->mnc[i] % 10) << 4) | (plmn_list->mcc[i] % 10);
        (*ie_ptr)[i * 3 + 2] = (((plmn_list->mnc[i] / 10) % 10) << 4) | ((plmn_list->mnc[i] / 100) % 10);
      }
    }
    *ie_ptr += (plmn_list->N_plmns * 3) + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_plmn_list_ie(uint8** ie_ptr, LIBLTE_MME_PLMN_LIST_STRUCT* plmn_list)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && plmn_list != NULL) {
    plmn_list->N_plmns = (*ie_ptr)[0] / 3;
    for (i = 0; i < plmn_list->N_plmns; i++) {
      plmn_list->mcc[i] = ((*ie_ptr)[i * 3 + 0] & 0x0F) * 100;
      plmn_list->mcc[i] += (((*ie_ptr)[i * 3 + 0] >> 4) & 0x0F) * 10;
      plmn_list->mcc[i] += (*ie_ptr)[i * 3 + 1] & 0x0F;
      if ((((*ie_ptr)[i * 3 + 1] >> 4) & 0x0F) == 0x0F) {
        plmn_list->mnc[i] = ((*ie_ptr)[i * 3 + 2] & 0x0F) * 10;
        plmn_list->mnc[i] += ((*ie_ptr)[i * 3 + 2] >> 4) & 0x0F;
      } else {
        plmn_list->mnc[i] = ((*ie_ptr)[i * 3 + 1] >> 4) & 0x0F;
        plmn_list->mnc[i] += ((*ie_ptr)[i * 3 + 2] & 0x0F) * 100;
        plmn_list->mnc[i] += (((*ie_ptr)[i * 3 + 2] >> 4) & 0x0F) * 10;
      }
    }
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Spare Half Octet

    Description: Used in the description of EMM and ESM messages when
                 an odd number of half octet type 1 information
                 elements are used.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.9
*********************************************************************/

/*********************************************************************
    IE Name: Supported Codec List

    Description: Provides the network with information about the
                 speech codecs supported by the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.2.10
                        24.008 v10.2.0 Section 10.5.4.32
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_supported_codec_list_ie(LIBLTE_MME_SUPPORTED_CODEC_LIST_STRUCT* supported_codec_list,
                                                          uint8**                                 ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (supported_codec_list != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = supported_codec_list->N_supported_codecs * 4;
    for (i = 0; i < supported_codec_list->N_supported_codecs; i++) {
      (*ie_ptr)[1 + i * 4 + 0] = supported_codec_list->supported_codec[i].sys_id;
      (*ie_ptr)[1 + i * 4 + 1] = 2;
      (*ie_ptr)[1 + i * 4 + 2] = (supported_codec_list->supported_codec[i].codec_bitmap >> 8) & 0xFF;
      (*ie_ptr)[1 + i * 4 + 3] = supported_codec_list->supported_codec[i].codec_bitmap & 0xFF;
    }
    *ie_ptr += (supported_codec_list->N_supported_codecs * 4) + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_supported_codec_list_ie(uint8** ie_ptr, LIBLTE_MME_SUPPORTED_CODEC_LIST_STRUCT* supported_codec_list)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && supported_codec_list != NULL) {
    supported_codec_list->N_supported_codecs = ((*ie_ptr)[0] / 4);
    for (i = 0; i < supported_codec_list->N_supported_codecs; i++) {
      supported_codec_list->supported_codec[i].sys_id       = (*ie_ptr)[1 + i * 4 + 0];
      supported_codec_list->supported_codec[i].codec_bitmap = (*ie_ptr)[1 + i * 4 + 2] << 8;
      supported_codec_list->supported_codec[i].codec_bitmap |= (*ie_ptr)[1 + i * 4 + 3];
    }
    *ie_ptr += (supported_codec_list->N_supported_codecs * 4) + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Additional Update Result

    Description: Provides additional information about the result of
                 a combined attached procedure or a combined tracking
                 area updating procedure.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.0A
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_additional_update_result_ie(LIBLTE_MME_ADDITIONAL_UPDATE_RESULT_ENUM result,
                                                              uint8                                    bit_offset,
                                                              uint8**                                  ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= result << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_additional_update_result_ie(uint8**                                   ie_ptr,
                                                                uint8                                     bit_offset,
                                                                LIBLTE_MME_ADDITIONAL_UPDATE_RESULT_ENUM* result)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (result != NULL && ie_ptr != NULL) {
    *result = (LIBLTE_MME_ADDITIONAL_UPDATE_RESULT_ENUM)((**ie_ptr >> bit_offset) & 0x03);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Additional Update Type

    Description: Provides additional information about the type of
                 request for a combined attach or a combined tracking
                 area updating procedure.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.0B
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_additional_update_type_ie(LIBLTE_MME_ADDITIONAL_UPDATE_TYPE_ENUM aut, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= aut << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_additional_update_type_ie(uint8**                                 ie_ptr,
                                                              uint8                                   bit_offset,
                                                              LIBLTE_MME_ADDITIONAL_UPDATE_TYPE_ENUM* aut)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && aut != NULL) {
    *aut = (LIBLTE_MME_ADDITIONAL_UPDATE_TYPE_ENUM)((**ie_ptr >> bit_offset) & 0x01);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Authentication Failure Parameter

    Description: Provides the network with the necessary information
                 to begin a re-authentication procedure in the case
                 of a 'Synch failure', following a UMTS or EPS
                 authentication challenge.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.1
                        24.008 v10.2.0 Section 10.5.3.2.2
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_failure_parameter_ie(uint8* auth_fail_param, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (auth_fail_param != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = 14;
    for (i = 0; i < 14; i++) {
      (*ie_ptr)[i + 1] = auth_fail_param[i];
    }
    *ie_ptr += 15;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_authentication_failure_parameter_ie(uint8** ie_ptr, uint8* auth_fail_param)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && auth_fail_param != NULL) {
    for (i = 0; i < 14; i++) {
      auth_fail_param[i] = (*ie_ptr)[i + 1];
    }
    *ie_ptr += 15;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Authentication Parameter AUTN

    Description: Provides the UE with a means of authenticating the
                 network.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.2
                        24.008 v10.2.0 Section 10.5.3.1.1
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_parameter_autn_ie(uint8* autn, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (autn != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = 16;
    for (i = 0; i < 16; i++) {
      (*ie_ptr)[i + 1] = autn[i];
    }
    *ie_ptr += 17;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_authentication_parameter_autn_ie(uint8** ie_ptr, uint8* autn)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && autn != NULL) {
    for (i = 0; i < 16; i++) {
      autn[i] = (*ie_ptr)[i + 1];
    }
    *ie_ptr += 17;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Authentication Parameter RAND

    Description: Provides the UE with a non-predictable number to be
                 used to calculate the authentication signature SRES
                 and the ciphering key Kc (for a GSM authentication
                 challenge), or the response RES and both the
                 ciphering key CK and the integrity key IK (for a
                 UMTS authentication challenge).

    Document Reference: 24.301 v10.2.0 Section 9.9.3.3
                        24.008 v10.2.0 Section 10.5.3.1
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_parameter_rand_ie(uint8* rand_val, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (rand_val != NULL && ie_ptr != NULL) {
    for (i = 0; i < 16; i++) {
      (*ie_ptr)[i] = rand_val[i];
    }
    *ie_ptr += 16;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_authentication_parameter_rand_ie(uint8** ie_ptr, uint8* rand_val)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && rand_val != NULL) {
    for (i = 0; i < 16; i++) {
      rand_val[i] = (*ie_ptr)[i];
    }
    *ie_ptr += 16;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Authentication Response Parameter

    Description: Provides the network with the authentication
                 response calculated in the USIM.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.4
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_response_parameter_ie(uint8* res, int res_len, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (res != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = res_len;
    *ie_ptr += 1;
    for (i = 0; i < res_len; i++) {
      (*ie_ptr)[i] = res[i];
    }
    *ie_ptr += res_len;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_authentication_response_parameter_ie(uint8** ie_ptr, uint8* res)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && res != NULL) {
    for (i = 0; i < (*ie_ptr)[0]; i++) {
      res[i] = (*ie_ptr)[i + 1];
    }
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Ciphering Key Sequence Number

    Description: Makes it possible for the network to identify the
                 ciphering key Kc which is stored in the UE without
                 invoking the authentication procedure.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.4A
                        24.008 v10.2.0 Section 10.5.1.2
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_ciphering_key_sequence_number_ie(uint8 key_seq, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] |= (key_seq & 0x07) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_ciphering_key_sequence_number_ie(uint8** ie_ptr, uint8 bit_offset, uint8* key_seq)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && key_seq != NULL) {
    *key_seq = ((*ie_ptr)[0] >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: CSFB Response

    Description: Indicates whether the UE accepts or rejects a paging
                 for CS fallback.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.5
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_csfb_response_ie(uint8 csfb_resp, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] |= (csfb_resp & 0x07) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_csfb_response_ie(uint8** ie_ptr, uint8 bit_offset, uint8* csfb_resp)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && csfb_resp != NULL) {
    *csfb_resp = ((*ie_ptr)[0] & 0x07) >> bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Daylight Saving Time

    Description: Encodes the daylight saving time in steps of 1 hour.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.6
                        24.008 v10.2.0 Section 10.5.3.12
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_daylight_saving_time_ie(LIBLTE_MME_DAYLIGHT_SAVING_TIME_ENUM dst, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = 1;
    (*ie_ptr)[1] = dst & 0x03;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_daylight_saving_time_ie(uint8** ie_ptr, LIBLTE_MME_DAYLIGHT_SAVING_TIME_ENUM* dst)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && dst != NULL) {
    *dst = (LIBLTE_MME_DAYLIGHT_SAVING_TIME_ENUM)((*ie_ptr)[1] & 0x03);
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Detach Type

    Description: Indicates the type of detach.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.7
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_detach_type_ie(LIBLTE_MME_DETACH_TYPE_STRUCT* detach_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (detach_type != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] |= (detach_type->switch_off & 0x01) << (3 + bit_offset);
    (*ie_ptr)[0] |= (detach_type->type_of_detach & 0x07) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_detach_type_ie(uint8** ie_ptr, uint8 bit_offset, LIBLTE_MME_DETACH_TYPE_STRUCT* detach_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && detach_type != NULL) {
    detach_type->switch_off     = ((*ie_ptr)[0] >> (3 + bit_offset)) & 0x01;
    detach_type->type_of_detach = ((*ie_ptr)[0] >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: DRX Parameter

    Description: Indicates whether the UE uses DRX mode or not.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.8
                        24.008 v10.2.0 Section 10.5.5.6
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_drx_parameter_ie(LIBLTE_MME_DRX_PARAMETER_STRUCT* drx_param, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (drx_param != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = drx_param->split_pg_cycle_code;
    (*ie_ptr)[1] = (drx_param->drx_cycle_len_coeff_and_value & 0x0F) << 4;
    (*ie_ptr)[1] |= (drx_param->split_on_ccch & 0x01) << 3;
    (*ie_ptr)[1] |= drx_param->non_drx_timer & 0x07;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_drx_parameter_ie(uint8** ie_ptr, LIBLTE_MME_DRX_PARAMETER_STRUCT* drx_param)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && drx_param != NULL) {
    drx_param->split_pg_cycle_code           = (*ie_ptr)[0];
    drx_param->drx_cycle_len_coeff_and_value = ((*ie_ptr)[1] >> 4) & 0x0F;
    drx_param->split_on_ccch                 = ((*ie_ptr)[1] >> 3) & 0x01;
    drx_param->non_drx_timer                 = (LIBLTE_MME_NON_DRX_TIMER_ENUM)((*ie_ptr)[1] & 0x07);
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EMM Cause

    Description: Indicates the reason why an EMM request from the UE
                 is rejected by the network.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.9
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_emm_cause_ie(uint8 emm_cause, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr = emm_cause;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_emm_cause_ie(uint8** ie_ptr, uint8* emm_cause)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && emm_cause != NULL) {
    *emm_cause = **ie_ptr;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Attach Result

    Description: Specifies the result of an attach procedure.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.10
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_eps_attach_result_ie(uint8 result, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= result << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_eps_attach_result_ie(uint8** ie_ptr, uint8 bit_offset, uint8* result)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && result != NULL) {
    *result = (**ie_ptr >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Attach Type

    Description: Indicates the type of the requested attach.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.11
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_eps_attach_type_ie(uint8 attach_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= attach_type << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_eps_attach_type_ie(uint8** ie_ptr, uint8 bit_offset, uint8* attach_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && attach_type != NULL) {
    *attach_type = (**ie_ptr >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Mobile Identity

    Description: Provides either the IMSI, the GUTI, or the IMEI.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.12
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_eps_mobile_id_ie(LIBLTE_MME_EPS_MOBILE_ID_STRUCT* eps_mobile_id, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            id;
  uint32            i;

  if (eps_mobile_id != NULL && ie_ptr != NULL) {
    if (LIBLTE_MME_EPS_MOBILE_ID_TYPE_GUTI == eps_mobile_id->type_of_id) {
      **ie_ptr = 11;
      *ie_ptr += 1;
      **ie_ptr = 0xF0 | eps_mobile_id->type_of_id;
      *ie_ptr += 1;
      **ie_ptr = (((eps_mobile_id->guti.mcc / 10) % 10) << 4) | ((eps_mobile_id->guti.mcc / 100) % 10);
      *ie_ptr += 1;
      if (eps_mobile_id->guti.mnc < 100) {
        **ie_ptr = 0xF0 | (eps_mobile_id->guti.mcc % 10);
        *ie_ptr += 1;
        **ie_ptr = ((eps_mobile_id->guti.mnc % 10) << 4) | ((eps_mobile_id->guti.mnc / 10) % 10);
        *ie_ptr += 1;
      } else {
        **ie_ptr = ((eps_mobile_id->guti.mnc % 10) << 4) | (eps_mobile_id->guti.mcc % 10);
        *ie_ptr += 1;
        **ie_ptr = (((eps_mobile_id->guti.mnc / 10) % 10) << 4) | ((eps_mobile_id->guti.mnc / 100) % 10);
        *ie_ptr += 1;
      }
      **ie_ptr = (eps_mobile_id->guti.mme_group_id >> 8) & 0xFF;
      *ie_ptr += 1;
      **ie_ptr = eps_mobile_id->guti.mme_group_id & 0xFF;
      *ie_ptr += 1;
      **ie_ptr = eps_mobile_id->guti.mme_code;
      *ie_ptr += 1;
      **ie_ptr = (eps_mobile_id->guti.m_tmsi >> 24) & 0xFF;
      *ie_ptr += 1;
      **ie_ptr = (eps_mobile_id->guti.m_tmsi >> 16) & 0xFF;
      *ie_ptr += 1;
      **ie_ptr = (eps_mobile_id->guti.m_tmsi >> 8) & 0xFF;
      *ie_ptr += 1;
      **ie_ptr = eps_mobile_id->guti.m_tmsi & 0xFF;
      *ie_ptr += 1;
    } else {
      if (LIBLTE_MME_EPS_MOBILE_ID_TYPE_IMSI == eps_mobile_id->type_of_id) {
        id = eps_mobile_id->imsi;
      } else {
        id = eps_mobile_id->imei;
      }

      **ie_ptr = 8;
      *ie_ptr += 1;
      **ie_ptr = (id[0] << 4) | (1 << 3) | eps_mobile_id->type_of_id;
      *ie_ptr += 1;
      for (i = 0; i < 7; i++) {
        **ie_ptr = (id[i * 2 + 2] << 4) | id[i * 2 + 1];
        *ie_ptr += 1;
      }
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_eps_mobile_id_ie(uint8** ie_ptr, LIBLTE_MME_EPS_MOBILE_ID_STRUCT* eps_mobile_id)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            id;
  uint32            length;
  uint32            i;

  if (ie_ptr != NULL && eps_mobile_id != NULL) {
    length = **ie_ptr;
    *ie_ptr += 1;

    eps_mobile_id->type_of_id = **ie_ptr & 0x07;

    if (LIBLTE_MME_EPS_MOBILE_ID_TYPE_GUTI == eps_mobile_id->type_of_id) {
      *ie_ptr += 1;
      eps_mobile_id->guti.mcc = (**ie_ptr & 0x0F) * 100;
      eps_mobile_id->guti.mcc += ((**ie_ptr >> 4) & 0x0F) * 10;
      *ie_ptr += 1;
      eps_mobile_id->guti.mcc += **ie_ptr & 0x0F;
      if (((**ie_ptr >> 4) & 0x0F) == 0x0F) {
        *ie_ptr += 1;
        eps_mobile_id->guti.mnc = (**ie_ptr & 0x0F) * 10;
        eps_mobile_id->guti.mnc += (**ie_ptr >> 4) & 0x0F;
        *ie_ptr += 1;
      } else {
        eps_mobile_id->guti.mnc = (**ie_ptr >> 4) & 0x0F;
        *ie_ptr += 1;
        eps_mobile_id->guti.mnc += (**ie_ptr & 0x0F) * 100;
        eps_mobile_id->guti.mnc += ((**ie_ptr >> 4) & 0x0F) * 10;
        *ie_ptr += 1;
      }
      eps_mobile_id->guti.mme_group_id = **ie_ptr << 8;
      *ie_ptr += 1;
      eps_mobile_id->guti.mme_group_id |= **ie_ptr;
      *ie_ptr += 1;
      eps_mobile_id->guti.mme_code = **ie_ptr;
      *ie_ptr += 1;
      eps_mobile_id->guti.m_tmsi = **ie_ptr << 24;
      *ie_ptr += 1;
      eps_mobile_id->guti.m_tmsi |= **ie_ptr << 16;
      *ie_ptr += 1;
      eps_mobile_id->guti.m_tmsi |= **ie_ptr << 8;
      *ie_ptr += 1;
      eps_mobile_id->guti.m_tmsi |= **ie_ptr;
      *ie_ptr += 1;
    } else {
      if (LIBLTE_MME_EPS_MOBILE_ID_TYPE_IMSI == eps_mobile_id->type_of_id) {
        id = eps_mobile_id->imsi;
      } else {
        id = eps_mobile_id->imei;
      }

      id[0] = **ie_ptr >> 4;
      *ie_ptr += 1;
      for (i = 0; i < 7; i++) {
        id[i * 2 + 1] = **ie_ptr & 0x0F;
        id[i * 2 + 2] = **ie_ptr >> 4;
        *ie_ptr += 1;
      }
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Network Feature Support

    Description: Indicates whether certain features are supported by
                 the network.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.12A
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_eps_network_feature_support_ie(LIBLTE_MME_EPS_NETWORK_FEATURE_SUPPORT_STRUCT* eps_nfs,
                                                                 uint8**                                        ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (eps_nfs != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = 1;
    (*ie_ptr)[1] = eps_nfs->esrps << 5;
    (*ie_ptr)[1] |= (eps_nfs->cs_lcs & 0x03) << 3;
    (*ie_ptr)[1] |= eps_nfs->epc_lcs << 2;
    (*ie_ptr)[1] |= eps_nfs->emc_bs << 1;
    (*ie_ptr)[1] |= eps_nfs->ims_vops;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_eps_network_feature_support_ie(uint8** ie_ptr, LIBLTE_MME_EPS_NETWORK_FEATURE_SUPPORT_STRUCT* eps_nfs)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && eps_nfs != NULL) {
    eps_nfs->esrps    = ((*ie_ptr)[1] >> 5) & 0x01;
    eps_nfs->cs_lcs   = (LIBLTE_MME_CS_LCS_ENUM)(((*ie_ptr)[1] >> 3) & 0x03);
    eps_nfs->epc_lcs  = ((*ie_ptr)[1] >> 2) & 0x01;
    eps_nfs->emc_bs   = ((*ie_ptr)[1] >> 1) & 0x01;
    eps_nfs->ims_vops = (*ie_ptr)[1] & 0x01;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Update Result

    Description: Specifies the result of the associated updating
                 procedure.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.13
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_eps_update_result_ie(uint8 eps_update_res, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] |= (eps_update_res & 0x07) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_eps_update_result_ie(uint8** ie_ptr, uint8 bit_offset, uint8* eps_update_res)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && eps_update_res != NULL) {
    *eps_update_res = ((*ie_ptr)[0] >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Update Type

    Description: Specifies the area the updating procedure is
                 associated with.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.14
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_eps_update_type_ie(LIBLTE_MME_EPS_UPDATE_TYPE_STRUCT* eps_update_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (eps_update_type != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] |= (eps_update_type->active_flag & 0x01) << (bit_offset + 3);
    (*ie_ptr)[0] |= (eps_update_type->type & 0x07) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_eps_update_type_ie(uint8**                            ie_ptr,
                                                       uint8                              bit_offset,
                                                       LIBLTE_MME_EPS_UPDATE_TYPE_STRUCT* eps_update_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && eps_update_type != NULL) {
    eps_update_type->active_flag = ((*ie_ptr)[0] >> (bit_offset + 3)) & 0x01;
    eps_update_type->type        = (LIBLTE_MME_EPS_UPDATE_TYPE_ENUM)(((*ie_ptr)[0] >> bit_offset) & 0x07);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: ESM Message Container

    Description: Enables piggybacked transfer of a single ESM message
                 within an EMM message.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.15
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_esm_message_container_ie(LIBLTE_BYTE_MSG_STRUCT* esm_msg, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (esm_msg != NULL && ie_ptr != NULL && esm_msg->N_bytes <= LIBLTE_MAX_MSG_SIZE_BYTES) {
    (*ie_ptr)[0] = esm_msg->N_bytes >> 8;
    (*ie_ptr)[1] = esm_msg->N_bytes & 0xFF;
    for (i = 0; i < esm_msg->N_bytes; i++) {
      (*ie_ptr)[2 + i] = esm_msg->msg[i];
    }
    *ie_ptr += esm_msg->N_bytes + 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_esm_message_container_ie(uint8** ie_ptr, LIBLTE_BYTE_MSG_STRUCT* esm_msg)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && esm_msg != NULL) {
    esm_msg->N_bytes = (*ie_ptr)[0] << 8;
    esm_msg->N_bytes |= (*ie_ptr)[1];

    if (esm_msg->N_bytes > LIBLTE_MAX_MSG_SIZE_BYTES) {
      return err;
    }

    for (i = 0; i < esm_msg->N_bytes; i++) {
      esm_msg->msg[i] = (*ie_ptr)[2 + i];
    }
    *ie_ptr += esm_msg->N_bytes + 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: GPRS Timer

    Description: Specifies GPRS specific timer values.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.16
                        24.008 v10.2.0 Section 10.5.7.3
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_gprs_timer_ie(LIBLTE_MME_GPRS_TIMER_STRUCT* timer, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (timer != NULL && ie_ptr != NULL) {
    **ie_ptr = ((timer->unit & 0x07) << 5) | (timer->value & 0x1F);
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_gprs_timer_ie(uint8** ie_ptr, LIBLTE_MME_GPRS_TIMER_STRUCT* timer)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && timer != NULL) {
    timer->unit  = **ie_ptr >> 5;
    timer->value = **ie_ptr & 0x1F;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: GPRS Timer 2

    Description: Specifies GPRS specific timer values.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.16A
                        24.008 v10.2.0 Section 10.5.7.4
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_gprs_timer_2_ie(uint8 value, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr = 1;
    *ie_ptr += 1;
    **ie_ptr = value;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_gprs_timer_2_ie(uint8** ie_ptr, uint8* value)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && value != NULL) {
    *ie_ptr += 1;
    *value = **ie_ptr;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: GPRS Timer 3

    Description: Specifies GPRS specific timer values.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.16B
                        24.008 v10.2.0 Section 10.5.7.4A
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_gprs_timer_3_ie(LIBLTE_MME_GPRS_TIMER_3_STRUCT* timer, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (timer != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = 1;
    (*ie_ptr)[1] = ((timer->unit & 0x07) << 5) | (timer->value & 0x1F);
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_gprs_timer_3_ie(uint8** ie_ptr, LIBLTE_MME_GPRS_TIMER_3_STRUCT* timer)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && timer != NULL) {
    timer->unit  = (*ie_ptr)[1] >> 5;
    timer->value = (*ie_ptr)[1] & 0x1F;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Identity Type 2

    Description: Specifies which identity is requested.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.17
                        24.008 v10.2.0 Section 10.5.5.9
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_identity_type_2_ie(uint8 id_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= id_type << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_identity_type_2_ie(uint8** ie_ptr, uint8 bit_offset, uint8* id_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && id_type != NULL) {
    *id_type = (**ie_ptr >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: IMEISV Request

    Description: Indicates that the IMEISV shall be included by the
                 UE in the authentication and ciphering response
                 message.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.18
                        24.008 v10.2.0 Section 10.5.5.10
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_imeisv_request_ie(LIBLTE_MME_IMEISV_REQUEST_ENUM imeisv_req, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= imeisv_req << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_imeisv_request_ie(uint8** ie_ptr, uint8 bit_offset, LIBLTE_MME_IMEISV_REQUEST_ENUM* imeisv_req)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && imeisv_req != NULL) {
    *imeisv_req = (LIBLTE_MME_IMEISV_REQUEST_ENUM)((**ie_ptr >> bit_offset) & 0x07);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: KSI And Sequence Number

    Description: Provides the network with the key set identifier
                 (KSI) value of the current EPS security context and
                 the 5 least significant bits of the NAS COUNT value
                 applicable for the message including this information
                 element.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.19
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_ksi_and_sequence_number_ie(LIBLTE_MME_KSI_AND_SEQUENCE_NUMBER_STRUCT* ksi_and_seq_num,
                                                             uint8**                                    ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ksi_and_seq_num != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = (ksi_and_seq_num->ksi & 0x07) << 5;
    (*ie_ptr)[0] |= ksi_and_seq_num->seq_num & 0x1F;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_ksi_and_sequence_number_ie(uint8** ie_ptr, LIBLTE_MME_KSI_AND_SEQUENCE_NUMBER_STRUCT* ksi_and_seq_num)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && ksi_and_seq_num != NULL) {
    ksi_and_seq_num->ksi     = ((*ie_ptr)[0] >> 5) & 0x07;
    ksi_and_seq_num->seq_num = (*ie_ptr)[0] & 0x1F;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: MS Network Capability

    Description: Provides the network with information concerning
                 aspects of the UE related to GPRS.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.20
                        24.008 v10.2.0 Section 10.5.5.12
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_ms_network_capability_ie(LIBLTE_MME_MS_NETWORK_CAPABILITY_STRUCT* ms_network_cap,
                                                           uint8**                                  ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ms_network_cap != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = 3;
    (*ie_ptr)[1] = ms_network_cap->gea[1] << 7;
    (*ie_ptr)[1] |= ms_network_cap->sm_cap_ded << 6;
    (*ie_ptr)[1] |= ms_network_cap->sm_cap_gprs << 5;
    (*ie_ptr)[1] |= ms_network_cap->ucs2 << 4;
    (*ie_ptr)[1] |= (ms_network_cap->ss_screening & 0x03) << 2;
    (*ie_ptr)[1] |= ms_network_cap->solsa << 1;
    (*ie_ptr)[1] |= ms_network_cap->revision;
    (*ie_ptr)[2] = ms_network_cap->pfc << 7;
    (*ie_ptr)[2] |= ms_network_cap->gea[2] << 6;
    (*ie_ptr)[2] |= ms_network_cap->gea[3] << 5;
    (*ie_ptr)[2] |= ms_network_cap->gea[4] << 4;
    (*ie_ptr)[2] |= ms_network_cap->gea[5] << 3;
    (*ie_ptr)[2] |= ms_network_cap->gea[6] << 2;
    (*ie_ptr)[2] |= ms_network_cap->gea[7] << 1;
    (*ie_ptr)[2] |= ms_network_cap->lcsva;
    (*ie_ptr)[3] = ms_network_cap->ho_g2u_via_iu << 7;
    (*ie_ptr)[3] |= ms_network_cap->ho_g2e_via_s1 << 6;
    (*ie_ptr)[3] |= ms_network_cap->emm_comb << 5;
    (*ie_ptr)[3] |= ms_network_cap->isr << 4;
    (*ie_ptr)[3] |= ms_network_cap->srvcc << 3;
    (*ie_ptr)[3] |= ms_network_cap->epc << 2;
    (*ie_ptr)[3] |= ms_network_cap->nf << 1;
    *ie_ptr += 4;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_ms_network_capability_ie(uint8**                                  ie_ptr,
                                                             LIBLTE_MME_MS_NETWORK_CAPABILITY_STRUCT* ms_network_cap)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && ms_network_cap != NULL) {
    ms_network_cap->gea[1]        = ((*ie_ptr)[1] >> 7) & 0x01;
    ms_network_cap->sm_cap_ded    = ((*ie_ptr)[1] >> 6) & 0x01;
    ms_network_cap->sm_cap_gprs   = ((*ie_ptr)[1] >> 5) & 0x01;
    ms_network_cap->ucs2          = ((*ie_ptr)[1] >> 4) & 0x01;
    ms_network_cap->ss_screening  = (LIBLTE_MME_SS_SCREENING_INDICATOR_ENUM)(((*ie_ptr)[1] >> 2) & 0x03);
    ms_network_cap->solsa         = ((*ie_ptr)[1] >> 1) & 0x01;
    ms_network_cap->revision      = (*ie_ptr)[1] & 0x01;
    ms_network_cap->pfc           = ((*ie_ptr)[2] >> 7) & 0x01;
    ms_network_cap->gea[2]        = ((*ie_ptr)[2] >> 6) & 0x01;
    ms_network_cap->gea[3]        = ((*ie_ptr)[2] >> 5) & 0x01;
    ms_network_cap->gea[4]        = ((*ie_ptr)[2] >> 4) & 0x01;
    ms_network_cap->gea[5]        = ((*ie_ptr)[2] >> 3) & 0x01;
    ms_network_cap->gea[6]        = ((*ie_ptr)[2] >> 2) & 0x01;
    ms_network_cap->gea[7]        = ((*ie_ptr)[2] >> 1) & 0x01;
    ms_network_cap->lcsva         = (*ie_ptr)[2] & 0x01;
    ms_network_cap->ho_g2u_via_iu = ((*ie_ptr)[3] >> 7) & 0x01;
    ms_network_cap->ho_g2e_via_s1 = ((*ie_ptr)[3] >> 6) & 0x01;
    ms_network_cap->emm_comb      = ((*ie_ptr)[3] >> 5) & 0x01;
    ms_network_cap->isr           = ((*ie_ptr)[3] >> 4) & 0x01;
    ms_network_cap->srvcc         = ((*ie_ptr)[3] >> 3) & 0x01;
    ms_network_cap->epc           = ((*ie_ptr)[3] >> 2) & 0x01;
    ms_network_cap->nf            = ((*ie_ptr)[3] >> 1) & 0x01;
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: NAS Key Set Identifier

    Description: Provides the NAS key set identifier that is allocated
                 by the network.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.21
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_nas_key_set_id_ie(LIBLTE_MME_NAS_KEY_SET_ID_STRUCT* nas_ksi, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (nas_ksi != NULL && ie_ptr != NULL) {
    **ie_ptr |= nas_ksi->tsc_flag << (bit_offset + 3);
    **ie_ptr |= nas_ksi->nas_ksi << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_nas_key_set_id_ie(uint8** ie_ptr, uint8 bit_offset, LIBLTE_MME_NAS_KEY_SET_ID_STRUCT* nas_ksi)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && nas_ksi != NULL) {
    nas_ksi->tsc_flag = (LIBLTE_MME_TYPE_OF_SECURITY_CONTEXT_FLAG_ENUM)((**ie_ptr >> (bit_offset + 3)) & 0x01);
    nas_ksi->nas_ksi  = (**ie_ptr >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: NAS Message Container

    Description: Encapsulates the SMS messages transferred between
                 the UE and the network.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.22
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_nas_message_container_ie(LIBLTE_BYTE_MSG_STRUCT* nas_msg, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (nas_msg != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = nas_msg->N_bytes & 0xFF;
    for (i = 0; i < nas_msg->N_bytes; i++) {
      (*ie_ptr)[1 + i] = nas_msg->msg[i];
    }
    *ie_ptr += nas_msg->N_bytes + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_nas_message_container_ie(uint8** ie_ptr, LIBLTE_BYTE_MSG_STRUCT* nas_msg)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && nas_msg != NULL) {
    nas_msg->N_bytes = (*ie_ptr)[0];
    for (i = 0; i < nas_msg->N_bytes; i++) {
      nas_msg->msg[i] = (*ie_ptr)[1 + i];
    }
    *ie_ptr += nas_msg->N_bytes + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: NAS Security Algorithms

    Description: Indicates the algorithms to be used for ciphering
                 and integrity protection.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.23
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_nas_security_algorithms_ie(LIBLTE_MME_NAS_SECURITY_ALGORITHMS_STRUCT* nas_sec_algs,
                                                             uint8**                                    ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (nas_sec_algs != NULL && ie_ptr != NULL) {
    **ie_ptr = (nas_sec_algs->type_of_eea << 4) | (nas_sec_algs->type_of_eia);
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_nas_security_algorithms_ie(uint8**                                    ie_ptr,
                                                               LIBLTE_MME_NAS_SECURITY_ALGORITHMS_STRUCT* nas_sec_algs)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && nas_sec_algs != NULL) {
    nas_sec_algs->type_of_eea = (LIBLTE_MME_TYPE_OF_CIPHERING_ALGORITHM_ENUM)((**ie_ptr >> 4) & 0x07);
    nas_sec_algs->type_of_eia = (LIBLTE_MME_TYPE_OF_INTEGRITY_ALGORITHM_ENUM)(**ie_ptr & 0x07);
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Network Name

    Description: Passes a text string to the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.24
                        24.008 v10.2.0 Section 10.5.3.5A
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_network_name_ie(LIBLTE_MME_NETWORK_NAME_STRUCT* net_name, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;
  uint32            bit_offset;
  uint32            byte_offset;
  const char*       char_str = net_name->name;

  if (net_name != NULL && ie_ptr != NULL) {
    bit_offset  = 0;
    byte_offset = 2;
    for (i = 0; i < strnlen(char_str, LIBLTE_STRING_LEN); i++) {
      if (char_str[i] == 0x0A || char_str[i] == 0x0D || (char_str[i] >= 0x20 && char_str[i] <= 0x3F) ||
          (char_str[i] >= 0x41 && char_str[i] <= 0x5A) || (char_str[i] >= 0x61 && char_str[i] <= 0x7A)) {
        switch (bit_offset) {
          case 0:
            (*ie_ptr)[byte_offset] = char_str[i];
            bit_offset             = 7;
            break;
          case 1:
            (*ie_ptr)[byte_offset] |= (char_str[i] << 1);
            bit_offset = 0;
            byte_offset++;
            break;
          case 2:
            (*ie_ptr)[byte_offset] |= ((char_str[i] << 2) & 0xFC);
            byte_offset++;
            (*ie_ptr)[byte_offset] = ((char_str[i] >> 6) & 0x01);
            bit_offset             = 1;
            break;
          case 3:
            (*ie_ptr)[byte_offset] |= ((char_str[i] << 3) & 0xF8);
            byte_offset++;
            (*ie_ptr)[byte_offset] = ((char_str[i] >> 5) & 0x03);
            bit_offset             = 2;
            break;
          case 4:
            (*ie_ptr)[byte_offset] |= ((char_str[i] << 4) & 0xF0);
            byte_offset++;
            (*ie_ptr)[byte_offset] = ((char_str[i] >> 4) & 0x07);
            bit_offset             = 3;
            break;
          case 5:
            (*ie_ptr)[byte_offset] |= ((char_str[i] << 5) & 0xE0);
            byte_offset++;
            (*ie_ptr)[byte_offset] = ((char_str[i] >> 3) & 0x0F);
            bit_offset             = 4;
            break;
          case 6:
            (*ie_ptr)[byte_offset] |= ((char_str[i] << 6) & 0xC0);
            byte_offset++;
            (*ie_ptr)[byte_offset] = ((char_str[i] >> 2) & 0x1F);
            bit_offset             = 5;
            break;
          case 7:
            (*ie_ptr)[byte_offset] |= ((char_str[i] << 7) & 0x80);
            byte_offset++;
            (*ie_ptr)[byte_offset] = ((char_str[i] >> 1) & 0x3F);
            bit_offset             = 6;
            break;
        }
      }
    }
    if (0 == bit_offset) {
      (*ie_ptr)[0] = byte_offset - 1;
      (*ie_ptr)[1] = 0x80 | ((net_name->add_ci & 0x01) << 3);
      *ie_ptr += byte_offset;
    } else {
      (*ie_ptr)[0] = byte_offset;
      (*ie_ptr)[1] = 0x80 | ((net_name->add_ci & 0x01) << 3) | ((8 - bit_offset) & 0x07);
      *ie_ptr += byte_offset + 1;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_network_name_ie(uint8** ie_ptr, LIBLTE_MME_NETWORK_NAME_STRUCT* net_name)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;
  uint32            bit_offset;
  uint32            byte_offset;
  uint32            N_bytes;
  uint8             spare_field;
  char              tmp_char;
  uint32            str_cnt;

  if (ie_ptr != NULL && net_name != NULL) {
    net_name->add_ci = (LIBLTE_MME_ADD_CI_ENUM)(((*ie_ptr)[1] >> 3) & 0x01);
    spare_field      = (*ie_ptr)[1] & 0x07;
    N_bytes          = (*ie_ptr)[0];
    bit_offset       = 0;
    byte_offset      = 2;
    str_cnt          = 0;

    while (byte_offset < N_bytes && str_cnt < LIBLTE_STRING_LEN) {
      switch (bit_offset) {
        case 0:
          tmp_char   = (*ie_ptr)[byte_offset] & 0x7F;
          bit_offset = 7;
          break;
        case 1:
          tmp_char   = ((*ie_ptr)[byte_offset] >> 1) & 0x7F;
          bit_offset = 0;
          byte_offset++;
          break;
        case 2:
          tmp_char = ((*ie_ptr)[byte_offset] >> 2) & 0x3F;
          byte_offset++;
          tmp_char |= ((*ie_ptr)[byte_offset] << 6) & 0x40;
          bit_offset = 1;
          break;
        case 3:
          tmp_char = ((*ie_ptr)[byte_offset] >> 3) & 0x1F;
          byte_offset++;
          tmp_char |= ((*ie_ptr)[byte_offset] << 5) & 0x60;
          bit_offset = 2;
          break;
        case 4:
          tmp_char = ((*ie_ptr)[byte_offset] >> 4) & 0x0F;
          byte_offset++;
          tmp_char |= ((*ie_ptr)[byte_offset] << 4) & 0x70;
          bit_offset = 3;
          break;
        case 5:
          tmp_char = ((*ie_ptr)[byte_offset] >> 5) & 0x07;
          byte_offset++;
          tmp_char |= ((*ie_ptr)[byte_offset] << 3) & 0x78;
          bit_offset = 4;
          break;
        case 6:
          tmp_char = ((*ie_ptr)[byte_offset] >> 6) & 0x03;
          byte_offset++;
          tmp_char |= ((*ie_ptr)[byte_offset] << 2) & 0x7C;
          bit_offset = 5;
          break;
        case 7:
          tmp_char = ((*ie_ptr)[byte_offset] >> 7) & 0x01;
          byte_offset++;
          tmp_char |= ((*ie_ptr)[byte_offset] << 1) & 0x7E;
          bit_offset = 6;
          break;
      }

      if (tmp_char == 0x0A || tmp_char == 0x0D || (tmp_char >= 0x20 && tmp_char <= 0x3F) ||
          (tmp_char >= 0x41 && tmp_char <= 0x5A) || (tmp_char >= 0x61 && tmp_char <= 0x7A)) {
        if (str_cnt < LIBLTE_STRING_LEN) {
          net_name->name[str_cnt] = tmp_char;
          str_cnt++;
        }
      }
    }

    if (0 == bit_offset || (1 == bit_offset && 0 == spare_field)) {
      if (0 == bit_offset) {
        tmp_char = (*ie_ptr)[byte_offset] & 0x7F;
      } else {
        tmp_char = ((*ie_ptr)[byte_offset] >> 1) & 0x7F;
      }
      if (tmp_char == 0x0A || tmp_char == 0x0D || (tmp_char >= 0x20 && tmp_char <= 0x3F) ||
          (tmp_char >= 0x41 && tmp_char <= 0x5A) || (tmp_char >= 0x61 && tmp_char <= 0x7A)) {
        if (str_cnt < LIBLTE_STRING_LEN) {
          net_name->name[str_cnt] = tmp_char;
          str_cnt++;
        }
      }
    }

    if (str_cnt < LIBLTE_STRING_LEN) {
      net_name->name[str_cnt] = '\0';
      str_cnt++;
    }

    *ie_ptr += byte_offset + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Nonce

    Description: Transfers a 32-bit nonce value to support deriving
                 a new mapped EPS security context.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.25
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_nonce_ie(uint32 nonce, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = (nonce >> 24) & 0xFF;
    (*ie_ptr)[1] = (nonce >> 16) & 0xFF;
    (*ie_ptr)[2] = (nonce >> 8) & 0xFF;
    (*ie_ptr)[3] = nonce & 0xFF;
    *ie_ptr += 4;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_nonce_ie(uint8** ie_ptr, uint32* nonce)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && nonce != NULL) {
    *nonce = (*ie_ptr)[0] << 24;
    *nonce |= (*ie_ptr)[1] << 16;
    *nonce |= (*ie_ptr)[2] << 8;
    *nonce |= (*ie_ptr)[3];
    *ie_ptr += 4;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Paging Identity

    Description: Indicates the identity used for paging for non-EPS
                 services.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.25A
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_paging_identity_ie(uint8 paging_id, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = paging_id & 0x01;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_paging_identity_ie(uint8** ie_ptr, uint8* paging_id)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && paging_id != NULL) {
    *paging_id = (*ie_ptr)[0] & 0x01;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: P-TMSI Signature

    Description: Identifies a GMM context of a UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.26
                        24.008 v10.2.0 Section 10.5.5.8
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_p_tmsi_signature_ie(uint32 p_tmsi_signature, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = (p_tmsi_signature >> 24) & 0xFF;
    (*ie_ptr)[1] = (p_tmsi_signature >> 16) & 0xFF;
    (*ie_ptr)[2] = (p_tmsi_signature >> 8) & 0xFF;
    (*ie_ptr)[3] = p_tmsi_signature & 0xFF;
    *ie_ptr += 4;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_p_tmsi_signature_ie(uint8** ie_ptr, uint32* p_tmsi_signature)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && p_tmsi_signature != NULL) {
    *p_tmsi_signature = (*ie_ptr)[0] << 24;
    *p_tmsi_signature |= (*ie_ptr)[1] << 16;
    *p_tmsi_signature |= (*ie_ptr)[2] << 8;
    *p_tmsi_signature |= (*ie_ptr)[3];
    *ie_ptr += 4;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Service Type

    Description: Specifies the purpose of the service request
                 procedure.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.27
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_service_type_ie(uint8 value, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] |= (value & 0x0F) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_service_type_ie(uint8** ie_ptr, uint8 bit_offset, uint8* value)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && value != NULL) {
    *value = ((*ie_ptr)[0] >> bit_offset) & 0x0F;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Short MAC

    Description: Protects the integrity of a SERVICE REQUEST message.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.28
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_short_mac_ie(uint16 short_mac, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = (short_mac >> 8) & 0xFF;
    (*ie_ptr)[1] = short_mac & 0xFF;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_short_mac_ie(uint8** ie_ptr, uint16* short_mac)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && short_mac != NULL) {
    *short_mac = (*ie_ptr)[0] << 8;
    *short_mac |= (*ie_ptr)[1];
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Time Zone

    Description: Encodes the offset between universal time and local
                 time in steps of 15 minutes.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.29
                        24.008 v10.2.0 Section 10.5.3.8
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_time_zone_ie(uint8 tz, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = tz;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_time_zone_ie(uint8** ie_ptr, uint8* tz)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && tz != NULL) {
    *tz = (*ie_ptr)[0];
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Time Zone And Time

    Description: Encodes the offset between universal time and local
                 time in steps of 15 minutes and encodes the universal
                 time at which the IE may have been sent by the
                 network.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.30
                        24.008 v10.2.0 Section 10.5.3.9
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_time_zone_and_time_ie(LIBLTE_MME_TIME_ZONE_AND_TIME_STRUCT* ttz, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ttz != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = ((ttz->year % 10) << 4) | ((ttz->year % 100) / 10);
    (*ie_ptr)[1] = ((ttz->month % 10) << 4) | (ttz->month / 10);
    (*ie_ptr)[2] = ((ttz->day % 10) << 4) | (ttz->day / 10);
    (*ie_ptr)[3] = ((ttz->hour % 10) << 4) | (ttz->hour / 10);
    (*ie_ptr)[4] = ((ttz->minute % 10) << 4) | (ttz->minute / 10);
    (*ie_ptr)[5] = ((ttz->second % 10) << 4) | (ttz->second / 10);
    (*ie_ptr)[6] = ttz->tz;
    *ie_ptr += 7;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_time_zone_and_time_ie(uint8** ie_ptr, LIBLTE_MME_TIME_ZONE_AND_TIME_STRUCT* ttz)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && ttz != NULL) {
    ttz->year   = 2000 + (((*ie_ptr)[0] & 0x0F) * 10) + (((*ie_ptr)[0] >> 4) & 0x0F);
    ttz->month  = (((*ie_ptr)[1] & 0x0F) * 10) + (((*ie_ptr)[1] >> 4) & 0x0F);
    ttz->day    = (((*ie_ptr)[2] & 0x0F) * 10) + (((*ie_ptr)[2] >> 4) & 0x0F);
    ttz->hour   = (((*ie_ptr)[3] & 0x0F) * 10) + (((*ie_ptr)[3] >> 4) & 0x0F);
    ttz->minute = (((*ie_ptr)[4] & 0x0F) * 10) + (((*ie_ptr)[4] >> 4) & 0x0F);
    ttz->second = (((*ie_ptr)[5] & 0x0F) * 10) + (((*ie_ptr)[5] >> 4) & 0x0F);
    ttz->tz     = (*ie_ptr)[6];
    *ie_ptr += 7;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: TMSI Status

    Description: Indicates whether a valid TMSI is available in the
                 UE or not.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.31
                        24.008 v10.2.0 Section 10.5.5.4
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_tmsi_status_ie(LIBLTE_MME_TMSI_STATUS_ENUM tmsi_status, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= tmsi_status << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_tmsi_status_ie(uint8** ie_ptr, uint8 bit_offset, LIBLTE_MME_TMSI_STATUS_ENUM* tmsi_status)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && tmsi_status != NULL) {
    *tmsi_status = (LIBLTE_MME_TMSI_STATUS_ENUM)((**ie_ptr >> bit_offset) & 0x01);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Tracking Area Identity

    Description: Provides an unambiguous identification of tracking
                 areas within the area covered by the 3GPP system.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.32
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_tracking_area_id_ie(LIBLTE_MME_TRACKING_AREA_ID_STRUCT* tai, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (tai != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = (((tai->mcc / 10) % 10) << 4) | ((tai->mcc / 100) % 10);
    if (tai->mnc < 100) {
      (*ie_ptr)[1] = 0xF0 | (tai->mcc % 10);
      (*ie_ptr)[2] = ((tai->mnc % 10) << 4) | ((tai->mnc / 10) % 10);
    } else {
      (*ie_ptr)[1] = ((tai->mnc % 10) << 4) | (tai->mcc % 10);
      (*ie_ptr)[2] = (((tai->mnc / 10) % 10) << 4) | ((tai->mnc / 100) % 10);
    }
    (*ie_ptr)[3] = (tai->tac >> 8) & 0xFF;
    (*ie_ptr)[4] = tai->tac & 0xFF;
    *ie_ptr += 5;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_tracking_area_id_ie(uint8** ie_ptr, LIBLTE_MME_TRACKING_AREA_ID_STRUCT* tai)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && tai != NULL) {
    tai->mcc = ((*ie_ptr)[0] & 0x0F) * 100;
    tai->mcc += (((*ie_ptr)[0] >> 4) & 0x0F) * 10;
    tai->mcc += (*ie_ptr)[1] & 0x0F;
    if ((((*ie_ptr)[1] >> 4) & 0x0F) == 0x0F) {
      tai->mnc = ((*ie_ptr)[2] & 0x0F) * 10;
      tai->mnc += ((*ie_ptr)[2] >> 4) & 0x0F;
    } else {
      tai->mnc = ((*ie_ptr)[1] >> 4) & 0x0F;
      tai->mnc += ((*ie_ptr)[2] & 0x0F) * 100;
      tai->mnc += (((*ie_ptr)[2] >> 4) & 0x0F) * 10;
    }
    tai->tac = (*ie_ptr)[3] << 8;
    tai->tac |= (*ie_ptr)[4];
    *ie_ptr += 5;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Tracking Area Identity List

    Description: Transfers a list of tracking areas from the network
                 to the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.33
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_tracking_area_identity_list_ie(LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_STRUCT* tai_list, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (tai_list != NULL && ie_ptr != NULL) {
    (*ie_ptr)[0] = (tai_list->N_tais * 5) + 1;
    // TODO: Support all types
    if (1 == tai_list->N_tais) {
      (*ie_ptr)[1] = (LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_TYPE_ONE_PLMN_NON_CONSECUTIVE_TACS << 5) |
                     ((tai_list->N_tais - 1) & 0x1F);
    } else {
      (*ie_ptr)[1] =
          (LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_TYPE_DIFFERENT_PLMNS << 5) | ((tai_list->N_tais - 1) & 0x1F);
    }
    *ie_ptr += 2;
    for (i = 0; i < tai_list->N_tais; i++) {
      liblte_mme_pack_tracking_area_id_ie(&tai_list->tai[i], ie_ptr);
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_tracking_area_identity_list_ie(uint8**                                        ie_ptr,
                                                 LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_STRUCT* tai_list)
{
  LIBLTE_ERROR_ENUM                                err = LIBLTE_ERROR_INVALID_INPUTS;
  LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_TYPE_ENUM type;
  uint32                                           sent_length;
  uint32                                           length;
  uint32                                           i;
  uint32                                           N_elems;
  uint16                                           mcc;
  uint16                                           mnc;
  uint16                                           tac;

  if (ie_ptr != NULL && tai_list != NULL) {
    sent_length      = (*ie_ptr)[0] + 1;
    length           = 1;
    tai_list->N_tais = 0;
    while (length < sent_length) {
      type    = (LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_TYPE_ENUM)(((*ie_ptr)[length] >> 5) & 0x03);
      N_elems = (*ie_ptr)[length++] & 0x1F;
      if (LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_TYPE_ONE_PLMN_NON_CONSECUTIVE_TACS == type) {
        mcc = ((*ie_ptr)[length] & 0x0F) * 100;
        mcc += (((*ie_ptr)[length++] >> 4) & 0x0F) * 10;
        mcc += (*ie_ptr)[length] & 0x0F;
        if ((((*ie_ptr)[length] >> 4) & 0x0F) == 0x0F) {
          length++;
          mnc = ((*ie_ptr)[length] & 0x0F) * 10;
          mnc += ((*ie_ptr)[length++] >> 4) & 0x0F;
        } else {
          mnc = ((*ie_ptr)[length++] >> 4) & 0x0F;
          mnc += ((*ie_ptr)[length] & 0x0F) * 100;
          mnc += (((*ie_ptr)[length++] >> 4) & 0x0F) * 10;
        }
        for (i = 0; i < N_elems; i++) {
          tai_list->tai[tai_list->N_tais].mcc = mcc;
          tai_list->tai[tai_list->N_tais].mnc = mnc;
          tai_list->tai[tai_list->N_tais].tac = (*ie_ptr)[length++] << 8;
          tai_list->tai[tai_list->N_tais].tac |= (*ie_ptr)[length++];
          tai_list->N_tais++;
        }
      } else if (LIBLTE_MME_TRACKING_AREA_IDENTITY_LIST_TYPE_ONE_PLMN_CONSECUTIVE_TACS == type) {
        mcc = ((*ie_ptr)[length] & 0x0F) * 100;
        mcc += (((*ie_ptr)[length++] >> 4) & 0x0F) * 10;
        mcc += (*ie_ptr)[length] & 0x0F;
        if ((((*ie_ptr)[length] >> 4) & 0x0F) == 0x0F) {
          length++;
          mnc = ((*ie_ptr)[length] & 0x0F) * 10;
          mnc += ((*ie_ptr)[length++] >> 4) & 0x0F;
        } else {
          mnc = ((*ie_ptr)[length++] >> 4) & 0x0F;
          mnc += ((*ie_ptr)[length] & 0x0F) * 100;
          mnc += (((*ie_ptr)[length++] >> 4) & 0x0F) * 10;
        }
        tac = (*ie_ptr)[length++] << 8;
        tac |= (*ie_ptr)[length++];
        for (i = 0; i < N_elems; i++) {
          tai_list->tai[tai_list->N_tais].mcc = mcc;
          tai_list->tai[tai_list->N_tais].mnc = mnc;
          tai_list->tai[tai_list->N_tais].tac = tac + i;
          tai_list->N_tais++;
        }
      } else {
        for (i = 0; i < N_elems; i++) {
          tai_list->tai[tai_list->N_tais].mcc = ((*ie_ptr)[length] & 0x0F) * 100;
          tai_list->tai[tai_list->N_tais].mcc += (((*ie_ptr)[length++] >> 4) & 0x0F) * 10;
          tai_list->tai[tai_list->N_tais].mcc += (*ie_ptr)[length] & 0x0F;
          if ((((*ie_ptr)[length] >> 4) & 0x0F) == 0x0F) {
            length++;
            tai_list->tai[tai_list->N_tais].mnc = ((*ie_ptr)[length] & 0x0F) * 10;
            tai_list->tai[tai_list->N_tais].mnc += ((*ie_ptr)[length++] >> 4) & 0x0F;
          } else {
            tai_list->tai[tai_list->N_tais].mnc = ((*ie_ptr)[length++] >> 4) & 0x0F;
            tai_list->tai[tai_list->N_tais].mnc += ((*ie_ptr)[length] & 0x0F) * 100;
            tai_list->tai[tai_list->N_tais].mnc += (((*ie_ptr)[length++] >> 4) & 0x0F) * 10;
          }
          tai_list->tai[tai_list->N_tais].tac = (*ie_ptr)[length++] << 8;
          tai_list->tai[tai_list->N_tais].tac |= (*ie_ptr)[length++];
          tai_list->N_tais++;
        }
      }
    }
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: UE Network Capability

    Description: Provides the network with information concerning
                 aspects of the UE related to EPS or interworking with
                 GPRS.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.34
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_ue_network_capability_ie(LIBLTE_MME_UE_NETWORK_CAPABILITY_STRUCT* ue_network_cap,
                                                           uint8**                                  ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ue_network_cap != NULL && ie_ptr != NULL) {
    if (ue_network_cap->uea_present && (ue_network_cap->ucs2_present || ue_network_cap->uia_present) &&
        (ue_network_cap->lpp_present || ue_network_cap->lcs_present || ue_network_cap->onexsrvcc_present ||
         ue_network_cap->nf_present)) {
      **ie_ptr = 5;
    } else if (ue_network_cap->uea_present && (ue_network_cap->ucs2_present || ue_network_cap->uia_present)) {
      **ie_ptr = 4;
    } else if (ue_network_cap->uea_present) {
      **ie_ptr = 3;
    } else {
      **ie_ptr = 2;
    }
    *ie_ptr += 1;
    **ie_ptr = ue_network_cap->eea[0] << 7;
    **ie_ptr |= ue_network_cap->eea[1] << 6;
    **ie_ptr |= ue_network_cap->eea[2] << 5;
    **ie_ptr |= ue_network_cap->eea[3] << 4;
    **ie_ptr |= ue_network_cap->eea[4] << 3;
    **ie_ptr |= ue_network_cap->eea[5] << 2;
    **ie_ptr |= ue_network_cap->eea[6] << 1;
    **ie_ptr |= ue_network_cap->eea[7];
    *ie_ptr += 1;
    **ie_ptr = ue_network_cap->eia[0] << 7;
    **ie_ptr |= ue_network_cap->eia[1] << 6;
    **ie_ptr |= ue_network_cap->eia[2] << 5;
    **ie_ptr |= ue_network_cap->eia[3] << 4;
    **ie_ptr |= ue_network_cap->eia[4] << 3;
    **ie_ptr |= ue_network_cap->eia[5] << 2;
    **ie_ptr |= ue_network_cap->eia[6] << 1;
    **ie_ptr |= ue_network_cap->eia[7];
    *ie_ptr += 1;
    if (ue_network_cap->uea_present) {
      **ie_ptr = ue_network_cap->uea[0] << 7;
      **ie_ptr |= ue_network_cap->uea[1] << 6;
      **ie_ptr |= ue_network_cap->uea[2] << 5;
      **ie_ptr |= ue_network_cap->uea[3] << 4;
      **ie_ptr |= ue_network_cap->uea[4] << 3;
      **ie_ptr |= ue_network_cap->uea[5] << 2;
      **ie_ptr |= ue_network_cap->uea[6] << 1;
      **ie_ptr |= ue_network_cap->uea[7];
      *ie_ptr += 1;
    }
    if (ue_network_cap->ucs2_present || ue_network_cap->uia_present) {
      **ie_ptr = ue_network_cap->ucs2 << 7;
      **ie_ptr |= ue_network_cap->uia[1] << 6;
      **ie_ptr |= ue_network_cap->uia[2] << 5;
      **ie_ptr |= ue_network_cap->uia[3] << 4;
      **ie_ptr |= ue_network_cap->uia[4] << 3;
      **ie_ptr |= ue_network_cap->uia[5] << 2;
      **ie_ptr |= ue_network_cap->uia[6] << 1;
      **ie_ptr |= ue_network_cap->uia[7];
      *ie_ptr += 1;
    }
    if (ue_network_cap->lpp_present || ue_network_cap->lcs_present || ue_network_cap->onexsrvcc_present ||
        ue_network_cap->nf_present) {
      **ie_ptr = ue_network_cap->lpp << 3;
      **ie_ptr |= ue_network_cap->lcs << 2;
      **ie_ptr |= ue_network_cap->onexsrvcc << 1;
      **ie_ptr |= ue_network_cap->nf;
      *ie_ptr += 1;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_ue_network_capability_ie(uint8**                                  ie_ptr,
                                                             LIBLTE_MME_UE_NETWORK_CAPABILITY_STRUCT* ue_network_cap)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            length;

  if (ie_ptr != NULL && ue_network_cap != NULL) {
    length = **ie_ptr;
    *ie_ptr += 1;
    ue_network_cap->eea[0] = (**ie_ptr >> 7) & 0x01;
    ue_network_cap->eea[1] = (**ie_ptr >> 6) & 0x01;
    ue_network_cap->eea[2] = (**ie_ptr >> 5) & 0x01;
    ue_network_cap->eea[3] = (**ie_ptr >> 4) & 0x01;
    ue_network_cap->eea[4] = (**ie_ptr >> 3) & 0x01;
    ue_network_cap->eea[5] = (**ie_ptr >> 2) & 0x01;
    ue_network_cap->eea[6] = (**ie_ptr >> 1) & 0x01;
    ue_network_cap->eea[7] = **ie_ptr & 0x01;
    *ie_ptr += 1;
    ue_network_cap->eia[0] = (**ie_ptr >> 7) & 0x01;
    ue_network_cap->eia[1] = (**ie_ptr >> 6) & 0x01;
    ue_network_cap->eia[2] = (**ie_ptr >> 5) & 0x01;
    ue_network_cap->eia[3] = (**ie_ptr >> 4) & 0x01;
    ue_network_cap->eia[4] = (**ie_ptr >> 3) & 0x01;
    ue_network_cap->eia[5] = (**ie_ptr >> 2) & 0x01;
    ue_network_cap->eia[6] = (**ie_ptr >> 1) & 0x01;
    ue_network_cap->eia[7] = **ie_ptr & 0x01;
    *ie_ptr += 1;
    if (length > 2) {
      ue_network_cap->uea[0]      = (**ie_ptr >> 7) & 0x01;
      ue_network_cap->uea[1]      = (**ie_ptr >> 6) & 0x01;
      ue_network_cap->uea[2]      = (**ie_ptr >> 5) & 0x01;
      ue_network_cap->uea[3]      = (**ie_ptr >> 4) & 0x01;
      ue_network_cap->uea[4]      = (**ie_ptr >> 3) & 0x01;
      ue_network_cap->uea[5]      = (**ie_ptr >> 2) & 0x01;
      ue_network_cap->uea[6]      = (**ie_ptr >> 1) & 0x01;
      ue_network_cap->uea[7]      = **ie_ptr & 0x01;
      ue_network_cap->uea_present = true;
      *ie_ptr += 1;
    } else {
      ue_network_cap->uea_present = false;
    }
    if (length > 3) {
      ue_network_cap->ucs2         = (**ie_ptr >> 7) & 0x01;
      ue_network_cap->ucs2_present = true;
      ue_network_cap->uia[1]       = (**ie_ptr >> 6) & 0x01;
      ue_network_cap->uia[2]       = (**ie_ptr >> 5) & 0x01;
      ue_network_cap->uia[3]       = (**ie_ptr >> 4) & 0x01;
      ue_network_cap->uia[4]       = (**ie_ptr >> 3) & 0x01;
      ue_network_cap->uia[5]       = (**ie_ptr >> 2) & 0x01;
      ue_network_cap->uia[6]       = (**ie_ptr >> 1) & 0x01;
      ue_network_cap->uia[7]       = **ie_ptr & 0x01;
      ue_network_cap->uia_present  = true;
      *ie_ptr += 1;
    } else {
      ue_network_cap->ucs2_present = false;
      ue_network_cap->uia_present  = false;
    }
    if (length > 4) {
      ue_network_cap->lpp               = (**ie_ptr >> 3) & 0x01;
      ue_network_cap->lpp_present       = true;
      ue_network_cap->lcs               = (**ie_ptr >> 2) & 0x01;
      ue_network_cap->lcs_present       = true;
      ue_network_cap->onexsrvcc         = (**ie_ptr >> 1) & 0x01;
      ue_network_cap->onexsrvcc_present = true;
      ue_network_cap->nf                = **ie_ptr >> 1;
      ue_network_cap->nf_present        = true;
      *ie_ptr += 1;
    } else {
      ue_network_cap->lpp_present       = false;
      ue_network_cap->lcs_present       = false;
      ue_network_cap->onexsrvcc_present = false;
      ue_network_cap->nf_present        = false;
    }
    if (length > 5) {
      *ie_ptr += length - 5;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: UE Radio Capability Update Needed

    Description: Indicates whether the MME shall delete the stored
                 UE radio capability information, if any.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.35
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_ue_radio_capability_update_needed_ie(uint8 urc_update, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] |= (urc_update & 0x01) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_ue_radio_capability_update_needed_ie(uint8** ie_ptr, uint8 bit_offset, uint8* urc_update)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && urc_update != NULL) {
    *urc_update = ((*ie_ptr)[0] >> bit_offset) & 0x01;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: UE Security Capability

    Description: Indicates which security algorithms are supported by
                 the UE in S1 mode.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.36
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_ue_security_capabilities_ie(LIBLTE_MME_UE_SECURITY_CAPABILITIES_STRUCT* ue_sec_cap,
                                                              uint8**                                     ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            idx;

  if (ue_sec_cap != NULL && ie_ptr != NULL) {
    if (ue_sec_cap->uea_present && ue_sec_cap->uia_present && ue_sec_cap->gea_present) {
      (*ie_ptr)[0] = 5;
    } else if (ue_sec_cap->uea_present && ue_sec_cap->uia_present) {
      (*ie_ptr)[0] = 4;
    } else if (ue_sec_cap->uea_present) {
      (*ie_ptr)[0] = 3;
    } else {
      (*ie_ptr)[0] = 2;
    }
    idx            = 1;
    (*ie_ptr)[idx] = ue_sec_cap->eea[0] << 7;
    (*ie_ptr)[idx] |= ue_sec_cap->eea[1] << 6;
    (*ie_ptr)[idx] |= ue_sec_cap->eea[2] << 5;
    (*ie_ptr)[idx] |= ue_sec_cap->eea[3] << 4;
    (*ie_ptr)[idx] |= ue_sec_cap->eea[4] << 3;
    (*ie_ptr)[idx] |= ue_sec_cap->eea[5] << 2;
    (*ie_ptr)[idx] |= ue_sec_cap->eea[6] << 1;
    (*ie_ptr)[idx] |= ue_sec_cap->eea[7];
    idx++;
    (*ie_ptr)[idx] = ue_sec_cap->eia[0] << 7;
    (*ie_ptr)[idx] |= ue_sec_cap->eia[1] << 6;
    (*ie_ptr)[idx] |= ue_sec_cap->eia[2] << 5;
    (*ie_ptr)[idx] |= ue_sec_cap->eia[3] << 4;
    (*ie_ptr)[idx] |= ue_sec_cap->eia[4] << 3;
    (*ie_ptr)[idx] |= ue_sec_cap->eia[5] << 2;
    (*ie_ptr)[idx] |= ue_sec_cap->eia[6] << 1;
    (*ie_ptr)[idx] |= ue_sec_cap->eia[7];
    idx++;
    if (ue_sec_cap->uea_present) {
      (*ie_ptr)[idx] = ue_sec_cap->uea[0] << 7;
      (*ie_ptr)[idx] |= ue_sec_cap->uea[1] << 6;
      (*ie_ptr)[idx] |= ue_sec_cap->uea[2] << 5;
      (*ie_ptr)[idx] |= ue_sec_cap->uea[3] << 4;
      (*ie_ptr)[idx] |= ue_sec_cap->uea[4] << 3;
      (*ie_ptr)[idx] |= ue_sec_cap->uea[5] << 2;
      (*ie_ptr)[idx] |= ue_sec_cap->uea[6] << 1;
      (*ie_ptr)[idx] |= ue_sec_cap->uea[7];
      idx++;
    }
    if (ue_sec_cap->uia_present) {
      (*ie_ptr)[idx] = ue_sec_cap->uia[1] << 6;
      (*ie_ptr)[idx] |= ue_sec_cap->uia[2] << 5;
      (*ie_ptr)[idx] |= ue_sec_cap->uia[3] << 4;
      (*ie_ptr)[idx] |= ue_sec_cap->uia[4] << 3;
      (*ie_ptr)[idx] |= ue_sec_cap->uia[5] << 2;
      (*ie_ptr)[idx] |= ue_sec_cap->uia[6] << 1;
      (*ie_ptr)[idx] |= ue_sec_cap->uia[7];
      idx++;
    }
    if (ue_sec_cap->gea_present) {
      (*ie_ptr)[idx] = ue_sec_cap->gea[1] << 6;
      (*ie_ptr)[idx] |= ue_sec_cap->gea[2] << 5;
      (*ie_ptr)[idx] |= ue_sec_cap->gea[3] << 4;
      (*ie_ptr)[idx] |= ue_sec_cap->gea[4] << 3;
      (*ie_ptr)[idx] |= ue_sec_cap->gea[5] << 2;
      (*ie_ptr)[idx] |= ue_sec_cap->gea[6] << 1;
      (*ie_ptr)[idx] |= ue_sec_cap->gea[7];
      idx++;
    }
    *ie_ptr += idx;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_ue_security_capabilities_ie(uint8**                                     ie_ptr,
                                                                LIBLTE_MME_UE_SECURITY_CAPABILITIES_STRUCT* ue_sec_cap)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            length;

  if (ie_ptr != NULL && ue_sec_cap != NULL) {
    length             = (*ie_ptr)[0];
    ue_sec_cap->eea[0] = ((*ie_ptr)[1] >> 7) & 0x01;
    ue_sec_cap->eea[1] = ((*ie_ptr)[1] >> 6) & 0x01;
    ue_sec_cap->eea[2] = ((*ie_ptr)[1] >> 5) & 0x01;
    ue_sec_cap->eea[3] = ((*ie_ptr)[1] >> 4) & 0x01;
    ue_sec_cap->eea[4] = ((*ie_ptr)[1] >> 3) & 0x01;
    ue_sec_cap->eea[5] = ((*ie_ptr)[1] >> 2) & 0x01;
    ue_sec_cap->eea[6] = ((*ie_ptr)[1] >> 1) & 0x01;
    ue_sec_cap->eea[7] = (*ie_ptr)[1] & 0x01;
    ue_sec_cap->eia[0] = ((*ie_ptr)[2] >> 7) & 0x01;
    ue_sec_cap->eia[1] = ((*ie_ptr)[2] >> 6) & 0x01;
    ue_sec_cap->eia[2] = ((*ie_ptr)[2] >> 5) & 0x01;
    ue_sec_cap->eia[3] = ((*ie_ptr)[2] >> 4) & 0x01;
    ue_sec_cap->eia[4] = ((*ie_ptr)[2] >> 3) & 0x01;
    ue_sec_cap->eia[5] = ((*ie_ptr)[2] >> 2) & 0x01;
    ue_sec_cap->eia[6] = ((*ie_ptr)[2] >> 1) & 0x01;
    ue_sec_cap->eia[7] = (*ie_ptr)[2] & 0x01;
    if (length > 2) {
      ue_sec_cap->uea[0]      = ((*ie_ptr)[3] >> 7) & 0x01;
      ue_sec_cap->uea[1]      = ((*ie_ptr)[3] >> 6) & 0x01;
      ue_sec_cap->uea[2]      = ((*ie_ptr)[3] >> 5) & 0x01;
      ue_sec_cap->uea[3]      = ((*ie_ptr)[3] >> 4) & 0x01;
      ue_sec_cap->uea[4]      = ((*ie_ptr)[3] >> 3) & 0x01;
      ue_sec_cap->uea[5]      = ((*ie_ptr)[3] >> 2) & 0x01;
      ue_sec_cap->uea[6]      = ((*ie_ptr)[3] >> 1) & 0x01;
      ue_sec_cap->uea[7]      = (*ie_ptr)[3] & 0x01;
      ue_sec_cap->uea_present = true;
    } else {
      ue_sec_cap->uea_present = false;
    }
    if (length > 3) {
      ue_sec_cap->uia[1]      = ((*ie_ptr)[4] >> 6) & 0x01;
      ue_sec_cap->uia[2]      = ((*ie_ptr)[4] >> 5) & 0x01;
      ue_sec_cap->uia[3]      = ((*ie_ptr)[4] >> 4) & 0x01;
      ue_sec_cap->uia[4]      = ((*ie_ptr)[4] >> 3) & 0x01;
      ue_sec_cap->uia[5]      = ((*ie_ptr)[4] >> 2) & 0x01;
      ue_sec_cap->uia[6]      = ((*ie_ptr)[4] >> 1) & 0x01;
      ue_sec_cap->uia[7]      = (*ie_ptr)[4] & 0x01;
      ue_sec_cap->uia_present = true;
    } else {
      ue_sec_cap->uia_present = false;
    }
    if (length > 4) {
      ue_sec_cap->gea[1]      = ((*ie_ptr)[5] >> 6) & 0x01;
      ue_sec_cap->gea[2]      = ((*ie_ptr)[5] >> 5) & 0x01;
      ue_sec_cap->gea[3]      = ((*ie_ptr)[5] >> 4) & 0x01;
      ue_sec_cap->gea[4]      = ((*ie_ptr)[5] >> 3) & 0x01;
      ue_sec_cap->gea[5]      = ((*ie_ptr)[5] >> 2) & 0x01;
      ue_sec_cap->gea[6]      = ((*ie_ptr)[5] >> 1) & 0x01;
      ue_sec_cap->gea[7]      = (*ie_ptr)[5] & 0x01;
      ue_sec_cap->gea_present = true;
    } else {
      ue_sec_cap->gea_present = false;
    }
    *ie_ptr += length + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Emergency Number List

    Description: Encodes emergency number(s) for use within the
                 country (as indicated by MCC) where the IE is
                 received.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.37
                        24.008 v10.2.0 Section 10.5.3.13
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_emergency_number_list_ie(LIBLTE_MME_EMERGENCY_NUMBER_LIST_STRUCT* emerg_num_list,
                                                           uint8**                                  ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;
  uint32            j;
  uint32            length;

  if (emerg_num_list != NULL && ie_ptr != NULL) {
    length = 1;
    for (i = 0; i < emerg_num_list->N_emerg_nums; i++) {
      if ((emerg_num_list->emerg_num[i].N_emerg_num_digits % 2) == 0) {
        (*ie_ptr)[length++] = (emerg_num_list->emerg_num[i].N_emerg_num_digits / 2) + 1;
        (*ie_ptr)[length++] = emerg_num_list->emerg_num[i].emerg_service_cat;

        if (emerg_num_list->emerg_num[i].N_emerg_num_digits / 2 > LIBLTE_MME_EMERGENCY_NUMBER_MAX_NUM_DIGITS) {
          return err;
        }

        for (j = 0; j < emerg_num_list->emerg_num[i].N_emerg_num_digits / 2; j++) {
          (*ie_ptr)[length] = emerg_num_list->emerg_num[i].emerg_num[j * 2 + 0];
          (*ie_ptr)[length++] |= emerg_num_list->emerg_num[i].emerg_num[j * 2 + 1] << 4;
        }
      } else {
        (*ie_ptr)[length++] = (emerg_num_list->emerg_num[i].N_emerg_num_digits / 2) + 2;
        (*ie_ptr)[length++] = emerg_num_list->emerg_num[i].emerg_service_cat;
        for (j = 0; j < emerg_num_list->emerg_num[i].N_emerg_num_digits / 2; j++) {
          (*ie_ptr)[length] = emerg_num_list->emerg_num[i].emerg_num[j * 2 + 0];
          (*ie_ptr)[length++] |= emerg_num_list->emerg_num[i].emerg_num[j * 2 + 1] << 4;
        }
        (*ie_ptr)[length++] = 0xF0 | emerg_num_list->emerg_num[i].emerg_num[j * 2];
      }
    }
    (*ie_ptr)[0] = length - 2;
    *ie_ptr += length - 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_emergency_number_list_ie(uint8**                                  ie_ptr,
                                                             LIBLTE_MME_EMERGENCY_NUMBER_LIST_STRUCT* emerg_num_list)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            sent_length;
  uint32            length;
  uint32            idx;
  uint32            i;

  if (ie_ptr != NULL && emerg_num_list != NULL) {
    sent_length                  = (*ie_ptr)[0] + 1;
    length                       = 1;
    emerg_num_list->N_emerg_nums = 0;
    while (length < sent_length) {
      idx                                               = emerg_num_list->N_emerg_nums;
      emerg_num_list->emerg_num[idx].N_emerg_num_digits = ((*ie_ptr)[length++] - 1) * 2;
      if (emerg_num_list->emerg_num[idx].N_emerg_num_digits > LIBLTE_MME_EMERGENCY_NUMBER_MAX_NUM_DIGITS) {
        return err;
      }

      emerg_num_list->emerg_num[idx].emerg_service_cat =
          (LIBLTE_MME_EMERGENCY_SERVICE_CATEGORY_ENUM)((*ie_ptr)[length++] & 0x1F);
      for (i = 0; i < emerg_num_list->emerg_num[idx].N_emerg_num_digits / 2; i++) {
        emerg_num_list->emerg_num[idx].emerg_num[i * 2 + 0] = (*ie_ptr)[length] & 0x0F;
        emerg_num_list->emerg_num[idx].emerg_num[i * 2 + 1] = (*ie_ptr)[length++] >> 4;
      }
      // Added by Ismael: if i==0 this is negative
      if (i > 0) {
        if (emerg_num_list->emerg_num[idx].emerg_num[i * 2 - 1] == 0x0F) {
          emerg_num_list->emerg_num[idx].N_emerg_num_digits--;
        }
      }
      emerg_num_list->N_emerg_nums++;
    }
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: CLI

    Description: Conveys information about the calling line for a
                 terminated call to a CS fallback capable UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.38
*********************************************************************/
// TODO

/*********************************************************************
    IE Name: SS Code

    Description: Conveys information related to a network initiated
                 supplementary service request to a CS fallback
                 capable UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.39
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_ss_code_ie(uint8 code, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = code;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_ss_code_ie(uint8** ie_ptr, uint8* code)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && code != NULL) {
    *code = (*ie_ptr)[0];
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: LCS Indicator

    Description: Indicates that the origin of the message is due to a
                 LCS request and the type of this request to a CS
                 fallback capable UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.40
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_lcs_indicator_ie(uint8 lcs_ind, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = lcs_ind;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_lcs_indicator_ie(uint8** ie_ptr, uint8* lcs_ind)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && lcs_ind != NULL) {
    *lcs_ind = (*ie_ptr)[0];
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: LCS Client Identity

    Description: Conveys information related to the client of a LCS
                 request for a CS fallback capable UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.41
*********************************************************************/
// TODO

/*********************************************************************
    IE Name: Generic Message Container Type

    Description: Specifies the type of message contained in the
                 generic message container IE.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.42
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_generic_message_container_type_ie(uint8 msg_cont_type, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = msg_cont_type;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_generic_message_container_type_ie(uint8** ie_ptr, uint8* msg_cont_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && msg_cont_type != NULL) {
    *msg_cont_type = (*ie_ptr)[0];
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Generic Message Container

    Description: Encapsulates the application message transferred
                 between the UE and the network.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.43
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_generic_message_container_ie(LIBLTE_BYTE_MSG_STRUCT* msg, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (msg != NULL && ie_ptr != NULL && msg->N_bytes <= LIBLTE_MAX_MSG_SIZE_BYTES) {
    (*ie_ptr)[0] = msg->N_bytes >> 8;
    (*ie_ptr)[1] = msg->N_bytes & 0xFF;
    for (i = 0; i < msg->N_bytes; i++) {
      (*ie_ptr)[2 + i] = msg->msg[i];
    }
    *ie_ptr += msg->N_bytes + 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_generic_message_container_ie(uint8** ie_ptr, LIBLTE_BYTE_MSG_STRUCT* msg)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && msg != NULL) {
    msg->N_bytes = (*ie_ptr)[0] << 8;
    msg->N_bytes |= (*ie_ptr)[1];

    if (msg->N_bytes > LIBLTE_MAX_MSG_SIZE_BYTES) {
      return err;
    }

    for (i = 0; i < msg->N_bytes; i++) {
      msg->msg[i] = (*ie_ptr)[2 + i];
    }
    *ie_ptr += msg->N_bytes + 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Voice Domain Preference and UE's Usage Setting

    Description: Provides the network with the UE's usage setting and
                 the voice domain preference for the E-UTRAN.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.44
                        24.008 v10.2.0 Section 10.5.5.28
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_voice_domain_pref_and_ue_usage_setting_ie(
    LIBLTE_MME_VOICE_DOMAIN_PREF_AND_UE_USAGE_SETTING_STRUCT* voice_domain_pref_and_ue_usage_setting,
    uint8**                                                   ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (voice_domain_pref_and_ue_usage_setting != NULL && ie_ptr != NULL) {
    **ie_ptr = 1;
    *ie_ptr += 1;
    **ie_ptr = voice_domain_pref_and_ue_usage_setting->ue_usage_setting << 2;
    **ie_ptr |= voice_domain_pref_and_ue_usage_setting->voice_domain_pref;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_voice_domain_pref_and_ue_usage_setting_ie(
    uint8**                                                   ie_ptr,
    LIBLTE_MME_VOICE_DOMAIN_PREF_AND_UE_USAGE_SETTING_STRUCT* voice_domain_pref_and_ue_usage_setting)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && voice_domain_pref_and_ue_usage_setting != NULL) {
    *ie_ptr += 1;
    voice_domain_pref_and_ue_usage_setting->ue_usage_setting =
        (LIBLTE_MME_UE_USAGE_SETTING_ENUM)((**ie_ptr >> 2) & 0x01);
    voice_domain_pref_and_ue_usage_setting->voice_domain_pref = (LIBLTE_MME_VOICE_DOMAIN_PREF_ENUM)(**ie_ptr & 0x03);
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: GUTI Type

    Description: Indicates whether the GUTI included in the same
                 message in an information element of type EPS
                 mobility identity represents a native GUTI or a
                 mapped GUTI.

    Document Reference: 24.301 v10.2.0 Section 9.9.3.45
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_guti_type_ie(LIBLTE_MME_GUTI_TYPE_ENUM guti_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= guti_type << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_guti_type_ie(uint8** ie_ptr, uint8 bit_offset, LIBLTE_MME_GUTI_TYPE_ENUM* guti_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && guti_type != NULL) {
    *guti_type = (LIBLTE_MME_GUTI_TYPE_ENUM)((**ie_ptr >> bit_offset) & 0x01);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Access Point Name

    Description: Identifies the packet data network to which the GPRS
                 user wishes to connect and notifies the access point
                 of the packet data network that wishes to connect to
                 the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.1
                        24.008 v10.2.0 Section 10.5.6.1
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_access_point_name_ie(LIBLTE_MME_ACCESS_POINT_NAME_STRUCT* apn, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  const char*       apn_str;
  uint32            i;
  uint32            len_idx;
  uint32            apn_idx;
  uint32            label_len;

  if (apn != NULL && ie_ptr != NULL) {
    apn_str      = apn->apn;
    (*ie_ptr)[0] = strnlen(apn->apn, LIBLTE_STRING_LEN) + 1;
    len_idx      = 0;
    apn_idx      = 0;
    label_len    = 0;
    while (strnlen(apn->apn, LIBLTE_STRING_LEN) > apn_idx) {
      (*ie_ptr)[1 + apn_idx + 1] = (uint8)apn_str[apn_idx];
      apn_idx++;
      label_len++;

      if (apn_str[apn_idx] == '.') {
        (*ie_ptr)[1 + len_idx] = label_len;
        label_len              = 0;
        len_idx                = apn_idx + 1;
        apn_idx++;
      }
    }
    (*ie_ptr)[1 + len_idx] = label_len;
    *ie_ptr += strnlen(apn->apn, LIBLTE_STRING_LEN) + 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_access_point_name_ie(uint8** ie_ptr, LIBLTE_MME_ACCESS_POINT_NAME_STRUCT* apn)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;
  uint32            ie_idx;
  uint32            label_len;
  uint32            str_cnt;

  if (ie_ptr != NULL && apn != NULL) {
    ie_idx  = 0;
    str_cnt = 0;
    while (ie_idx < (*ie_ptr)[0] && str_cnt < LIBLTE_STRING_LEN) {
      label_len = (*ie_ptr)[1 + ie_idx];
      for (i = 0; i < label_len && str_cnt < LIBLTE_STRING_LEN; i++) {
        apn->apn[str_cnt] = (char)((*ie_ptr)[1 + ie_idx + i + 1]);
        str_cnt++;
      }
      ie_idx += label_len + 1;
      if (ie_idx < (*ie_ptr)[0] && str_cnt < LIBLTE_STRING_LEN) {
        apn->apn[str_cnt] = '.';
        str_cnt++;
      }
    }
    if (str_cnt < LIBLTE_STRING_LEN) {
      apn->apn[str_cnt] = '\0';
    }
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: APN Aggregate Maximum Bit Rate

    Description: Indicates the initial subscribed APN-AMBR when the
                 UE establishes a PDN connection or indicates the new
                 APN-AMBR if it is changed by the network.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.2
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_apn_aggregate_maximum_bit_rate_ie(LIBLTE_MME_APN_AGGREGATE_MAXIMUM_BIT_RATE_STRUCT* apn_ambr,
                                                  uint8**                                           ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (apn_ambr != NULL && ie_ptr != NULL) {
    if (apn_ambr->ext_present && apn_ambr->ext2_present) {
      (*ie_ptr)[0] = 6;
    } else if (apn_ambr->ext_present) {
      (*ie_ptr)[0] = 4;
    } else {
      (*ie_ptr)[0] = 2;
    }
    (*ie_ptr)[1] = apn_ambr->apn_ambr_dl;
    (*ie_ptr)[2] = apn_ambr->apn_ambr_ul;
    if (apn_ambr->ext_present) {
      (*ie_ptr)[3] = apn_ambr->apn_ambr_dl_ext;
      (*ie_ptr)[4] = apn_ambr->apn_ambr_ul_ext;
    }
    if (apn_ambr->ext2_present) {
      (*ie_ptr)[5] = apn_ambr->apn_ambr_dl_ext2;
      (*ie_ptr)[6] = apn_ambr->apn_ambr_ul_ext2;
    }
    if (apn_ambr->ext_present && apn_ambr->ext2_present) {
      *ie_ptr += 7;
    } else if (apn_ambr->ext_present) {
      *ie_ptr += 5;
    } else {
      *ie_ptr += 3;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_apn_aggregate_maximum_bit_rate_ie(uint8**                                           ie_ptr,
                                                    LIBLTE_MME_APN_AGGREGATE_MAXIMUM_BIT_RATE_STRUCT* apn_ambr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && apn_ambr != NULL) {
    if (6 == (*ie_ptr)[0]) {
      apn_ambr->ext_present  = true;
      apn_ambr->ext2_present = true;
    } else if (4 == (*ie_ptr)[0]) {
      apn_ambr->ext_present  = true;
      apn_ambr->ext2_present = false;
    } else {
      apn_ambr->ext_present  = false;
      apn_ambr->ext2_present = false;
    }
    apn_ambr->apn_ambr_dl = (*ie_ptr)[1];
    apn_ambr->apn_ambr_ul = (*ie_ptr)[2];
    if (apn_ambr->ext_present) {
      apn_ambr->apn_ambr_dl_ext = (*ie_ptr)[3];
      apn_ambr->apn_ambr_ul_ext = (*ie_ptr)[4];
    }
    if (apn_ambr->ext2_present) {
      apn_ambr->apn_ambr_dl_ext2 = (*ie_ptr)[5];
      apn_ambr->apn_ambr_ul_ext2 = (*ie_ptr)[6];
    }
    if (apn_ambr->ext_present && apn_ambr->ext2_present) {
      *ie_ptr += 7;
    } else if (apn_ambr->ext_present) {
      *ie_ptr += 5;
    } else {
      *ie_ptr += 3;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Connectivity Type

    Description: Specifies the type of connectivity selected by the
                 network for the PDN connection.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.2A
                        24.008 v10.2.0 Section 10.5.6.19
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_connectivity_type_ie(uint8 con_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] |= con_type << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_connectivity_type_ie(uint8** ie_ptr, uint8 bit_offset, uint8* con_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && con_type != NULL) {
    *con_type = ((*ie_ptr)[0] >> bit_offset) & 0x0F;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: EPS Quality Of Service

    Description: Specifies the QoS parameters for an EPS bearer
                 context.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.3
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_eps_quality_of_service_ie(LIBLTE_MME_EPS_QUALITY_OF_SERVICE_STRUCT* qos,
                                                            uint8**                                   ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (qos != NULL && ie_ptr != NULL) {
    if (qos->br_present && qos->br_ext_present) {
      (*ie_ptr)[0] = 9;
    } else if (qos->br_present) {
      (*ie_ptr)[0] = 5;
    } else {
      (*ie_ptr)[0] = 1;
    }
    (*ie_ptr)[1] = qos->qci;
    if (qos->br_present) {
      (*ie_ptr)[2] = qos->mbr_ul;
      (*ie_ptr)[3] = qos->mbr_dl;
      (*ie_ptr)[4] = qos->gbr_ul;
      (*ie_ptr)[5] = qos->gbr_dl;
    }
    if (qos->br_ext_present) {
      (*ie_ptr)[6] = qos->mbr_ul_ext;
      (*ie_ptr)[7] = qos->mbr_dl_ext;
      (*ie_ptr)[8] = qos->gbr_ul_ext;
      (*ie_ptr)[9] = qos->gbr_dl_ext;
    }
    if (qos->br_present && qos->br_ext_present) {
      *ie_ptr += 10;
    } else if (qos->br_present) {
      *ie_ptr += 6;
    } else {
      *ie_ptr += 2;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_eps_quality_of_service_ie(uint8**                                   ie_ptr,
                                                              LIBLTE_MME_EPS_QUALITY_OF_SERVICE_STRUCT* qos)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && qos != NULL) {
    if ((*ie_ptr)[0] == 1) {
      qos->br_present     = false;
      qos->br_ext_present = false;
    } else if ((*ie_ptr)[0] == 5) {
      qos->br_present     = true;
      qos->br_ext_present = false;
    } else {
      qos->br_present     = true;
      qos->br_ext_present = true;
    }
    qos->qci = (*ie_ptr)[1];
    if (qos->br_present) {
      qos->mbr_ul = (*ie_ptr)[2];
      qos->mbr_dl = (*ie_ptr)[3];
      qos->gbr_ul = (*ie_ptr)[4];
      qos->gbr_dl = (*ie_ptr)[5];
    }
    if (qos->br_ext_present) {
      qos->mbr_ul_ext = (*ie_ptr)[6];
      qos->mbr_dl_ext = (*ie_ptr)[7];
      qos->gbr_ul_ext = (*ie_ptr)[8];
      qos->gbr_dl_ext = (*ie_ptr)[9];
    }
    if (qos->br_present && qos->br_ext_present) {
      *ie_ptr += 10;
    } else if (qos->br_present) {
      *ie_ptr += 6;
    } else {
      *ie_ptr += 2;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: ESM Cause

    Description: Indicates the reason why a session management request
                 is rejected.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.4
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_esm_cause_ie(uint8 cause, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = cause;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_esm_cause_ie(uint8** ie_ptr, uint8* cause)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && cause != NULL) {
    *cause = (*ie_ptr)[0];
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: ESM Information Transfer Flag

    Description: Indicates whether ESM information, i.e. protocol
                 configuration options or APN or both, is to be
                 transferred security protected.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.5
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_esm_info_transfer_flag_ie(LIBLTE_MME_ESM_INFO_TRANSFER_FLAG_ENUM esm_info_transfer_flag,
                                          uint8                                  bit_offset,
                                          uint8**                                ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= esm_info_transfer_flag << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_esm_info_transfer_flag_ie(uint8**                                 ie_ptr,
                                            uint8                                   bit_offset,
                                            LIBLTE_MME_ESM_INFO_TRANSFER_FLAG_ENUM* esm_info_transfer_flag)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && esm_info_transfer_flag != NULL) {
    *esm_info_transfer_flag = (LIBLTE_MME_ESM_INFO_TRANSFER_FLAG_ENUM)((**ie_ptr >> bit_offset) & 0x01);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Linked EPS Bearer Identity

    Description: Identifies the default bearer that is associated
                 with a dedicated EPS bearer or identifies the EPS
                 bearer (default or dedicated) with which one or more
                 packet filters specified in a traffic flow aggregate
                 are associated.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.6
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_linked_eps_bearer_identity_ie(uint8 bearer_id, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] |= (bearer_id & 0x0F) << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_linked_eps_bearer_identity_ie(uint8** ie_ptr, uint8 bit_offset, uint8* bearer_id)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && bearer_id != NULL) {
    *bearer_id = ((*ie_ptr)[0] >> bit_offset) & 0x0F;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: LLC Service Access Point Identifier

    Description: Identifies the service access point that is used for
                 the GPRS data transfer at LLC layer.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.7
                        24.008 v10.2.0 Section 10.5.6.9
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_llc_service_access_point_identifier_ie(uint8 llc_sapi, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = llc_sapi;
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_llc_service_access_point_identifier_ie(uint8** ie_ptr, uint8* llc_sapi)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && llc_sapi != NULL) {
    *llc_sapi = (*ie_ptr)[0];
    *ie_ptr += 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Notification Indicator

    Description: Informs the UE about an event which is relevant for
                 the upper layer using an EPS bearer context or
                 having requested a procedure transaction.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.7A
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_notification_indicator_ie(uint8 notification_ind, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = 1;
    (*ie_ptr)[1] = notification_ind;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_notification_indicator_ie(uint8** ie_ptr, uint8* notification_ind)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && notification_ind != NULL) {
    *notification_ind = (*ie_ptr)[1];
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Packet Flow Identifier

    Description: Indicates the packet flow identifier for a packet
                 flow context.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.8
                        24.008 v10.2.0 Section 10.5.6.11
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_packet_flow_identifier_ie(uint8 packet_flow_id, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = 1;
    (*ie_ptr)[1] = packet_flow_id;
    *ie_ptr += 2;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_packet_flow_identifier_ie(uint8** ie_ptr, uint8* packet_flow_id)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && packet_flow_id != NULL) {
    *packet_flow_id = (*ie_ptr)[1];
    *ie_ptr += (*ie_ptr)[0];

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: PDN Address

    Description: Assigns an IPv4 address to the UE associated with a
                 packet data network and provides the UE with an
                 interface identifier to be used to build the IPv6
                 link local address.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.9
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_pdn_address_ie(LIBLTE_MME_PDN_ADDRESS_STRUCT* pdn_addr, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (pdn_addr != NULL && ie_ptr != NULL) {
    (*ie_ptr)[1] = 0x00 | (pdn_addr->pdn_type & 0x07);
    if (LIBLTE_MME_PDN_TYPE_IPV4 == pdn_addr->pdn_type) {
      for (i = 0; i < 4; i++) {
        (*ie_ptr)[2 + i] = pdn_addr->addr[i];
      }
      (*ie_ptr)[0] = 5;
      *ie_ptr += 6;
    } else if (LIBLTE_MME_PDN_TYPE_IPV6 == pdn_addr->pdn_type) {
      for (i = 0; i < 8; i++) {
        (*ie_ptr)[2 + i] = pdn_addr->addr[i];
      }
      (*ie_ptr)[0] = 9;
      *ie_ptr += 10;
    } else {
      for (i = 0; i < 12; i++) {
        (*ie_ptr)[2 + i] = pdn_addr->addr[i];
      }
      (*ie_ptr)[0] = 13;
      *ie_ptr += 14;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_pdn_address_ie(uint8** ie_ptr, LIBLTE_MME_PDN_ADDRESS_STRUCT* pdn_addr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            i;

  if (ie_ptr != NULL && pdn_addr != NULL) {
    pdn_addr->pdn_type = (*ie_ptr)[1] & 0x07;
    if (LIBLTE_MME_PDN_TYPE_IPV4 == pdn_addr->pdn_type) {
      for (i = 0; i < 4; i++) {
        pdn_addr->addr[i] = (*ie_ptr)[2 + i];
      }
    } else if (LIBLTE_MME_PDN_TYPE_IPV6 == pdn_addr->pdn_type) {
      for (i = 0; i < 8; i++) {
        pdn_addr->addr[i] = (*ie_ptr)[2 + i];
      }
    } else {
      for (i = 0; i < 12; i++) {
        pdn_addr->addr[i] = (*ie_ptr)[2 + i];
      }
    }
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: PDN Type

    Description: Indicates the IP version capability of the IP stack
                 associated with the UE.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.10
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_pdn_type_ie(uint8 pdn_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= pdn_type << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_pdn_type_ie(uint8** ie_ptr, uint8 bit_offset, uint8* pdn_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && pdn_type != NULL) {
    *pdn_type = (**ie_ptr >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Protocol Configuration Options

    Description: Transfers external network protocol options
                 associated with a PDP context activation and
                 transfers additional (protocol) data (e.g.
                 configuration parameters, error codes or messages/
                 events) associated with an external protocol or an
                 application.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.11
                        24.008 v10.2.0 Section 10.5.6.3
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_protocol_config_options_ie(LIBLTE_MME_PROTOCOL_CONFIG_OPTIONS_STRUCT* protocol_cnfg_opts,
                                           uint8**                                    ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            idx;
  uint32            i;
  uint32            j;

  if (protocol_cnfg_opts != NULL && ie_ptr != NULL) {
    (*ie_ptr)[1] = 0x80;
    idx          = 2;
    for (i = 0; i < protocol_cnfg_opts->N_opts; i++) {
      (*ie_ptr)[idx++] = protocol_cnfg_opts->opt[i].id >> 8;
      (*ie_ptr)[idx++] = protocol_cnfg_opts->opt[i].id & 0x00FF;
      (*ie_ptr)[idx++] = protocol_cnfg_opts->opt[i].len;
      for (j = 0; j < protocol_cnfg_opts->opt[i].len; j++) {
        (*ie_ptr)[idx++] = protocol_cnfg_opts->opt[i].contents[j];
      }
    }
    (*ie_ptr)[0] = idx - 1;
    *ie_ptr += idx;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_protocol_config_options_ie(uint8**                                    ie_ptr,
                                             LIBLTE_MME_PROTOCOL_CONFIG_OPTIONS_STRUCT* protocol_cnfg_opts)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            idx;
  uint32            i;

  if (ie_ptr != NULL && protocol_cnfg_opts != NULL) {
    idx                        = 2;
    protocol_cnfg_opts->N_opts = 0;
    while (idx < (*ie_ptr)[0]) {
      protocol_cnfg_opts->opt[protocol_cnfg_opts->N_opts].id = (*ie_ptr)[idx++] << 8;
      protocol_cnfg_opts->opt[protocol_cnfg_opts->N_opts].id |= (*ie_ptr)[idx++];
      protocol_cnfg_opts->opt[protocol_cnfg_opts->N_opts].len = (*ie_ptr)[idx++];
      for (i = 0; i < protocol_cnfg_opts->opt[protocol_cnfg_opts->N_opts].len; i++) {
        protocol_cnfg_opts->opt[protocol_cnfg_opts->N_opts].contents[i] = (*ie_ptr)[idx++];
      }
      protocol_cnfg_opts->N_opts++;
    }
    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Quality Of Service

    Description: Specifies the QoS parameters for a PDP context.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.12
                        24.008 v10.2.0 Section 10.5.6.5
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_quality_of_service_ie(LIBLTE_MME_QUALITY_OF_SERVICE_STRUCT* qos, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (qos != NULL && ie_ptr != NULL) {
    if (qos->dl_ext_present && qos->ul_ext_present) {
      (*ie_ptr)[0] = 16;
    } else if (qos->dl_ext_present) {
      (*ie_ptr)[0] = 14;
    } else {
      (*ie_ptr)[0] = 12;
    }
    (*ie_ptr)[1] = ((qos->delay_class & 0x07) << 3) | (qos->reliability_class & 0x07);
    (*ie_ptr)[2] = ((qos->peak_throughput & 0x0F) << 4) | (qos->precedence_class & 0x07);
    (*ie_ptr)[3] = qos->mean_throughput & 0x1F;
    (*ie_ptr)[4] = ((qos->traffic_class & 0x07) << 5) | ((qos->delivery_order & 0x03) << 3) |
                   (qos->delivery_of_erroneous_sdu & 0x03);
    (*ie_ptr)[5]  = qos->max_sdu_size;
    (*ie_ptr)[6]  = qos->mbr_ul;
    (*ie_ptr)[7]  = qos->mbr_dl;
    (*ie_ptr)[8]  = ((qos->residual_ber & 0x0F) << 4) | (qos->sdu_error_ratio & 0x0F);
    (*ie_ptr)[9]  = ((qos->transfer_delay & 0x3F) << 2) | (qos->traffic_handling_prio & 0x03);
    (*ie_ptr)[10] = qos->gbr_ul;
    (*ie_ptr)[11] = qos->gbr_dl;
    (*ie_ptr)[12] = ((qos->signalling_ind & 0x01) << 4) | (qos->source_stats_descriptor & 0x0F);
    if (qos->dl_ext_present) {
      (*ie_ptr)[13] = qos->mbr_dl_ext;
      (*ie_ptr)[14] = qos->gbr_dl_ext;
    }
    if (qos->ul_ext_present) {
      (*ie_ptr)[15] = qos->mbr_ul_ext;
      (*ie_ptr)[16] = qos->gbr_ul_ext;
    }
    if (qos->dl_ext_present && qos->ul_ext_present) {
      *ie_ptr += 17;
    } else if (qos->dl_ext_present) {
      *ie_ptr += 15;
    } else {
      *ie_ptr += 13;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_quality_of_service_ie(uint8** ie_ptr, LIBLTE_MME_QUALITY_OF_SERVICE_STRUCT* qos)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && qos != NULL) {
    if (16 == (*ie_ptr)[0]) {
      qos->dl_ext_present = true;
      qos->ul_ext_present = true;
    } else if (14 == (*ie_ptr)[0]) {
      qos->dl_ext_present = true;
      qos->ul_ext_present = false;
    } else {
      qos->dl_ext_present = false;
      qos->ul_ext_present = false;
    }
    qos->delay_class               = ((*ie_ptr)[1] >> 3) & 0x07;
    qos->reliability_class         = (*ie_ptr)[1] & 0x07;
    qos->peak_throughput           = (*ie_ptr)[2] >> 4;
    qos->precedence_class          = (*ie_ptr)[2] & 0x07;
    qos->mean_throughput           = (*ie_ptr)[3] & 0x1F;
    qos->traffic_class             = ((*ie_ptr)[4] >> 5) & 0x07;
    qos->delivery_order            = ((*ie_ptr)[4] >> 3) & 0x03;
    qos->delivery_of_erroneous_sdu = (*ie_ptr)[4] & 0x07;
    qos->max_sdu_size              = (*ie_ptr)[5];
    qos->mbr_ul                    = (*ie_ptr)[6];
    qos->mbr_dl                    = (*ie_ptr)[7];
    qos->residual_ber              = ((*ie_ptr)[8] >> 4) & 0x0F;
    qos->sdu_error_ratio           = (*ie_ptr)[8] & 0x0F;
    qos->transfer_delay            = ((*ie_ptr)[9] >> 2) & 0x3F;
    qos->traffic_handling_prio     = (*ie_ptr)[9] & 0x03;
    qos->gbr_ul                    = (*ie_ptr)[10];
    qos->gbr_dl                    = (*ie_ptr)[11];
    qos->signalling_ind            = ((*ie_ptr)[12] >> 4) & 0x01;
    qos->source_stats_descriptor   = (*ie_ptr)[12] & 0x0F;
    if (qos->dl_ext_present) {
      qos->mbr_dl_ext = (*ie_ptr)[13];
      qos->gbr_dl_ext = (*ie_ptr)[14];
    }
    if (qos->ul_ext_present) {
      qos->mbr_ul_ext = (*ie_ptr)[15];
      qos->gbr_ul_ext = (*ie_ptr)[16];
    }
    if (qos->dl_ext_present && qos->ul_ext_present) {
      *ie_ptr += 17;
    } else if (qos->dl_ext_present) {
      *ie_ptr += 15;
    } else {
      *ie_ptr += 13;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Radio Priority

    Description: Specifies the priority level the UE shall use at the
                 lower layers for transmission of data related to a
                 PDP context or for mobile originated SMS
                 transmission.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.13
                        24.008 v10.2.0 Section 10.5.7.2
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_radio_priority_ie(uint8 radio_prio, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    (*ie_ptr)[0] = radio_prio << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_radio_priority_ie(uint8** ie_ptr, uint8 bit_offset, uint8* radio_prio)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && radio_prio != NULL) {
    *radio_prio = ((*ie_ptr)[0] >> bit_offset) & 0x07;
    err         = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Request Type

    Description: Indicates whether the UE requests to establish a new
                 connectivity to a PDN or keep the connection(s) to
                 which it has connected via non-3GPP access.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.14
                        24.008 v10.2.0 Section 10.5.6.17
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_request_type_ie(uint8 req_type, uint8 bit_offset, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL) {
    **ie_ptr |= req_type << bit_offset;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_request_type_ie(uint8** ie_ptr, uint8 bit_offset, uint8* req_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && req_type != NULL) {
    *req_type = (**ie_ptr >> bit_offset) & 0x07;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Traffic Flow Aggregate Description

    Description: Specifies the aggregate of one or more packet filters
                 and their related parameters and operations.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.15
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_traffic_flow_aggregate_description_ie(LIBLTE_MME_TRAFFIC_FLOW_AGGREGATE_DESCRIPTION_STRUCT* tfad,
                                                      uint8**                                               ie_ptr)
{
  return (liblte_mme_pack_traffic_flow_template_ie((LIBLTE_MME_TRAFFIC_FLOW_TEMPLATE_STRUCT*)tfad, ie_ptr));
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_traffic_flow_aggregate_description_ie(uint8**                                               ie_ptr,
                                                        LIBLTE_MME_TRAFFIC_FLOW_AGGREGATE_DESCRIPTION_STRUCT* tfad)
{
  return (liblte_mme_unpack_traffic_flow_template_ie(ie_ptr, (LIBLTE_MME_TRAFFIC_FLOW_TEMPLATE_STRUCT*)tfad));
}

/*********************************************************************
    IE Name: Traffic Flow Template

    Description: Specifies the TFT parameters and operations for a
                 PDP context.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.16
                        24.008 v10.2.0 Section 10.5.6.12
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_traffic_flow_template_ie(LIBLTE_MME_TRAFFIC_FLOW_TEMPLATE_STRUCT* tft, uint8** ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            idx;
  uint32            i;
  uint32            j;

  if (tft != NULL && ie_ptr != NULL) {
    idx            = 1;
    (*ie_ptr)[idx] = (tft->tft_op_code & 0x07) << 5;
    if (0 != tft->parameter_list_size) {
      (*ie_ptr)[idx] |= 0x10;
    }
    (*ie_ptr)[idx] |= tft->packet_filter_list_size & 0x0F;
    idx++;

    for (i = 0; i < tft->packet_filter_list_size; i++) {
      (*ie_ptr)[idx] = (tft->packet_filter_list[i].dir & 0x0F) << 4;
      (*ie_ptr)[idx] |= tft->packet_filter_list[i].id & 0x0F;
      idx++;
      if (LIBLTE_MME_TFT_OPERATION_CODE_DELETE_PACKET_FILTERS_FROM_EXISTING_TFT != tft->tft_op_code) {
        (*ie_ptr)[idx] = tft->packet_filter_list[i].eval_precedence;
        idx++;
        (*ie_ptr)[idx] = tft->packet_filter_list[i].filter_size;
        idx++;
        for (j = 0; j < tft->packet_filter_list[i].filter_size; j++) {
          (*ie_ptr)[idx] = tft->packet_filter_list[i].filter[j];
          idx++;
        }
      }
    }

    for (i = 0; i < tft->parameter_list_size; i++) {
      (*ie_ptr)[idx] = tft->parameter_list[i].id;
      idx++;
      (*ie_ptr)[idx] = tft->parameter_list[i].parameter_size;
      idx++;
      for (j = 0; j < tft->parameter_list[i].parameter_size; j++) {
        (*ie_ptr)[idx] = tft->parameter_list[i].parameter[j];
        idx++;
      }
    }
    (*ie_ptr)[0] = idx - 1;
    *ie_ptr += idx;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_traffic_flow_template_ie(uint8**                                  ie_ptr,
                                                             LIBLTE_MME_TRAFFIC_FLOW_TEMPLATE_STRUCT* tft)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint32            idx;
  uint32            i;
  uint32            j;
  bool              param_list_present;

  if (ie_ptr != NULL && tft != NULL) {
    idx                          = 1;
    tft->tft_op_code             = (LIBLTE_MME_TFT_OPERATION_CODE_ENUM)(((*ie_ptr)[idx] >> 5) & 0x07);
    param_list_present           = ((*ie_ptr)[idx] >> 4) & 0x01;
    tft->packet_filter_list_size = (*ie_ptr)[idx] & 0x0F;
    idx++;

    for (i = 0; i < tft->packet_filter_list_size; i++) {
      tft->packet_filter_list[i].dir = (LIBLTE_MME_TFT_PACKET_FILTER_DIRECTION_ENUM)(((*ie_ptr)[idx] >> 4) & 0x0F);
      tft->packet_filter_list[i].id  = (*ie_ptr)[idx] & 0x0F;
      idx++;
      if (LIBLTE_MME_TFT_OPERATION_CODE_DELETE_PACKET_FILTERS_FROM_EXISTING_TFT != tft->tft_op_code) {
        tft->packet_filter_list[i].eval_precedence = (*ie_ptr)[idx];
        idx++;
        tft->packet_filter_list[i].filter_size = (*ie_ptr)[idx];
        idx++;
        for (j = 0; j < tft->packet_filter_list[i].filter_size; j++) {
          tft->packet_filter_list[i].filter[j] = (*ie_ptr)[idx];
          idx++;
        }
      }
    }

    tft->parameter_list_size = 0;
    if (param_list_present) {
      while (idx < (*ie_ptr)[0]) {
        tft->parameter_list[tft->parameter_list_size].id = (*ie_ptr)[idx];
        idx++;
        tft->parameter_list[tft->parameter_list_size].parameter_size = (*ie_ptr)[idx];
        idx++;
        for (i = 0; i < tft->parameter_list[tft->parameter_list_size].parameter_size; i++) {
          tft->parameter_list[tft->parameter_list_size].parameter[i] = (*ie_ptr)[idx];
          idx++;
        }
        tft->parameter_list_size++;
      }
    }

    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    IE Name: Transaction Identifier

    Description: Represents the corresponding PDP context in A/Gb
                 mode or Iu mode which is mapped from the EPS bearer
                 context.

    Document Reference: 24.301 v10.2.0 Section 9.9.4.17
                        24.008 v10.2.0 Section 10.5.6.7
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_transaction_identifier_ie(LIBLTE_MME_TRANSACTION_IDENTIFIER_STRUCT* trans_id,
                                                            uint8**                                   ie_ptr)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (trans_id != NULL && ie_ptr != NULL) {
    if (LIBLTE_MME_TI_VALUE_IS_GIVEN_BY_TIE == trans_id->tio) {
      (*ie_ptr)[0] = 2;
    } else {
      (*ie_ptr)[0] = 1;
    }
    (*ie_ptr)[1] = ((trans_id->ti_flag & 0x01) << 7) | ((trans_id->tio & 0x07) << 4);
    if (LIBLTE_MME_TI_VALUE_IS_GIVEN_BY_TIE == trans_id->tio) {
      (*ie_ptr)[2] = 0x80 | (trans_id->tie & 0x7F);
      *ie_ptr += 3;
    } else {
      *ie_ptr += 2;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_transaction_identifier_ie(uint8**                                   ie_ptr,
                                                              LIBLTE_MME_TRANSACTION_IDENTIFIER_STRUCT* trans_id)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (ie_ptr != NULL && trans_id != NULL) {
    uint8_t len       = (*ie_ptr)[0];
    trans_id->ti_flag = (*ie_ptr)[1] >> 7;
    trans_id->tio     = ((*ie_ptr)[1] >> 4) & 0x07;
    if (len > 2 || len == 0) {
      return err;
    }

    if (2 == len) {
      trans_id->tie = (*ie_ptr)[2] & 0x7F;
    }

    *ie_ptr += (*ie_ptr)[0] + 1;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*******************************************************************************
                              MESSAGE FUNCTIONS
*******************************************************************************/

/*********************************************************************
    Message Name: Security Message Header (Plain NAS Message)

    Description: Security header for NAS messages.

    Document Reference: 24.301 v10.2.0 Section 9.1
*********************************************************************/

LIBLTE_ERROR_ENUM liblte_mme_parse_msg_sec_header(LIBLTE_BYTE_MSG_STRUCT* msg, uint8* pd, uint8* sec_hdr_type)
{

  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;

  if (msg != NULL && pd != NULL && sec_hdr_type != NULL) {
    *sec_hdr_type = (uint8)((msg->msg[0] & 0xF0) >> 4);
    err           = LIBLTE_SUCCESS;
  }
  return (err);
}

/*********************************************************************
    Message Name: Message Header (Plain NAS Message)

    Description: Message header for plain NAS messages.

    Document Reference: 24.301 v10.2.0 Section 9.1
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_parse_msg_header(LIBLTE_BYTE_MSG_STRUCT* msg, uint8* pd, uint8* msg_type)
{
  LIBLTE_ERROR_ENUM err = LIBLTE_ERROR_INVALID_INPUTS;
  uint8             sec_hdr_type;

  if (msg != NULL && pd != NULL && msg_type != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;

    // Protocol Discriminator
    *pd = msg->msg[0] & 0x0F;

    if (LIBLTE_MME_SECURITY_HDR_TYPE_SERVICE_REQUEST == sec_hdr_type) {
      *msg_type = LIBLTE_MME_SECURITY_HDR_TYPE_SERVICE_REQUEST;
    } else {
      if (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT == *pd) {
        // Message Type
        *msg_type = msg->msg[2];
      } else {
        if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
          // Message Type
          *msg_type = msg->msg[1];
        } else {
          // Protocol Discriminator
          *pd = msg->msg[6] & 0x0F;

          if (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT == *pd) {
            *msg_type = msg->msg[8];
          } else {
            *msg_type = msg->msg[7];
          }
        }
      }
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_pack_security_protected_nas_msg(LIBLTE_BYTE_MSG_STRUCT* msg,
                                                             uint8                   sec_hdr_type,
                                                             uint32                  count,
                                                             LIBLTE_BYTE_MSG_STRUCT* sec_msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = sec_msg->msg;
  uint32            i;

  if (msg != NULL && sec_msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // NAS Message
    for (i = 0; i < msg->N_bytes; i++) {
      *msg_ptr = msg->msg[i];
      msg_ptr++;
    }

    // Fill in the number of bytes used
    sec_msg->N_bytes = msg_ptr - sec_msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Attach Accept

    Description: Sent by the network to the UE to indicate that the
                 corresponding attach request has been accepted.

    Document Reference: 24.301 v10.2.0 Section 8.2.1
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_attach_accept_msg(LIBLTE_MME_ATTACH_ACCEPT_MSG_STRUCT* attach_accept,
                                                    uint8                                sec_hdr_type,
                                                    uint32                               count,
                                                    LIBLTE_BYTE_MSG_STRUCT*              msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (attach_accept != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ATTACH_ACCEPT;
    msg_ptr++;

    // EPS Attach Result & Spare Half Octet
    *msg_ptr = 0;
    liblte_mme_pack_eps_attach_result_ie(attach_accept->eps_attach_result, 0, &msg_ptr);
    msg_ptr++;

    // T3412 Value
    liblte_mme_pack_gprs_timer_ie(&attach_accept->t3412, &msg_ptr);

    // TAI List
    liblte_mme_pack_tracking_area_identity_list_ie(&attach_accept->tai_list, &msg_ptr);

    // ESM Message Container
    liblte_mme_pack_esm_message_container_ie(&attach_accept->esm_msg, &msg_ptr);

    // GUTI
    if (attach_accept->guti_present) {
      *msg_ptr = LIBLTE_MME_GUTI_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_mobile_id_ie(&attach_accept->guti, &msg_ptr);
    }

    // Location Area Identification
    if (attach_accept->lai_present) {
      *msg_ptr = LIBLTE_MME_LOCATION_AREA_IDENTIFICATION_IEI;
      msg_ptr++;
      liblte_mme_pack_location_area_id_ie(&attach_accept->lai, &msg_ptr);
    }

    // MS Identity
    if (attach_accept->ms_id_present) {
      *msg_ptr = LIBLTE_MME_MS_IDENTITY_IEI;
      msg_ptr++;
      liblte_mme_pack_mobile_id_ie(&attach_accept->ms_id, &msg_ptr);
    }

    // EMM Cause
    if (attach_accept->emm_cause_present) {
      *msg_ptr = LIBLTE_MME_EMM_CAUSE_IEI;
      msg_ptr++;
      liblte_mme_pack_emm_cause_ie(attach_accept->emm_cause, &msg_ptr);
    }

    // T3402 Value
    if (attach_accept->t3402_present) {
      *msg_ptr = LIBLTE_MME_T3402_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_ie(&attach_accept->t3402, &msg_ptr);
    }

    // T3423 Value
    if (attach_accept->t3423_present) {
      *msg_ptr = LIBLTE_MME_T3423_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_ie(&attach_accept->t3423, &msg_ptr);
    }

    // Equivalent PLMNs
    if (attach_accept->equivalent_plmns_present) {
      *msg_ptr = LIBLTE_MME_EQUIVALENT_PLMNS_IEI;
      msg_ptr++;
      liblte_mme_pack_plmn_list_ie(&attach_accept->equivalent_plmns, &msg_ptr);
    }

    // Emergency Number List
    if (attach_accept->emerg_num_list_present) {
      *msg_ptr = LIBLTE_MME_EMERGENCY_NUMBER_LIST_IEI;
      msg_ptr++;
      liblte_mme_pack_emergency_number_list_ie(&attach_accept->emerg_num_list, &msg_ptr);
    }

    // EPS Network Feature Support
    if (attach_accept->eps_network_feature_support_present) {
      *msg_ptr = LIBLTE_MME_EPS_NETWORK_FEATURE_SUPPORT_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_network_feature_support_ie(&attach_accept->eps_network_feature_support, &msg_ptr);
    }

    // Additional Update Result
    if (attach_accept->additional_update_result_present) {
      *msg_ptr = LIBLTE_MME_ADDITIONAL_UPDATE_RESULT_IEI << 4;
      liblte_mme_pack_additional_update_result_ie(attach_accept->additional_update_result, 0, &msg_ptr);
      msg_ptr++;
    }

    // T3412 Extended Value
    if (attach_accept->t3412_ext_present) {
      *msg_ptr = LIBLTE_MME_T3412_EXTENDED_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_3_ie(&attach_accept->t3412_ext, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_attach_accept_msg(LIBLTE_BYTE_MSG_STRUCT*              msg,
                                                      LIBLTE_MME_ATTACH_ACCEPT_MSG_STRUCT* attach_accept)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && attach_accept != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EPS Attach Result & Spare Half Octet
    liblte_mme_unpack_eps_attach_result_ie(&msg_ptr, 0, &attach_accept->eps_attach_result);
    msg_ptr++;

    // T3412 Value
    liblte_mme_unpack_gprs_timer_ie(&msg_ptr, &attach_accept->t3412);

    // TAI List
    liblte_mme_unpack_tracking_area_identity_list_ie(&msg_ptr, &attach_accept->tai_list);

    // ESM Message Container
    liblte_mme_unpack_esm_message_container_ie(&msg_ptr, &attach_accept->esm_msg);

    // GUTI
    if (LIBLTE_MME_GUTI_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_mobile_id_ie(&msg_ptr, &attach_accept->guti);
      attach_accept->guti_present = true;
    } else {
      attach_accept->guti_present = false;
    }

    // Location Area Identification
    if (LIBLTE_MME_LOCATION_AREA_IDENTIFICATION_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_location_area_id_ie(&msg_ptr, &attach_accept->lai);
      attach_accept->lai_present = true;
    } else {
      attach_accept->lai_present = false;
    }

    // MS Identity
    if (LIBLTE_MME_MS_IDENTITY_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_mobile_id_ie(&msg_ptr, &attach_accept->ms_id);
      attach_accept->ms_id_present = true;
    } else {
      attach_accept->ms_id_present = false;
    }

    // EMM Cause
    if (LIBLTE_MME_EMM_CAUSE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_emm_cause_ie(&msg_ptr, &attach_accept->emm_cause);
      attach_accept->emm_cause_present = true;
    } else {
      attach_accept->emm_cause_present = false;
    }

    // T3402 Value
    if (LIBLTE_MME_T3402_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_ie(&msg_ptr, &attach_accept->t3402);
      attach_accept->t3402_present = true;
    } else {
      attach_accept->t3402_present = false;
    }

    // T3423 Value
    if (LIBLTE_MME_T3423_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_ie(&msg_ptr, &attach_accept->t3423);
      attach_accept->t3423_present = true;
    } else {
      attach_accept->t3423_present = false;
    }

    // Equivalent PLMNs
    if (LIBLTE_MME_EQUIVALENT_PLMNS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_plmn_list_ie(&msg_ptr, &attach_accept->equivalent_plmns);
      attach_accept->equivalent_plmns_present = true;
    } else {
      attach_accept->equivalent_plmns_present = false;
    }

    // Emergency Number List
    if (LIBLTE_MME_EMERGENCY_NUMBER_LIST_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_emergency_number_list_ie(&msg_ptr, &attach_accept->emerg_num_list);
      attach_accept->emerg_num_list_present = true;
    } else {
      attach_accept->emerg_num_list_present = false;
    }

    // EPS Network Feature Support
    if (LIBLTE_MME_EPS_NETWORK_FEATURE_SUPPORT_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_network_feature_support_ie(&msg_ptr, &attach_accept->eps_network_feature_support);
      attach_accept->eps_network_feature_support_present = true;
    } else {
      attach_accept->eps_network_feature_support_present = false;
    }

    // Additional Update Result
    if ((LIBLTE_MME_ADDITIONAL_UPDATE_RESULT_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_additional_update_result_ie(&msg_ptr, 0, &attach_accept->additional_update_result);
      msg_ptr++;
      attach_accept->additional_update_result_present = true;
    } else {
      attach_accept->additional_update_result_present = false;
    }

    // T3412 Extended Value
    if (LIBLTE_MME_T3412_EXTENDED_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_3_ie(&msg_ptr, &attach_accept->t3412_ext);
      attach_accept->t3412_ext_present = true;
    } else {
      attach_accept->t3412_ext_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Attach Complete

    Description: Sent by the UE to the network in response to an
                 ATTACH ACCEPT message.

    Document Reference: 24.301 v10.2.0 Section 8.2.2
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_attach_complete_msg(LIBLTE_MME_ATTACH_COMPLETE_MSG_STRUCT* attach_comp,
                                                      uint8                                  sec_hdr_type,
                                                      uint32                                 count,
                                                      LIBLTE_BYTE_MSG_STRUCT*                msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (attach_comp != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ATTACH_COMPLETE;
    msg_ptr++;

    // ESM Message Container
    liblte_mme_pack_esm_message_container_ie(&attach_comp->esm_msg, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_attach_complete_msg(LIBLTE_BYTE_MSG_STRUCT*                msg,
                                                        LIBLTE_MME_ATTACH_COMPLETE_MSG_STRUCT* attach_comp)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && attach_comp != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // ESM Message Container
    liblte_mme_unpack_esm_message_container_ie(&msg_ptr, &attach_comp->esm_msg);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Attach Reject

    Description: Sent by the network to the UE to indicate that the
                 corresponding attach request has been rejected.

    Document Reference: 24.301 v10.2.0 Section 8.2.3
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_attach_reject_msg(LIBLTE_MME_ATTACH_REJECT_MSG_STRUCT* attach_rej,
                                                    LIBLTE_BYTE_MSG_STRUCT*              msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (attach_rej != NULL && msg != NULL) {
    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ATTACH_REJECT;
    msg_ptr++;

    // EMM Cause
    liblte_mme_pack_emm_cause_ie(attach_rej->emm_cause, &msg_ptr);

    // ESM Message Container
    if (attach_rej->esm_msg_present) {
      *msg_ptr = LIBLTE_MME_ESM_MSG_CONTAINER_IEI;
      msg_ptr++;
      liblte_mme_pack_esm_message_container_ie(&attach_rej->esm_msg, &msg_ptr);
    }

    // T3446 Value
    if (attach_rej->t3446_value_present) {
      *msg_ptr = LIBLTE_MME_T3446_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_2_ie(attach_rej->t3446_value, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_attach_reject_msg(LIBLTE_BYTE_MSG_STRUCT*              msg,
                                                      LIBLTE_MME_ATTACH_REJECT_MSG_STRUCT* attach_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && attach_rej != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EMM Cause
    liblte_mme_unpack_emm_cause_ie(&msg_ptr, &attach_rej->emm_cause);

    // ESM Message Container
    if (LIBLTE_MME_ESM_MSG_CONTAINER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_esm_message_container_ie(&msg_ptr, &attach_rej->esm_msg);
      attach_rej->esm_msg_present = true;
    } else {
      attach_rej->esm_msg_present = false;
    }

    // T3446 Value
    if (LIBLTE_MME_T3446_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_2_ie(&msg_ptr, &attach_rej->t3446_value);
      attach_rej->t3446_value_present = true;
    } else {
      attach_rej->t3446_value_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Attach Request

    Description: Sent by the UE to the network to perform an attach
                 procedure.

    Document Reference: 24.301 v10.2.0 Section 8.2.4
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_attach_request_msg(LIBLTE_MME_ATTACH_REQUEST_MSG_STRUCT* attach_req,
                                                     LIBLTE_BYTE_MSG_STRUCT*               msg)
{
  return liblte_mme_pack_attach_request_msg(attach_req, LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS, 0, msg);
}

LIBLTE_ERROR_ENUM liblte_mme_pack_attach_request_msg(LIBLTE_MME_ATTACH_REQUEST_MSG_STRUCT* attach_req,
                                                     uint8                                 sec_hdr_type,
                                                     uint32                                count,
                                                     LIBLTE_BYTE_MSG_STRUCT*               msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (attach_req != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ATTACH_REQUEST;
    msg_ptr++;

    // EPS Attach Type & NAS Key Set Identifier
    *msg_ptr = 0;
    liblte_mme_pack_eps_attach_type_ie(attach_req->eps_attach_type, 0, &msg_ptr);
    liblte_mme_pack_nas_key_set_id_ie(&attach_req->nas_ksi, 4, &msg_ptr);
    msg_ptr++;

    // EPS Mobile ID
    liblte_mme_pack_eps_mobile_id_ie(&attach_req->eps_mobile_id, &msg_ptr);

    // UE Network Capability
    liblte_mme_pack_ue_network_capability_ie(&attach_req->ue_network_cap, &msg_ptr);

    // ESM Message Container
    liblte_mme_pack_esm_message_container_ie(&attach_req->esm_msg, &msg_ptr);

    // Old P-TMSI Signature
    if (attach_req->old_p_tmsi_signature_present) {
      *msg_ptr = LIBLTE_MME_P_TMSI_SIGNATURE_IEI;
      msg_ptr++;
      liblte_mme_pack_p_tmsi_signature_ie(attach_req->old_p_tmsi_signature, &msg_ptr);
    }

    // Additional GUTI
    if (attach_req->additional_guti_present) {
      *msg_ptr = LIBLTE_MME_ADDITIONAL_GUTI_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_mobile_id_ie(&attach_req->additional_guti, &msg_ptr);
    }

    // Last Visited Registered TAI
    if (attach_req->last_visited_registered_tai_present) {
      *msg_ptr = LIBLTE_MME_LAST_VISITED_REGISTERED_TAI_IEI;
      msg_ptr++;
      liblte_mme_pack_tracking_area_id_ie(&attach_req->last_visited_registered_tai, &msg_ptr);
    }

    // DRX Parameter
    if (attach_req->drx_param_present) {
      *msg_ptr = LIBLTE_MME_DRX_PARAMETER_IEI;
      msg_ptr++;
      liblte_mme_pack_drx_parameter_ie(&attach_req->drx_param, &msg_ptr);
    }

    // MS Network Capability
    if (attach_req->ms_network_cap_present) {
      *msg_ptr = LIBLTE_MME_MS_NETWORK_CAPABILITY_IEI;
      msg_ptr++;
      liblte_mme_pack_ms_network_capability_ie(&attach_req->ms_network_cap, &msg_ptr);
    }

    // Old Location Area ID
    if (attach_req->old_lai_present) {
      *msg_ptr = LIBLTE_MME_LOCATION_AREA_IDENTIFICATION_IEI;
      msg_ptr++;
      liblte_mme_pack_location_area_id_ie(&attach_req->old_lai, &msg_ptr);
    }

    // TMSI Status
    if (attach_req->tmsi_status_present) {
      *msg_ptr = LIBLTE_MME_TMSI_STATUS_IEI << 4;
      liblte_mme_pack_tmsi_status_ie(attach_req->tmsi_status, 0, &msg_ptr);
      msg_ptr++;
    }

    // Mobile Station Classmark 2
    if (attach_req->ms_cm2_present) {
      *msg_ptr = LIBLTE_MME_MS_CLASSMARK_2_IEI;
      msg_ptr++;
      liblte_mme_pack_mobile_station_classmark_2_ie(&attach_req->ms_cm2, &msg_ptr);
    }

    // Mobile Station Classmark 3
    if (attach_req->ms_cm3_present) {
      *msg_ptr = LIBLTE_MME_MS_CLASSMARK_3_IEI;
      msg_ptr++;
      liblte_mme_pack_mobile_station_classmark_3_ie(&attach_req->ms_cm3, &msg_ptr);
    }

    // Supported Codecs
    if (attach_req->supported_codecs_present) {
      *msg_ptr = LIBLTE_MME_SUPPORTED_CODEC_LIST_IEI;
      msg_ptr++;
      liblte_mme_pack_supported_codec_list_ie(&attach_req->supported_codecs, &msg_ptr);
    }

    // Additional Update Type
    if (attach_req->additional_update_type_present) {
      *msg_ptr = LIBLTE_MME_ADDITIONAL_UPDATE_TYPE_IEI << 4;
      liblte_mme_pack_additional_update_type_ie(attach_req->additional_update_type, 0, &msg_ptr);
      msg_ptr++;
    }

    // Voice Domain Preference and UE's Usage Setting
    if (attach_req->voice_domain_pref_and_ue_usage_setting_present) {
      *msg_ptr = LIBLTE_MME_VOICE_DOMAIN_PREF_AND_UE_USAGE_SETTING_IEI;
      msg_ptr++;
      liblte_mme_pack_voice_domain_pref_and_ue_usage_setting_ie(&attach_req->voice_domain_pref_and_ue_usage_setting,
                                                                &msg_ptr);
    }

    // Device Properties
    if (attach_req->device_properties_present) {
      *msg_ptr = LIBLTE_MME_ATTACH_REQUEST_DEVICE_PROPERTIES_IEI << 4;
      liblte_mme_pack_device_properties_ie(attach_req->device_properties, 0, &msg_ptr);
      msg_ptr++;
    }

    // Old GUTI Type
    if (attach_req->old_guti_type_present) {
      *msg_ptr = LIBLTE_MME_GUTI_TYPE_IEI << 4;
      liblte_mme_pack_guti_type_ie(attach_req->old_guti_type, 0, &msg_ptr);
      msg_ptr++;
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_attach_request_msg(LIBLTE_BYTE_MSG_STRUCT*               msg,
                                                       LIBLTE_MME_ATTACH_REQUEST_MSG_STRUCT* attach_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && attach_req != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EPS Attach Type & NAS Key Set Identifier
    liblte_mme_unpack_eps_attach_type_ie(&msg_ptr, 0, &attach_req->eps_attach_type);
    liblte_mme_unpack_nas_key_set_id_ie(&msg_ptr, 4, &attach_req->nas_ksi);
    msg_ptr++;

    // EPS Mobile ID
    liblte_mme_unpack_eps_mobile_id_ie(&msg_ptr, &attach_req->eps_mobile_id);

    // UE Network Capability
    liblte_mme_unpack_ue_network_capability_ie(&msg_ptr, &attach_req->ue_network_cap);

    // ESM Message Container
    liblte_mme_unpack_esm_message_container_ie(&msg_ptr, &attach_req->esm_msg);

    // Old P-TMSI Signature
    if (LIBLTE_MME_P_TMSI_SIGNATURE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_p_tmsi_signature_ie(&msg_ptr, &attach_req->old_p_tmsi_signature);
      attach_req->old_p_tmsi_signature_present = true;
    } else {
      attach_req->old_p_tmsi_signature_present = false;
    }

    // Additional GUTI
    if (LIBLTE_MME_ADDITIONAL_GUTI_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_mobile_id_ie(&msg_ptr, &attach_req->additional_guti);
      attach_req->additional_guti_present = true;
    } else {
      attach_req->additional_guti_present = false;
    }

    // Last Visited Registered TAI
    if (LIBLTE_MME_LAST_VISITED_REGISTERED_TAI_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_tracking_area_id_ie(&msg_ptr, &attach_req->last_visited_registered_tai);
      attach_req->last_visited_registered_tai_present = true;
    } else {
      attach_req->last_visited_registered_tai_present = false;
    }

    // DRX Parameter
    if (LIBLTE_MME_DRX_PARAMETER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_drx_parameter_ie(&msg_ptr, &attach_req->drx_param);
      attach_req->drx_param_present = true;
    } else {
      attach_req->drx_param_present = false;
    }

    // MS Network Capability
    if (LIBLTE_MME_MS_NETWORK_CAPABILITY_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_ms_network_capability_ie(&msg_ptr, &attach_req->ms_network_cap);
      attach_req->ms_network_cap_present = true;
    } else {
      attach_req->ms_network_cap_present = false;
    }

    // Old Location Area ID
    if (LIBLTE_MME_LOCATION_AREA_IDENTIFICATION_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_location_area_id_ie(&msg_ptr, &attach_req->old_lai);
      attach_req->old_lai_present = true;
    } else {
      attach_req->old_lai_present = false;
    }

    // TMSI Status
    if ((LIBLTE_MME_TMSI_STATUS_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_tmsi_status_ie(&msg_ptr, 0, &attach_req->tmsi_status);
      msg_ptr++;
      attach_req->tmsi_status_present = true;
    } else {
      attach_req->tmsi_status_present = false;
    }

    // Mobile Station Classmark 2
    if (LIBLTE_MME_MS_CLASSMARK_2_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_mobile_station_classmark_2_ie(&msg_ptr, &attach_req->ms_cm2);
      attach_req->ms_cm2_present = true;
    } else {
      attach_req->ms_cm2_present = false;
    }

    // Mobile Station Classmark 3
    if (LIBLTE_MME_MS_CLASSMARK_3_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_mobile_station_classmark_3_ie(&msg_ptr, &attach_req->ms_cm3);
      attach_req->ms_cm3_present = true;
    } else {
      attach_req->ms_cm3_present = false;
    }

    // Supported Codecs
    if (LIBLTE_MME_SUPPORTED_CODEC_LIST_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_supported_codec_list_ie(&msg_ptr, &attach_req->supported_codecs);
      attach_req->supported_codecs_present = true;
    } else {
      attach_req->supported_codecs_present = false;
    }

    // Additional Update Type
    if ((LIBLTE_MME_ADDITIONAL_UPDATE_TYPE_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_additional_update_type_ie(&msg_ptr, 0, &attach_req->additional_update_type);
      msg_ptr++;
      attach_req->additional_update_type_present = true;
    } else {
      attach_req->additional_update_type_present = false;
    }

    // Voice Domain Preference and UE's Usage Setting
    if (LIBLTE_MME_VOICE_DOMAIN_PREF_AND_UE_USAGE_SETTING_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_voice_domain_pref_and_ue_usage_setting_ie(&msg_ptr,
                                                                  &attach_req->voice_domain_pref_and_ue_usage_setting);
      attach_req->voice_domain_pref_and_ue_usage_setting_present = true;
    } else {
      attach_req->voice_domain_pref_and_ue_usage_setting_present = false;
    }

    // Device Properties
    if ((LIBLTE_MME_ATTACH_REQUEST_DEVICE_PROPERTIES_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_device_properties_ie(&msg_ptr, 0, &attach_req->device_properties);
      msg_ptr++;
      attach_req->device_properties_present = true;
    } else {
      attach_req->device_properties_present = false;
    }

    // Old GUTI Type
    if ((LIBLTE_MME_GUTI_TYPE_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_guti_type_ie(&msg_ptr, 0, &attach_req->old_guti_type);
      msg_ptr++;
      attach_req->old_guti_type_present = true;
    } else {
      attach_req->old_guti_type_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Authentication Failure

    Description: Sent by the UE to the network to indicate that
                 authentication of the network has failed.

    Document Reference: 24.301 v10.2.0 Section 8.2.5
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_failure_msg(LIBLTE_MME_AUTHENTICATION_FAILURE_MSG_STRUCT* auth_fail,
                                                             LIBLTE_BYTE_MSG_STRUCT*                       msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (auth_fail != NULL && msg != NULL) {
    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_AUTHENTICATION_FAILURE;
    msg_ptr++;

    // EMM Cause
    liblte_mme_pack_emm_cause_ie(auth_fail->emm_cause, &msg_ptr);

    // Authentication Failure Parameter
    if (auth_fail->auth_fail_param_present) {
      *msg_ptr = LIBLTE_MME_AUTHENTICATION_FAILURE_PARAMETER_IEI;
      msg_ptr++;
      liblte_mme_pack_authentication_failure_parameter_ie(auth_fail->auth_fail_param, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_authentication_failure_msg(LIBLTE_BYTE_MSG_STRUCT*                       msg,
                                                               LIBLTE_MME_AUTHENTICATION_FAILURE_MSG_STRUCT* auth_fail)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && auth_fail != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EMM Cause
    liblte_mme_unpack_emm_cause_ie(&msg_ptr, &auth_fail->emm_cause);

    // Authentication Failure Parameter
    if (LIBLTE_MME_AUTHENTICATION_FAILURE_PARAMETER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_authentication_failure_parameter_ie(&msg_ptr, auth_fail->auth_fail_param);
      auth_fail->auth_fail_param_present = true;
    } else {
      auth_fail->auth_fail_param_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Authentication Reject

    Description: Sent by the network to the UE to indicate that the
                 authentication procedure has failed and that the UE
                 shall abort all activities.

    Document Reference: 24.301 v10.2.0 Section 8.2.6
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_reject_msg(LIBLTE_MME_AUTHENTICATION_REJECT_MSG_STRUCT* auth_reject,
                                                            LIBLTE_BYTE_MSG_STRUCT*                      msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (auth_reject != NULL && msg != NULL) {
    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_AUTHENTICATION_REJECT;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_authentication_reject_msg(LIBLTE_BYTE_MSG_STRUCT*                      msg,
                                                              LIBLTE_MME_AUTHENTICATION_REJECT_MSG_STRUCT* auth_reject)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && auth_reject != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Authentication Request

    Description: Sent by the network to the UE to initiate
                 authentication of the UE identity.

    Document Reference: 24.301 v10.2.0 Section 8.2.7
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_request_msg(LIBLTE_MME_AUTHENTICATION_REQUEST_MSG_STRUCT* auth_req,
                                                             LIBLTE_BYTE_MSG_STRUCT*                       msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (auth_req != NULL && msg != NULL) {
    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_AUTHENTICATION_REQUEST;
    msg_ptr++;

    // NAS Key Set Identifier & Spare Half Octet
    *msg_ptr = 0;
    liblte_mme_pack_nas_key_set_id_ie(&auth_req->nas_ksi, 0, &msg_ptr);
    msg_ptr++;

    // Authentication Parameter RAND
    liblte_mme_pack_authentication_parameter_rand_ie(auth_req->rand, &msg_ptr);

    // Authentication Parameter AUTN
    liblte_mme_pack_authentication_parameter_autn_ie(auth_req->autn, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_authentication_request_msg(LIBLTE_BYTE_MSG_STRUCT*                       msg,
                                                               LIBLTE_MME_AUTHENTICATION_REQUEST_MSG_STRUCT* auth_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && auth_req != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // NAS Key Set Identifier & Spare Half Octet
    liblte_mme_unpack_nas_key_set_id_ie(&msg_ptr, 0, &auth_req->nas_ksi);
    msg_ptr++;

    // Authentication Parameter RAND
    liblte_mme_unpack_authentication_parameter_rand_ie(&msg_ptr, auth_req->rand);

    // Authentication Parameter AUTN
    liblte_mme_unpack_authentication_parameter_autn_ie(&msg_ptr, auth_req->autn);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Authentication Response

    Description: Sent by the UE to the network to deliver a calculated
                 authentication response to the network.

    Document Reference: 24.301 v10.2.0 Section 8.2.8
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_authentication_response_msg(LIBLTE_MME_AUTHENTICATION_RESPONSE_MSG_STRUCT* auth_resp,
                                                              uint8                   sec_hdr_type,
                                                              uint32                  count,
                                                              LIBLTE_BYTE_MSG_STRUCT* msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (auth_resp != NULL && msg != NULL) {

    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_AUTHENTICATION_RESPONSE;
    msg_ptr++;

    // Authentication Response Parameter (RES)
    liblte_mme_pack_authentication_response_parameter_ie(auth_resp->res, auth_resp->res_len, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_authentication_response_msg(LIBLTE_BYTE_MSG_STRUCT*                        msg,
                                              LIBLTE_MME_AUTHENTICATION_RESPONSE_MSG_STRUCT* auth_resp)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && auth_resp != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Authentication Response Parameter (RES)
    liblte_mme_unpack_authentication_response_parameter_ie(&msg_ptr, auth_resp->res);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: CS Service Notification

    Description: Sent by the network when a paging request with CS
                 call indicator was received via SGs for a UE, and a
                 NAS signalling connection is already established for
                 the UE.

    Document Reference: 24.301 v10.2.0 Section 8.2.9
*********************************************************************/
// TODO

/*********************************************************************
    Message Name: Detach Accept

    Description: Sent by the network to indicate that the detach
                 procedure has been completed.

    Document Reference: 24.301 v10.2.0 Section 8.2.10
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_detach_accept_msg(LIBLTE_MME_DETACH_ACCEPT_MSG_STRUCT* detach_accept,
                                                    uint8                                sec_hdr_type,
                                                    uint32                               count,
                                                    LIBLTE_BYTE_MSG_STRUCT*              msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (detach_accept != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_DETACH_ACCEPT;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_detach_accept_msg(LIBLTE_BYTE_MSG_STRUCT*              msg,
                                                      LIBLTE_MME_DETACH_ACCEPT_MSG_STRUCT* detach_accept)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && detach_accept != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Detach Request

    Description: Sent by the UE to request the release of an EMM
                 context.

    Document Reference: 24.301 v10.2.0 Section 8.2.11
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_detach_request_msg(LIBLTE_MME_DETACH_REQUEST_MSG_STRUCT* detach_req,
                                                     uint8                                 sec_hdr_type,
                                                     uint32                                count,
                                                     LIBLTE_BYTE_MSG_STRUCT*               msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (detach_req != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_DETACH_REQUEST;
    msg_ptr++;

    // Detach Type & NAS Key Set Identifier
    *msg_ptr = 0;
    liblte_mme_pack_detach_type_ie(&detach_req->detach_type, 0, &msg_ptr);
    liblte_mme_pack_nas_key_set_id_ie(&detach_req->nas_ksi, 4, &msg_ptr);
    msg_ptr++;

    // EPS Mobile ID
    liblte_mme_pack_eps_mobile_id_ie(&detach_req->eps_mobile_id, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_detach_request_msg(LIBLTE_BYTE_MSG_STRUCT*               msg,
                                                       LIBLTE_MME_DETACH_REQUEST_MSG_STRUCT* detach_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && detach_req != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Detach Type & NAS Key Set Identifier
    liblte_mme_unpack_detach_type_ie(&msg_ptr, 0, &detach_req->detach_type);
    liblte_mme_unpack_nas_key_set_id_ie(&msg_ptr, 4, &detach_req->nas_ksi);
    msg_ptr++;

    // EPS Mobile ID
    liblte_mme_unpack_eps_mobile_id_ie(&msg_ptr, &detach_req->eps_mobile_id);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Downlink NAS Transport

    Description: Sent by the network to the UE in order to carry an
                 SMS message in encapsulated format.

    Document Reference: 24.301 v10.2.0 Section 8.2.12
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_downlink_nas_transport_msg(LIBLTE_MME_DOWNLINK_NAS_TRANSPORT_MSG_STRUCT* dl_nas_transport,
                                           uint8                                         sec_hdr_type,
                                           uint32                                        count,
                                           LIBLTE_BYTE_MSG_STRUCT*                       msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (dl_nas_transport != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_DOWNLINK_NAS_TRANSPORT;
    msg_ptr++;

    // NAS Message Container
    liblte_mme_pack_nas_message_container_ie(&dl_nas_transport->nas_msg, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_downlink_nas_transport_msg(LIBLTE_BYTE_MSG_STRUCT*                       msg,
                                             LIBLTE_MME_DOWNLINK_NAS_TRANSPORT_MSG_STRUCT* dl_nas_transport)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && dl_nas_transport != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // NAS Message Container
    liblte_mme_unpack_nas_message_container_ie(&msg_ptr, &dl_nas_transport->nas_msg);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: EMM Information

    Description: Sent by the network at any time during EMM context is
                 established to send certain information to the UE.

    Document Reference: 24.301 v10.2.0 Section 8.2.13
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_emm_information_msg(LIBLTE_MME_EMM_INFORMATION_MSG_STRUCT* emm_info,
                                                      uint8                                  sec_hdr_type,
                                                      uint32                                 count,
                                                      LIBLTE_BYTE_MSG_STRUCT*                msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (emm_info != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_EMM_INFORMATION;
    msg_ptr++;

    // Full Name For Network
    if (emm_info->full_net_name_present) {
      *msg_ptr = LIBLTE_MME_FULL_NAME_FOR_NETWORK_IEI;
      msg_ptr++;
      liblte_mme_pack_network_name_ie(&emm_info->full_net_name, &msg_ptr);
    }

    // Short Name For Network
    if (emm_info->short_net_name_present) {
      *msg_ptr = LIBLTE_MME_SHORT_NAME_FOR_NETWORK_IEI;
      msg_ptr++;
      liblte_mme_pack_network_name_ie(&emm_info->short_net_name, &msg_ptr);
    }

    // Local Time Zone
    if (emm_info->local_time_zone_present) {
      *msg_ptr = LIBLTE_MME_LOCAL_TIME_ZONE_IEI;
      msg_ptr++;
      liblte_mme_pack_time_zone_ie(emm_info->local_time_zone, &msg_ptr);
    }

    // Universal Time And Local Time Zone
    if (emm_info->utc_and_local_time_zone_present) {
      *msg_ptr = LIBLTE_MME_UNIVERSAL_TIME_AND_LOCAL_TIME_ZONE_IEI;
      msg_ptr++;
      liblte_mme_pack_time_zone_and_time_ie(&emm_info->utc_and_local_time_zone, &msg_ptr);
    }

    // Network Daylight Saving Time
    if (emm_info->net_dst_present) {
      *msg_ptr = LIBLTE_MME_NETWORK_DAYLIGHT_SAVING_TIME_IEI;
      msg_ptr++;
      liblte_mme_pack_daylight_saving_time_ie(emm_info->net_dst, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_emm_information_msg(LIBLTE_BYTE_MSG_STRUCT*                msg,
                                                        LIBLTE_MME_EMM_INFORMATION_MSG_STRUCT* emm_info)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && emm_info != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Full Name For Network
    if (LIBLTE_MME_FULL_NAME_FOR_NETWORK_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_network_name_ie(&msg_ptr, &emm_info->full_net_name);
      emm_info->full_net_name_present = true;
    } else {
      emm_info->full_net_name_present = false;
    }

    // Short Name For Network
    if (LIBLTE_MME_SHORT_NAME_FOR_NETWORK_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_network_name_ie(&msg_ptr, &emm_info->short_net_name);
      emm_info->short_net_name_present = true;
    } else {
      emm_info->short_net_name_present = false;
    }

    // Local Time Zone
    if (LIBLTE_MME_LOCAL_TIME_ZONE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_time_zone_ie(&msg_ptr, &emm_info->local_time_zone);
      emm_info->local_time_zone_present = true;
    } else {
      emm_info->local_time_zone_present = false;
    }

    // Universal Time And Local Time Zone
    if (LIBLTE_MME_UNIVERSAL_TIME_AND_LOCAL_TIME_ZONE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_time_zone_and_time_ie(&msg_ptr, &emm_info->utc_and_local_time_zone);
      emm_info->utc_and_local_time_zone_present = true;
    } else {
      emm_info->utc_and_local_time_zone_present = false;
    }

    // Network Daylight Saving Time
    if (LIBLTE_MME_NETWORK_DAYLIGHT_SAVING_TIME_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_daylight_saving_time_ie(&msg_ptr, &emm_info->net_dst);
      emm_info->net_dst_present = true;
    } else {
      emm_info->net_dst_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: EMM Status

    Description: Sent by the UE or by the network at any time to
                 report certain error conditions.

    Document Reference: 24.301 v10.2.0 Section 8.2.14
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_emm_status_msg(LIBLTE_MME_EMM_STATUS_MSG_STRUCT* emm_status,
                                                 uint8                             sec_hdr_type,
                                                 uint32                            count,
                                                 LIBLTE_BYTE_MSG_STRUCT*           msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (emm_status != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_EMM_STATUS;
    msg_ptr++;

    // EMM Cause
    liblte_mme_pack_emm_cause_ie(emm_status->emm_cause, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_emm_status_msg(LIBLTE_BYTE_MSG_STRUCT*           msg,
                                                   LIBLTE_MME_EMM_STATUS_MSG_STRUCT* emm_status)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && emm_status != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EMM Cause
    liblte_mme_unpack_emm_cause_ie(&msg_ptr, &emm_status->emm_cause);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Extended Service Request

    Description: Sent by the UE to the network to initiate a CS
                 fallback or 1xCS fallback call or respond to a mobile
                 terminated CS fallback or 1xCS fallback request from
                 the network or to request the establishment of a NAS
                 signalling connection and of the radio and S1 bearers
                 for packet services, if the UE needs to provide
                 additional information that cannot be provided via a
                 SERVICE REQUEST message.

    Document Reference: 24.301 v10.2.0 Section 8.2.15
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_extended_service_request_msg(LIBLTE_MME_EXTENDED_SERVICE_REQUEST_MSG_STRUCT* ext_service_req,
                                             uint8                                           sec_hdr_type,
                                             uint32                                          count,
                                             LIBLTE_BYTE_MSG_STRUCT*                         msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (ext_service_req != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_EXTENDED_SERVICE_REQUEST;
    msg_ptr++;

    // Service Type & NAS Key Set Identifier
    *msg_ptr = 0;
    liblte_mme_pack_service_type_ie(ext_service_req->service_type, 0, &msg_ptr);
    liblte_mme_pack_nas_key_set_id_ie(&ext_service_req->nas_ksi, 4, &msg_ptr);
    msg_ptr++;

    // M-TMSI
    liblte_mme_pack_mobile_id_ie(&ext_service_req->m_tmsi, &msg_ptr);

    // CSFB Response
    if (ext_service_req->csfb_resp_present) {
      *msg_ptr = LIBLTE_MME_CSFB_RESPONSE_IEI << 4;
      liblte_mme_pack_csfb_response_ie(ext_service_req->csfb_resp, 0, &msg_ptr);
      msg_ptr++;
    }

    // EPS Bearer Context Status
    if (ext_service_req->eps_bearer_context_status_present) {
      *msg_ptr = LIBLTE_MME_EPS_BEARER_CONTEXT_STATUS_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_bearer_context_status_ie(&ext_service_req->eps_bearer_context_status, &msg_ptr);
    }

    // Device Properties
    if (ext_service_req->device_props_present) {
      *msg_ptr = LIBLTE_MME_EXTENDED_SERVICE_REQUEST_DEVICE_PROPERTIES_IEI << 4;
      liblte_mme_pack_device_properties_ie(ext_service_req->device_props, 0, &msg_ptr);
      msg_ptr++;
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_extended_service_request_msg(LIBLTE_BYTE_MSG_STRUCT*                         msg,
                                               LIBLTE_MME_EXTENDED_SERVICE_REQUEST_MSG_STRUCT* ext_service_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && ext_service_req != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Service Type & NAS Key Set Identifier
    liblte_mme_unpack_service_type_ie(&msg_ptr, 0, &ext_service_req->service_type);
    liblte_mme_unpack_nas_key_set_id_ie(&msg_ptr, 4, &ext_service_req->nas_ksi);
    msg_ptr++;

    // M-TMSI
    liblte_mme_unpack_mobile_id_ie(&msg_ptr, &ext_service_req->m_tmsi);

    // CSFB Response
    if ((LIBLTE_MME_CSFB_RESPONSE_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_csfb_response_ie(&msg_ptr, 0, &ext_service_req->csfb_resp);
      msg_ptr++;
      ext_service_req->csfb_resp_present = true;
    } else {
      ext_service_req->csfb_resp_present = false;
    }

    // EPS Bearer Context Status
    if (LIBLTE_MME_EPS_BEARER_CONTEXT_STATUS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_bearer_context_status_ie(&msg_ptr, &ext_service_req->eps_bearer_context_status);
      ext_service_req->eps_bearer_context_status_present = true;
    } else {
      ext_service_req->eps_bearer_context_status_present = false;
    }

    // Device Properties
    if ((LIBLTE_MME_EXTENDED_SERVICE_REQUEST_DEVICE_PROPERTIES_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_device_properties_ie(&msg_ptr, 0, &ext_service_req->device_props);
      msg_ptr++;
      ext_service_req->device_props_present = true;
    } else {
      ext_service_req->device_props_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: GUTI Reallocation Command

    Description: Sent by the network to the UE to reallocate a GUTI
                 and optionally provide a new TAI list.

    Document Reference: 24.301 v10.2.0 Section 8.2.16
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_guti_reallocation_command_msg(LIBLTE_MME_GUTI_REALLOCATION_COMMAND_MSG_STRUCT* guti_realloc_cmd,
                                              uint8                                            sec_hdr_type,
                                              uint32                                           count,
                                              LIBLTE_BYTE_MSG_STRUCT*                          msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (guti_realloc_cmd != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_GUTI_REALLOCATION_COMMAND;
    msg_ptr++;

    // GUTI
    liblte_mme_pack_eps_mobile_id_ie(&guti_realloc_cmd->guti, &msg_ptr);

    // TAI List
    if (guti_realloc_cmd->tai_list_present) {
      *msg_ptr = LIBLTE_MME_TAI_LIST_IEI;
      msg_ptr++;
      liblte_mme_pack_tracking_area_identity_list_ie(&guti_realloc_cmd->tai_list, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_guti_reallocation_command_msg(LIBLTE_BYTE_MSG_STRUCT*                          msg,
                                                LIBLTE_MME_GUTI_REALLOCATION_COMMAND_MSG_STRUCT* guti_realloc_cmd)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && guti_realloc_cmd != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // GUTI
    liblte_mme_unpack_eps_mobile_id_ie(&msg_ptr, &guti_realloc_cmd->guti);

    // TAI List
    if (LIBLTE_MME_TAI_LIST_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_tracking_area_identity_list_ie(&msg_ptr, &guti_realloc_cmd->tai_list);
      guti_realloc_cmd->tai_list_present = true;
    } else {
      guti_realloc_cmd->tai_list_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: GUTI Reallocation Complete

    Description: Sent by the UE to the network to indicate that
                 reallocation of a GUTI has taken place.

    Document Reference: 24.301 v10.2.0 Section 8.2.17
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_guti_reallocation_complete_msg(LIBLTE_MME_GUTI_REALLOCATION_COMPLETE_MSG_STRUCT* guti_realloc_complete,
                                               uint8                                             sec_hdr_type,
                                               uint32                                            count,
                                               LIBLTE_BYTE_MSG_STRUCT*                           msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (guti_realloc_complete != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_GUTI_REALLOCATION_COMPLETE;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_guti_reallocation_complete_msg(
    LIBLTE_BYTE_MSG_STRUCT*                           msg,
    LIBLTE_MME_GUTI_REALLOCATION_COMPLETE_MSG_STRUCT* guti_realloc_complete)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && guti_realloc_complete != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Identity Request

    Description: Sent by the network to the UE to request the UE to
                 provide the specified identity.

    Document Reference: 24.301 v10.2.0 Section 8.2.18
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_identity_request_msg(LIBLTE_MME_ID_REQUEST_MSG_STRUCT* id_req,
                                                       LIBLTE_BYTE_MSG_STRUCT*           msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (id_req != NULL && msg != NULL) {
    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_IDENTITY_REQUEST;
    msg_ptr++;

    // ID Type & Spare Half Octet
    *msg_ptr = 0;
    liblte_mme_pack_identity_type_2_ie(id_req->id_type, 0, &msg_ptr);
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_identity_request_msg(LIBLTE_BYTE_MSG_STRUCT*           msg,
                                                         LIBLTE_MME_ID_REQUEST_MSG_STRUCT* id_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && id_req != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // ID Type & Spare Half Offset
    liblte_mme_unpack_identity_type_2_ie(&msg_ptr, 0, &id_req->id_type);
    msg_ptr++;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
/*********************************************************************
    Message Name: Identity Response

    Description: Sent by the UE to the network in response to an
                 IDENTITY REQUEST message and provides the requested
                 identity.

    Document Reference: 24.301 v10.2.0 Section 8.2.19
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_identity_response_msg(LIBLTE_MME_ID_RESPONSE_MSG_STRUCT* id_resp,
                                                        uint8                              sec_hdr_type,
                                                        uint32                             count,
                                                        LIBLTE_BYTE_MSG_STRUCT*            msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (id_resp != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_IDENTITY_RESPONSE;
    msg_ptr++;

    // Mobile Identity
    liblte_mme_pack_mobile_id_ie(&id_resp->mobile_id, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

LIBLTE_ERROR_ENUM liblte_mme_unpack_identity_response_msg(LIBLTE_BYTE_MSG_STRUCT*            msg,
                                                          LIBLTE_MME_ID_RESPONSE_MSG_STRUCT* id_resp)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && id_resp != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Mobile Identity
    liblte_mme_unpack_mobile_id_ie(&msg_ptr, &id_resp->mobile_id);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Security Mode Command

    Description: Sent by the network to the UE to establish NAS
                 signalling security.

    Document Reference: 24.301 v10.2.0 Section 8.2.20
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_security_mode_command_msg(LIBLTE_MME_SECURITY_MODE_COMMAND_MSG_STRUCT* sec_mode_cmd,
                                                            uint8                                        sec_hdr_type,
                                                            uint32                                       count,
                                                            LIBLTE_BYTE_MSG_STRUCT*                      msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (sec_mode_cmd != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_SECURITY_MODE_COMMAND;
    msg_ptr++;

    // Selected NAS Security Algorithms
    liblte_mme_pack_nas_security_algorithms_ie(&sec_mode_cmd->selected_nas_sec_algs, &msg_ptr);

    // NAS Key Set Identifier & Spare Half Octet
    *msg_ptr = 0;
    liblte_mme_pack_nas_key_set_id_ie(&sec_mode_cmd->nas_ksi, 0, &msg_ptr);
    msg_ptr++;

    // Replayed UE Security Capabilities
    liblte_mme_pack_ue_security_capabilities_ie(&sec_mode_cmd->ue_security_cap, &msg_ptr);

    // IMEISV Request
    if (sec_mode_cmd->imeisv_req_present) {
      *msg_ptr = LIBLTE_MME_IMEISV_REQUEST_IEI << 4;
      liblte_mme_pack_imeisv_request_ie(sec_mode_cmd->imeisv_req, 0, &msg_ptr);
      msg_ptr++;
    }

    // Replayed NONCE_ue
    if (sec_mode_cmd->nonce_ue_present) {
      *msg_ptr = LIBLTE_MME_REPLAYED_NONCE_UE_IEI;
      msg_ptr++;
      liblte_mme_pack_nonce_ie(sec_mode_cmd->nonce_ue, &msg_ptr);
    }

    // NONCE_mme
    if (sec_mode_cmd->nonce_mme_present) {
      *msg_ptr = LIBLTE_MME_NONCE_MME_IEI;
      msg_ptr++;
      liblte_mme_pack_nonce_ie(sec_mode_cmd->nonce_mme, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_security_mode_command_msg(LIBLTE_BYTE_MSG_STRUCT*                      msg,
                                                              LIBLTE_MME_SECURITY_MODE_COMMAND_MSG_STRUCT* sec_mode_cmd)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && sec_mode_cmd != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Selected NAS Security Algorithms
    liblte_mme_unpack_nas_security_algorithms_ie(&msg_ptr, &sec_mode_cmd->selected_nas_sec_algs);

    // NAS Key Set Identifier & Spare Half Octet
    liblte_mme_unpack_nas_key_set_id_ie(&msg_ptr, 0, &sec_mode_cmd->nas_ksi);
    msg_ptr++;

    // Replayed UE Security Capabilities
    liblte_mme_unpack_ue_security_capabilities_ie(&msg_ptr, &sec_mode_cmd->ue_security_cap);

    // IMEISV Request
    if ((LIBLTE_MME_IMEISV_REQUEST_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_imeisv_request_ie(&msg_ptr, 0, &sec_mode_cmd->imeisv_req);
      msg_ptr++;
      sec_mode_cmd->imeisv_req_present = true;
    } else {
      sec_mode_cmd->imeisv_req_present = false;
    }

    // Replayed NONCE_ue
    if (LIBLTE_MME_REPLAYED_NONCE_UE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_nonce_ie(&msg_ptr, &sec_mode_cmd->nonce_ue);
      sec_mode_cmd->nonce_ue_present = true;
    } else {
      sec_mode_cmd->nonce_ue_present = false;
    }

    // NONCE_mme
    if (LIBLTE_MME_NONCE_MME_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_nonce_ie(&msg_ptr, &sec_mode_cmd->nonce_mme);
      sec_mode_cmd->nonce_mme_present = true;
    } else {
      sec_mode_cmd->nonce_mme_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Security Mode Complete

    Description: Sent by the UE to the network in response to a
                 SECURITY MODE COMMAND message.

    Document Reference: 24.301 v10.2.0 Section 8.2.21
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_security_mode_complete_msg(LIBLTE_MME_SECURITY_MODE_COMPLETE_MSG_STRUCT* sec_mode_comp,
                                           uint8                                         sec_hdr_type,
                                           uint32                                        count,
                                           LIBLTE_BYTE_MSG_STRUCT*                       msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (sec_mode_comp != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_SECURITY_MODE_COMPLETE;
    msg_ptr++;

    // IMEISV
    if (sec_mode_comp->imeisv_present) {
      *msg_ptr = LIBLTE_MME_IMEISV_IEI;
      msg_ptr++;
      liblte_mme_pack_mobile_id_ie(&sec_mode_comp->imeisv, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_security_mode_complete_msg(LIBLTE_BYTE_MSG_STRUCT*                       msg,
                                             LIBLTE_MME_SECURITY_MODE_COMPLETE_MSG_STRUCT* sec_mode_comp)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && sec_mode_comp != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // IMEISV
    if (LIBLTE_MME_IMEISV_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_mobile_id_ie(&msg_ptr, &sec_mode_comp->imeisv);
      sec_mode_comp->imeisv_present = true;
    } else {
      sec_mode_comp->imeisv_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Security Mode Reject

    Description: Sent by the UE to the network to indicate that the
                 corresponding security mode command has been
                 rejected.

    Document Reference: 24.301 v10.2.0 Section 8.2.22
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_security_mode_reject_msg(LIBLTE_MME_SECURITY_MODE_REJECT_MSG_STRUCT* sec_mode_rej,
                                                           LIBLTE_BYTE_MSG_STRUCT*                     msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (sec_mode_rej != NULL && msg != NULL) {
    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_SECURITY_MODE_REJECT;
    msg_ptr++;

    // EMM Cause
    liblte_mme_pack_emm_cause_ie(sec_mode_rej->emm_cause, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_security_mode_reject_msg(LIBLTE_BYTE_MSG_STRUCT*                     msg,
                                                             LIBLTE_MME_SECURITY_MODE_REJECT_MSG_STRUCT* sec_mode_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && sec_mode_rej != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EMM Cause
    liblte_mme_unpack_emm_cause_ie(&msg_ptr, &sec_mode_rej->emm_cause);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Service Reject

    Description: Sent by the network to the UE in order to reject the
                 service request procedure.

    Document Reference: 24.301 v10.2.0 Section 8.2.24
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_service_reject_msg(LIBLTE_MME_SERVICE_REJECT_MSG_STRUCT* service_rej,
                                                     uint8                                 sec_hdr_type,
                                                     uint32                                count,
                                                     LIBLTE_BYTE_MSG_STRUCT*               msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (service_rej != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_SERVICE_REJECT;
    msg_ptr++;

    // EMM Cause
    liblte_mme_pack_emm_cause_ie(service_rej->emm_cause, &msg_ptr);

    // T3442 Value
    if (service_rej->t3442_present) {
      *msg_ptr = LIBLTE_MME_T3442_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_ie(&service_rej->t3442, &msg_ptr);
    }

    // T3446 Value
    if (service_rej->t3446_present) {
      *msg_ptr = LIBLTE_MME_T3446_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_2_ie(service_rej->t3446, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_service_reject_msg(LIBLTE_BYTE_MSG_STRUCT*               msg,
                                                       LIBLTE_MME_SERVICE_REJECT_MSG_STRUCT* service_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && service_rej != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EMM Cause
    liblte_mme_unpack_emm_cause_ie(&msg_ptr, &service_rej->emm_cause);

    // T3442 Value
    if (LIBLTE_MME_T3442_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_ie(&msg_ptr, &service_rej->t3442);
      service_rej->t3442_present = true;
    } else {
      service_rej->t3442_present = false;
    }

    // T3446 Value
    if (LIBLTE_MME_T3446_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_2_ie(&msg_ptr, &service_rej->t3446);
      service_rej->t3446_present = true;
    } else {
      service_rej->t3446_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Service Request

    Description: Sent by the UE to the network to request the
                 establishment of a NAS signalling connection and of
                 the radio and S1 bearers.

    Document Reference: 24.301 v10.2.0 Section 8.2.25
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_service_request_msg(LIBLTE_MME_SERVICE_REQUEST_MSG_STRUCT* service_req,
                                                      LIBLTE_BYTE_MSG_STRUCT*                msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (service_req != NULL && msg != NULL) {
    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_SERVICE_REQUEST << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // KSI and Sequence Number
    liblte_mme_pack_ksi_and_sequence_number_ie(&service_req->ksi_and_seq_num, &msg_ptr);

    // Short MAC
    liblte_mme_pack_short_mac_ie(service_req->short_mac, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_service_request_msg(LIBLTE_BYTE_MSG_STRUCT*                msg,
                                                        LIBLTE_MME_SERVICE_REQUEST_MSG_STRUCT* service_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && service_req != NULL) {
    // Protocol Discriminator and Security Header Type
    msg_ptr++;

    // KSI and Sequence Number
    liblte_mme_unpack_ksi_and_sequence_number_ie(&msg_ptr, &service_req->ksi_and_seq_num);

    // Short MAC
    liblte_mme_unpack_short_mac_ie(&msg_ptr, &service_req->short_mac);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Tracking Area Update Accept

    Description: Sent by the network to the UE to provide the UE with
                 EPS mobility management related data in response to
                 a tracking area update request message.

    Document Reference: 24.301 v10.2.0 Section 8.2.26
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_tracking_area_update_accept_msg(LIBLTE_MME_TRACKING_AREA_UPDATE_ACCEPT_MSG_STRUCT* ta_update_accept,
                                                uint8                                              sec_hdr_type,
                                                uint32                                             count,
                                                LIBLTE_BYTE_MSG_STRUCT*                            msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (ta_update_accept != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_TRACKING_AREA_UPDATE_ACCEPT;
    msg_ptr++;

    // EPS Update Result & Spare Half Octet
    *msg_ptr = 0;
    liblte_mme_pack_eps_update_result_ie(ta_update_accept->eps_update_result, 0, &msg_ptr);
    msg_ptr++;

    // T3412 Value
    if (ta_update_accept->t3412_present) {
      *msg_ptr = LIBLTE_MME_T3412_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_ie(&ta_update_accept->t3412, &msg_ptr);
    }

    // GUTI
    if (ta_update_accept->guti_present) {
      *msg_ptr = LIBLTE_MME_GUTI_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_mobile_id_ie(&ta_update_accept->guti, &msg_ptr);
    }

    // TAI List
    if (ta_update_accept->tai_list_present) {
      *msg_ptr = LIBLTE_MME_TAI_LIST_IEI;
      msg_ptr++;
      liblte_mme_pack_tracking_area_identity_list_ie(&ta_update_accept->tai_list, &msg_ptr);
    }

    // EPS Bearer Context Status
    if (ta_update_accept->eps_bearer_context_status_present) {
      *msg_ptr = LIBLTE_MME_EPS_BEARER_CONTEXT_STATUS_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_bearer_context_status_ie(&ta_update_accept->eps_bearer_context_status, &msg_ptr);
    }

    // Location Area Identification
    if (ta_update_accept->lai_present) {
      *msg_ptr = LIBLTE_MME_LOCATION_AREA_IDENTIFICATION_IEI;
      msg_ptr++;
      liblte_mme_pack_location_area_id_ie(&ta_update_accept->lai, &msg_ptr);
    }

    // MS Identity
    if (ta_update_accept->ms_id_present) {
      *msg_ptr = LIBLTE_MME_MS_IDENTITY_IEI;
      msg_ptr++;
      liblte_mme_pack_mobile_id_ie(&ta_update_accept->ms_id, &msg_ptr);
    }

    // EMM Cause
    if (ta_update_accept->emm_cause_present) {
      *msg_ptr = LIBLTE_MME_EMM_CAUSE_IEI;
      msg_ptr++;
      liblte_mme_pack_emm_cause_ie(ta_update_accept->emm_cause, &msg_ptr);
    }

    // T3402 Value
    if (ta_update_accept->t3402_present) {
      *msg_ptr = LIBLTE_MME_T3402_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_ie(&ta_update_accept->t3402, &msg_ptr);
    }

    // T3423 Value
    if (ta_update_accept->t3423_present) {
      *msg_ptr = LIBLTE_MME_T3423_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_ie(&ta_update_accept->t3423, &msg_ptr);
    }

    // Equivalent PLMNs
    if (ta_update_accept->equivalent_plmns_present) {
      *msg_ptr = LIBLTE_MME_EQUIVALENT_PLMNS_IEI;
      msg_ptr++;
      liblte_mme_pack_plmn_list_ie(&ta_update_accept->equivalent_plmns, &msg_ptr);
    }

    // Emergency Number List
    if (ta_update_accept->emerg_num_list_present) {
      *msg_ptr = LIBLTE_MME_EMERGENCY_NUMBER_LIST_IEI;
      msg_ptr++;
      liblte_mme_pack_emergency_number_list_ie(&ta_update_accept->emerg_num_list, &msg_ptr);
    }

    // EPS Network Feature Support
    if (ta_update_accept->eps_network_feature_support_present) {
      *msg_ptr = LIBLTE_MME_EPS_NETWORK_FEATURE_SUPPORT_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_network_feature_support_ie(&ta_update_accept->eps_network_feature_support, &msg_ptr);
    }

    // Additional Update Result
    if (ta_update_accept->additional_update_result_present) {
      *msg_ptr = LIBLTE_MME_ADDITIONAL_UPDATE_RESULT_IEI << 4;
      liblte_mme_pack_additional_update_result_ie(ta_update_accept->additional_update_result, 0, &msg_ptr);
      msg_ptr++;
    }

    // T3412 Extended Value
    if (ta_update_accept->t3412_ext_present) {
      *msg_ptr = LIBLTE_MME_T3412_EXTENDED_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_3_ie(&ta_update_accept->t3412_ext, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_tracking_area_update_accept_msg(LIBLTE_BYTE_MSG_STRUCT*                            msg,
                                                  LIBLTE_MME_TRACKING_AREA_UPDATE_ACCEPT_MSG_STRUCT* ta_update_accept)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && ta_update_accept != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EPS Update Result & Spare Half Octet
    liblte_mme_unpack_eps_update_result_ie(&msg_ptr, 0, &ta_update_accept->eps_update_result);
    msg_ptr++;

    // T3412 Value
    if (LIBLTE_MME_T3412_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_ie(&msg_ptr, &ta_update_accept->t3412);
      ta_update_accept->t3412_present = true;
    } else {
      ta_update_accept->t3412_present = false;
    }

    // GUTI
    if (LIBLTE_MME_GUTI_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_mobile_id_ie(&msg_ptr, &ta_update_accept->guti);
      ta_update_accept->guti_present = true;
    } else {
      ta_update_accept->guti_present = false;
    }

    // TAI List
    if (LIBLTE_MME_TAI_LIST_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_tracking_area_identity_list_ie(&msg_ptr, &ta_update_accept->tai_list);
      ta_update_accept->tai_list_present = true;
    } else {
      ta_update_accept->tai_list_present = false;
    }

    // EPS Bearer Context Status
    if (LIBLTE_MME_EPS_BEARER_CONTEXT_STATUS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_bearer_context_status_ie(&msg_ptr, &ta_update_accept->eps_bearer_context_status);
      ta_update_accept->eps_bearer_context_status_present = true;
    } else {
      ta_update_accept->eps_bearer_context_status_present = false;
    }

    // Location Area Identification
    if (LIBLTE_MME_LOCATION_AREA_IDENTIFICATION_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_location_area_id_ie(&msg_ptr, &ta_update_accept->lai);
      ta_update_accept->lai_present = true;
    } else {
      ta_update_accept->lai_present = false;
    }

    // MS Identity
    if (LIBLTE_MME_MS_IDENTITY_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_mobile_id_ie(&msg_ptr, &ta_update_accept->ms_id);
      ta_update_accept->ms_id_present = true;
    } else {
      ta_update_accept->ms_id_present = false;
    }

    // EMM Cause
    if (LIBLTE_MME_EMM_CAUSE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_emm_cause_ie(&msg_ptr, &ta_update_accept->emm_cause);
      ta_update_accept->emm_cause_present = true;
    } else {
      ta_update_accept->emm_cause_present = false;
    }

    // T3402 Value
    if (LIBLTE_MME_T3402_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_ie(&msg_ptr, &ta_update_accept->t3402);
      ta_update_accept->t3402_present = true;
    } else {
      ta_update_accept->t3402_present = false;
    }

    // T3423 Value
    if (LIBLTE_MME_T3423_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_ie(&msg_ptr, &ta_update_accept->t3423);
      ta_update_accept->t3423_present = true;
    } else {
      ta_update_accept->t3423_present = false;
    }

    // Equivalent PLMNs
    if (LIBLTE_MME_EQUIVALENT_PLMNS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_plmn_list_ie(&msg_ptr, &ta_update_accept->equivalent_plmns);
      ta_update_accept->equivalent_plmns_present = true;
    } else {
      ta_update_accept->equivalent_plmns_present = false;
    }

    // Emergency Number List
    if (LIBLTE_MME_EMERGENCY_NUMBER_LIST_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_emergency_number_list_ie(&msg_ptr, &ta_update_accept->emerg_num_list);
      ta_update_accept->emerg_num_list_present = true;
    } else {
      ta_update_accept->emerg_num_list_present = false;
    }

    // EPS Network Feature Support
    if (LIBLTE_MME_EPS_NETWORK_FEATURE_SUPPORT_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_network_feature_support_ie(&msg_ptr, &ta_update_accept->eps_network_feature_support);
      ta_update_accept->eps_network_feature_support_present = true;
    } else {
      ta_update_accept->eps_network_feature_support_present = false;
    }

    // Additional Update Result
    if ((LIBLTE_MME_ADDITIONAL_UPDATE_RESULT_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_additional_update_result_ie(&msg_ptr, 0, &ta_update_accept->additional_update_result);
      msg_ptr++;
      ta_update_accept->additional_update_result_present = true;
    } else {
      ta_update_accept->additional_update_result_present = false;
    }

    // T3412 Extended Value
    if (LIBLTE_MME_T3412_EXTENDED_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_3_ie(&msg_ptr, &ta_update_accept->t3412_ext);
      ta_update_accept->t3412_ext_present = true;
    } else {
      ta_update_accept->t3412_ext_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Tracking Area Update Complete

    Description: Sent by the UE to the network in response to a
                 tracking area update accept message if a GUTI has
                 been changed or a new TMSI has been assigned.

    Document Reference: 24.301 v10.2.0 Section 8.2.27
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_tracking_area_update_complete_msg(
    LIBLTE_MME_TRACKING_AREA_UPDATE_COMPLETE_MSG_STRUCT* ta_update_complete,
    uint8                                                sec_hdr_type,
    uint32                                               count,
    LIBLTE_BYTE_MSG_STRUCT*                              msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (ta_update_complete != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_TRACKING_AREA_UPDATE_COMPLETE;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_tracking_area_update_complete_msg(
    LIBLTE_BYTE_MSG_STRUCT*                              msg,
    LIBLTE_MME_TRACKING_AREA_UPDATE_COMPLETE_MSG_STRUCT* ta_update_complete)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && ta_update_complete != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Tracking Area Update Reject

    Description: Sent by the network to the UE in order to reject the
                 tracking area updating procedure.

    Document Reference: 24.301 v10.2.0 Section 8.2.28
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_tracking_area_update_reject_msg(LIBLTE_MME_TRACKING_AREA_UPDATE_REJECT_MSG_STRUCT* ta_update_rej,
                                                uint8                                              sec_hdr_type,
                                                uint32                                             count,
                                                LIBLTE_BYTE_MSG_STRUCT*                            msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (ta_update_rej != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_TRACKING_AREA_UPDATE_REJECT;
    msg_ptr++;

    // EMM Cause
    liblte_mme_pack_emm_cause_ie(ta_update_rej->emm_cause, &msg_ptr);

    // T3446 Value
    if (ta_update_rej->t3446_present) {
      *msg_ptr = LIBLTE_MME_T3446_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_2_ie(ta_update_rej->t3446, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_tracking_area_update_reject_msg(LIBLTE_BYTE_MSG_STRUCT*                            msg,
                                                  LIBLTE_MME_TRACKING_AREA_UPDATE_REJECT_MSG_STRUCT* ta_update_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && ta_update_rej != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // EMM Cause
    liblte_mme_unpack_emm_cause_ie(&msg_ptr, &ta_update_rej->emm_cause);

    // T3446 Value
    if (LIBLTE_MME_T3446_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_2_ie(&msg_ptr, &ta_update_rej->t3446);
      ta_update_rej->t3446_present = true;
    } else {
      ta_update_rej->t3446_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Tracking Area Update Request

    Description: Sent by the UE to the network to initiate a tracking
                 area updating procedure.

    Document Reference: 24.301 v10.2.0 Section 8.2.29
*********************************************************************/
// TODO

/*********************************************************************
    Message Name: Uplink NAS Transport

    Description: Sent by the UE to the network in order to carry an
                 SMS message in encapsulated format.

    Document Reference: 24.301 v10.2.0 Section 8.2.30
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_uplink_nas_transport_msg(LIBLTE_MME_UPLINK_NAS_TRANSPORT_MSG_STRUCT* ul_nas_transport,
                                                           uint8                                       sec_hdr_type,
                                                           uint32                                      count,
                                                           LIBLTE_BYTE_MSG_STRUCT*                     msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (ul_nas_transport != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_UPLINK_NAS_TRANSPORT;
    msg_ptr++;

    // NAS Message Container
    liblte_mme_pack_nas_message_container_ie(&ul_nas_transport->nas_msg, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_uplink_nas_transport_msg(LIBLTE_BYTE_MSG_STRUCT*                     msg,
                                           LIBLTE_MME_UPLINK_NAS_TRANSPORT_MSG_STRUCT* ul_nas_transport)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && ul_nas_transport != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // NAS Message Container
    liblte_mme_unpack_nas_message_container_ie(&msg_ptr, &ul_nas_transport->nas_msg);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Downlink Generic NAS Transport

    Description: Sent by the network to the UE in order to carry an
                 application message in encapsulated format.

    Document Reference: 24.301 v10.2.0 Section 8.2.31
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_downlink_generic_nas_transport_msg(
    LIBLTE_MME_DOWNLINK_GENERIC_NAS_TRANSPORT_MSG_STRUCT* dl_generic_nas_transport,
    uint8                                                 sec_hdr_type,
    uint32                                                count,
    LIBLTE_BYTE_MSG_STRUCT*                               msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (dl_generic_nas_transport != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_DOWNLINK_GENERIC_NAS_TRANSPORT;
    msg_ptr++;

    // Generic Message Container Type
    liblte_mme_pack_generic_message_container_type_ie(dl_generic_nas_transport->generic_msg_cont_type, &msg_ptr);

    // Generic Message Container
    liblte_mme_pack_generic_message_container_ie(&dl_generic_nas_transport->generic_msg_cont, &msg_ptr);

    // Additional Information
    liblte_mme_pack_additional_information_ie(&dl_generic_nas_transport->add_info, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_downlink_generic_nas_transport_msg(
    LIBLTE_BYTE_MSG_STRUCT*                               msg,
    LIBLTE_MME_DOWNLINK_GENERIC_NAS_TRANSPORT_MSG_STRUCT* dl_generic_nas_transport)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && dl_generic_nas_transport != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Generic Message Container Type
    liblte_mme_unpack_generic_message_container_type_ie(&msg_ptr, &dl_generic_nas_transport->generic_msg_cont_type);

    // Generic Message Container
    liblte_mme_unpack_generic_message_container_ie(&msg_ptr, &dl_generic_nas_transport->generic_msg_cont);

    // Additional Information
    liblte_mme_unpack_additional_information_ie(&msg_ptr, &dl_generic_nas_transport->add_info);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Uplink Generic NAS Transport

    Description: Sent by the UE to the network in order to carry an
                 application protocol message in encapsulated format.

    Document Reference: 24.301 v10.2.0 Section 8.2.32
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_uplink_generic_nas_transport_msg(
    LIBLTE_MME_UPLINK_GENERIC_NAS_TRANSPORT_MSG_STRUCT* ul_generic_nas_transport,
    uint8                                               sec_hdr_type,
    uint32                                              count,
    LIBLTE_BYTE_MSG_STRUCT*                             msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (ul_generic_nas_transport != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and Security Header Type
    *msg_ptr = (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_UPLINK_GENERIC_NAS_TRANSPORT;
    msg_ptr++;

    // Generic Message Container Type
    liblte_mme_pack_generic_message_container_type_ie(ul_generic_nas_transport->generic_msg_cont_type, &msg_ptr);

    // Generic Message Container
    liblte_mme_pack_generic_message_container_ie(&ul_generic_nas_transport->generic_msg_cont, &msg_ptr);

    // Additional Information
    liblte_mme_pack_additional_information_ie(&ul_generic_nas_transport->add_info, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_uplink_generic_nas_transport_msg(
    LIBLTE_BYTE_MSG_STRUCT*                             msg,
    LIBLTE_MME_UPLINK_GENERIC_NAS_TRANSPORT_MSG_STRUCT* ul_generic_nas_transport)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && ul_generic_nas_transport != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 7;
    }

    // Skip Message Type
    msg_ptr++;

    // Generic Message Container Type
    liblte_mme_unpack_generic_message_container_type_ie(&msg_ptr, &ul_generic_nas_transport->generic_msg_cont_type);

    // Generic Message Container
    liblte_mme_unpack_generic_message_container_ie(&msg_ptr, &ul_generic_nas_transport->generic_msg_cont);

    // Additional Information
    liblte_mme_unpack_additional_information_ie(&msg_ptr, &ul_generic_nas_transport->add_info);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Activate Dedicated EPS Bearer Context Accept

    Description: Sent by the UE to the network to acknowledge
                 activation of a dedicated EPS bearer context
                 associated with the same PDN address(es) and APN as
                 an already active EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.1
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_activate_dedicated_eps_bearer_context_accept_msg(
    LIBLTE_MME_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* act_ded_eps_bearer_context_accept,
    uint8                                                               sec_hdr_type,
    uint32                                                              count,
    LIBLTE_BYTE_MSG_STRUCT*                                             msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (act_ded_eps_bearer_context_accept != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (act_ded_eps_bearer_context_accept->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = act_ded_eps_bearer_context_accept->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_ACCEPT;
    msg_ptr++;

    // Protocol Configuration Options
    if (act_ded_eps_bearer_context_accept->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&act_ded_eps_bearer_context_accept->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_activate_dedicated_eps_bearer_context_accept_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                             msg,
    LIBLTE_MME_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* act_ded_eps_bearer_context_accept)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && act_ded_eps_bearer_context_accept != NULL) {
    // EPS Bearer ID
    act_ded_eps_bearer_context_accept->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    act_ded_eps_bearer_context_accept->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &act_ded_eps_bearer_context_accept->protocol_cnfg_opts);
      act_ded_eps_bearer_context_accept->protocol_cnfg_opts_present = true;
    } else {
      act_ded_eps_bearer_context_accept->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Activate Dedicated EPS Bearer Context Reject

    Description: Sent by the UE to the network to reject activation
                 of a dedicated EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.2
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_activate_dedicated_eps_bearer_context_reject_msg(
    LIBLTE_MME_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REJECT_MSG_STRUCT* act_ded_eps_bearer_context_rej,
    LIBLTE_BYTE_MSG_STRUCT*                                             msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (act_ded_eps_bearer_context_rej != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (act_ded_eps_bearer_context_rej->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = act_ded_eps_bearer_context_rej->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REJECT;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(act_ded_eps_bearer_context_rej->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (act_ded_eps_bearer_context_rej->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&act_ded_eps_bearer_context_rej->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_activate_dedicated_eps_bearer_context_reject_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                             msg,
    LIBLTE_MME_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REJECT_MSG_STRUCT* act_ded_eps_bearer_context_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && act_ded_eps_bearer_context_rej != NULL) {
    // EPS Bearer ID
    act_ded_eps_bearer_context_rej->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    act_ded_eps_bearer_context_rej->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &act_ded_eps_bearer_context_rej->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &act_ded_eps_bearer_context_rej->protocol_cnfg_opts);
      act_ded_eps_bearer_context_rej->protocol_cnfg_opts_present = true;
    } else {
      act_ded_eps_bearer_context_rej->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Activate Dedicated EPS Bearer Context Request

    Description: Sent by the network to the UE to request activation
                 of a dedicated EPS bearer context associated with
                 the same PDN address(es) and APN as an already
                 active default EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.3
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_activate_dedicated_eps_bearer_context_request_msg(
    LIBLTE_MME_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* act_ded_eps_bearer_context_req,
    LIBLTE_BYTE_MSG_STRUCT*                                              msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (act_ded_eps_bearer_context_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (act_ded_eps_bearer_context_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = act_ded_eps_bearer_context_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REQUEST;
    msg_ptr++;

    // Linked EPS Bearer Identity & Spare Half Octet
    *msg_ptr = 0;
    liblte_mme_pack_linked_eps_bearer_identity_ie(act_ded_eps_bearer_context_req->linked_eps_bearer_id, 0, &msg_ptr);
    msg_ptr++;

    // EPS QoS
    liblte_mme_pack_eps_quality_of_service_ie(&act_ded_eps_bearer_context_req->eps_qos, &msg_ptr);

    // TFT
    liblte_mme_pack_traffic_flow_template_ie(&act_ded_eps_bearer_context_req->tft, &msg_ptr);

    // Transaction Identifier
    if (act_ded_eps_bearer_context_req->transaction_id_present) {
      *msg_ptr = LIBLTE_MME_TRANSACTION_IDENTIFIER_IEI;
      msg_ptr++;
      liblte_mme_pack_transaction_identifier_ie(&act_ded_eps_bearer_context_req->transaction_id, &msg_ptr);
    }

    // Negotiated QoS
    if (act_ded_eps_bearer_context_req->negotiated_qos_present) {
      *msg_ptr = LIBLTE_MME_QUALITY_OF_SERVICE_IEI;
      msg_ptr++;
      liblte_mme_pack_quality_of_service_ie(&act_ded_eps_bearer_context_req->negotiated_qos, &msg_ptr);
    }

    // Negotiated LLC SAPI
    if (act_ded_eps_bearer_context_req->llc_sapi_present) {
      *msg_ptr = LIBLTE_MME_LLC_SAPI_IEI;
      msg_ptr++;
      liblte_mme_pack_llc_service_access_point_identifier_ie(act_ded_eps_bearer_context_req->llc_sapi, &msg_ptr);
    }

    // Radio Priority
    if (act_ded_eps_bearer_context_req->radio_prio_present) {
      *msg_ptr = LIBLTE_MME_RADIO_PRIORITY_IEI << 4;
      liblte_mme_pack_radio_priority_ie(act_ded_eps_bearer_context_req->radio_prio, 0, &msg_ptr);
      msg_ptr++;
    }

    // Packet Flow Identifier
    if (act_ded_eps_bearer_context_req->packet_flow_id_present) {
      *msg_ptr = LIBLTE_MME_PACKET_FLOW_IDENTIFIER_IEI;
      msg_ptr++;
      liblte_mme_pack_packet_flow_identifier_ie(act_ded_eps_bearer_context_req->packet_flow_id, &msg_ptr);
    }

    // Protocol Configuration Options
    if (act_ded_eps_bearer_context_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&act_ded_eps_bearer_context_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_activate_dedicated_eps_bearer_context_request_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                              msg,
    LIBLTE_MME_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* act_ded_eps_bearer_context_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && act_ded_eps_bearer_context_req != NULL) {
    // Security Header Type
    uint8_t sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 6;
    }

    // EPS Bearer ID
    act_ded_eps_bearer_context_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    act_ded_eps_bearer_context_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Linked Bearer Identity & Spare Half Octet
    liblte_mme_unpack_linked_eps_bearer_identity_ie(&msg_ptr, 0, &act_ded_eps_bearer_context_req->linked_eps_bearer_id);
    msg_ptr++;

    // EPS QoS
    liblte_mme_unpack_eps_quality_of_service_ie(&msg_ptr, &act_ded_eps_bearer_context_req->eps_qos);

    // TFT
    liblte_mme_unpack_traffic_flow_template_ie(&msg_ptr, &act_ded_eps_bearer_context_req->tft);

    // Transaction Identifier
    if (LIBLTE_MME_TRANSACTION_IDENTIFIER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_transaction_identifier_ie(&msg_ptr, &act_ded_eps_bearer_context_req->transaction_id);
      act_ded_eps_bearer_context_req->transaction_id_present = true;
    } else {
      act_ded_eps_bearer_context_req->transaction_id_present = false;
    }

    // Negotiated QoS
    if (LIBLTE_MME_QUALITY_OF_SERVICE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_quality_of_service_ie(&msg_ptr, &act_ded_eps_bearer_context_req->negotiated_qos);
      act_ded_eps_bearer_context_req->negotiated_qos_present = true;
    } else {
      act_ded_eps_bearer_context_req->negotiated_qos_present = false;
    }

    // Negotiated LLC SAPI
    if (LIBLTE_MME_LLC_SAPI_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_llc_service_access_point_identifier_ie(&msg_ptr, &act_ded_eps_bearer_context_req->llc_sapi);
      act_ded_eps_bearer_context_req->llc_sapi_present = true;
    } else {
      act_ded_eps_bearer_context_req->llc_sapi_present = false;
    }

    // Radio Priority
    if ((LIBLTE_MME_RADIO_PRIORITY_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_radio_priority_ie(&msg_ptr, 0, &act_ded_eps_bearer_context_req->radio_prio);
      msg_ptr++;
      act_ded_eps_bearer_context_req->radio_prio_present = true;
    } else {
      act_ded_eps_bearer_context_req->radio_prio_present = false;
    }

    // Packet Flow Identifier
    if (LIBLTE_MME_PACKET_FLOW_IDENTIFIER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_packet_flow_identifier_ie(&msg_ptr, &act_ded_eps_bearer_context_req->packet_flow_id);
      act_ded_eps_bearer_context_req->packet_flow_id_present = true;
    } else {
      act_ded_eps_bearer_context_req->packet_flow_id_present = false;
    }

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &act_ded_eps_bearer_context_req->protocol_cnfg_opts);
      act_ded_eps_bearer_context_req->protocol_cnfg_opts_present = true;
    } else {
      act_ded_eps_bearer_context_req->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Activate Default EPS Bearer Context Accept

    Description: Sent by the UE to the network to acknowledge
                 activation of a default EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.4
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_activate_default_eps_bearer_context_accept_msg(
    LIBLTE_MME_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* act_def_eps_bearer_context_accept,
    LIBLTE_BYTE_MSG_STRUCT*                                           msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (act_def_eps_bearer_context_accept != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (act_def_eps_bearer_context_accept->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = act_def_eps_bearer_context_accept->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_ACCEPT;
    msg_ptr++;

    // Protocol Configuration Options
    if (act_def_eps_bearer_context_accept->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&act_def_eps_bearer_context_accept->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_activate_default_eps_bearer_context_accept_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                           msg,
    LIBLTE_MME_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* act_def_eps_bearer_context_accept)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && act_def_eps_bearer_context_accept != NULL) {
    // EPS Bearer ID
    act_def_eps_bearer_context_accept->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    act_def_eps_bearer_context_accept->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &act_def_eps_bearer_context_accept->protocol_cnfg_opts);
      act_def_eps_bearer_context_accept->protocol_cnfg_opts_present = true;
    } else {
      act_def_eps_bearer_context_accept->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Activate Default EPS Bearer Context Reject

    Description: Sent by the UE to the network to reject activation
                 of a default EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.5
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_activate_default_eps_bearer_context_reject_msg(
    LIBLTE_MME_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REJECT_MSG_STRUCT* act_def_eps_bearer_context_rej,
    LIBLTE_BYTE_MSG_STRUCT*                                           msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (act_def_eps_bearer_context_rej != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (act_def_eps_bearer_context_rej->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = act_def_eps_bearer_context_rej->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REJECT;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(act_def_eps_bearer_context_rej->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (act_def_eps_bearer_context_rej->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&act_def_eps_bearer_context_rej->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_activate_default_eps_bearer_context_reject_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                           msg,
    LIBLTE_MME_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REJECT_MSG_STRUCT* act_def_eps_bearer_context_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && act_def_eps_bearer_context_rej != NULL) {
    // EPS Bearer ID
    act_def_eps_bearer_context_rej->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    act_def_eps_bearer_context_rej->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &act_def_eps_bearer_context_rej->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &act_def_eps_bearer_context_rej->protocol_cnfg_opts);
      act_def_eps_bearer_context_rej->protocol_cnfg_opts_present = true;
    } else {
      act_def_eps_bearer_context_rej->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Activate Default EPS Bearer Context Request

    Description: Sent by the network to the UE to request activation
                 of a default EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.6
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_activate_default_eps_bearer_context_request_msg(
    LIBLTE_MME_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* act_def_eps_bearer_context_req,
    LIBLTE_BYTE_MSG_STRUCT*                                            msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (act_def_eps_bearer_context_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (act_def_eps_bearer_context_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = act_def_eps_bearer_context_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REQUEST;
    msg_ptr++;

    // EPS QoS
    liblte_mme_pack_eps_quality_of_service_ie(&act_def_eps_bearer_context_req->eps_qos, &msg_ptr);

    // Access Point Name
    liblte_mme_pack_access_point_name_ie(&act_def_eps_bearer_context_req->apn, &msg_ptr);

    // PDN Address
    liblte_mme_pack_pdn_address_ie(&act_def_eps_bearer_context_req->pdn_addr, &msg_ptr);

    // Transaction Identifier
    if (act_def_eps_bearer_context_req->transaction_id_present) {
      *msg_ptr = LIBLTE_MME_TRANSACTION_IDENTIFIER_IEI;
      msg_ptr++;
      liblte_mme_pack_transaction_identifier_ie(&act_def_eps_bearer_context_req->transaction_id, &msg_ptr);
    }

    // Negotiated QoS
    if (act_def_eps_bearer_context_req->negotiated_qos_present) {
      *msg_ptr = LIBLTE_MME_QUALITY_OF_SERVICE_IEI;
      msg_ptr++;
      liblte_mme_pack_quality_of_service_ie(&act_def_eps_bearer_context_req->negotiated_qos, &msg_ptr);
    }

    // Negotiated LLC SAPI
    if (act_def_eps_bearer_context_req->llc_sapi_present) {
      *msg_ptr = LIBLTE_MME_LLC_SAPI_IEI;
      msg_ptr++;
      liblte_mme_pack_llc_service_access_point_identifier_ie(act_def_eps_bearer_context_req->llc_sapi, &msg_ptr);
    }

    // Radio Priority
    if (act_def_eps_bearer_context_req->radio_prio_present) {
      *msg_ptr = LIBLTE_MME_RADIO_PRIORITY_IEI << 4;
      liblte_mme_pack_radio_priority_ie(act_def_eps_bearer_context_req->radio_prio, 0, &msg_ptr);
      msg_ptr++;
    }

    // Packet Flow Identifier
    if (act_def_eps_bearer_context_req->packet_flow_id_present) {
      *msg_ptr = LIBLTE_MME_PACKET_FLOW_IDENTIFIER_IEI;
      msg_ptr++;
      liblte_mme_pack_packet_flow_identifier_ie(act_def_eps_bearer_context_req->packet_flow_id, &msg_ptr);
    }

    // APN-AMBR
    if (act_def_eps_bearer_context_req->apn_ambr_present) {
      *msg_ptr = LIBLTE_MME_APN_AMBR_IEI;
      msg_ptr++;
      liblte_mme_pack_apn_aggregate_maximum_bit_rate_ie(&act_def_eps_bearer_context_req->apn_ambr, &msg_ptr);
    }

    // ESM Cause
    if (act_def_eps_bearer_context_req->esm_cause_present) {
      *msg_ptr = LIBLTE_MME_ESM_CAUSE_IEI;
      msg_ptr++;
      liblte_mme_pack_esm_cause_ie(act_def_eps_bearer_context_req->esm_cause, &msg_ptr);
    }

    // Protocol Configuration Options
    if (act_def_eps_bearer_context_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&act_def_eps_bearer_context_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Connectivity Type
    if (act_def_eps_bearer_context_req->connectivity_type_present) {
      *msg_ptr = LIBLTE_MME_CONNECTIVITY_TYPE_IEI << 4;
      liblte_mme_pack_connectivity_type_ie(act_def_eps_bearer_context_req->connectivity_type, 0, &msg_ptr);
      msg_ptr++;
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_activate_default_eps_bearer_context_request_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                            msg,
    LIBLTE_MME_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* act_def_eps_bearer_context_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && act_def_eps_bearer_context_req != NULL) {

    // EPS Bearer ID
    act_def_eps_bearer_context_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    act_def_eps_bearer_context_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // EPS QoS
    liblte_mme_unpack_eps_quality_of_service_ie(&msg_ptr, &act_def_eps_bearer_context_req->eps_qos);

    // Access Point Name
    liblte_mme_unpack_access_point_name_ie(&msg_ptr, &act_def_eps_bearer_context_req->apn);

    // PDN Address
    liblte_mme_unpack_pdn_address_ie(&msg_ptr, &act_def_eps_bearer_context_req->pdn_addr);

    // Transaction Identifier
    if (LIBLTE_MME_TRANSACTION_IDENTIFIER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_transaction_identifier_ie(&msg_ptr, &act_def_eps_bearer_context_req->transaction_id);
      act_def_eps_bearer_context_req->transaction_id_present = true;
    } else {
      act_def_eps_bearer_context_req->transaction_id_present = false;
    }

    // Negotiated QoS
    if (LIBLTE_MME_QUALITY_OF_SERVICE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_quality_of_service_ie(&msg_ptr, &act_def_eps_bearer_context_req->negotiated_qos);
      act_def_eps_bearer_context_req->negotiated_qos_present = true;
    } else {
      act_def_eps_bearer_context_req->negotiated_qos_present = false;
    }

    // Negotiated LLC SAPI
    if (LIBLTE_MME_LLC_SAPI_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_llc_service_access_point_identifier_ie(&msg_ptr, &act_def_eps_bearer_context_req->llc_sapi);
      act_def_eps_bearer_context_req->llc_sapi_present = true;
    } else {
      act_def_eps_bearer_context_req->llc_sapi_present = false;
    }

    // Radio Priority
    if ((LIBLTE_MME_RADIO_PRIORITY_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_radio_priority_ie(&msg_ptr, 0, &act_def_eps_bearer_context_req->radio_prio);
      msg_ptr++;
      act_def_eps_bearer_context_req->radio_prio_present = true;
    } else {
      act_def_eps_bearer_context_req->radio_prio_present = false;
    }

    // Packet Flow Identifier
    if (LIBLTE_MME_PACKET_FLOW_IDENTIFIER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_packet_flow_identifier_ie(&msg_ptr, &act_def_eps_bearer_context_req->packet_flow_id);
      act_def_eps_bearer_context_req->packet_flow_id_present = true;
    } else {
      act_def_eps_bearer_context_req->packet_flow_id_present = false;
    }

    // APN-AMBR
    if (LIBLTE_MME_APN_AMBR_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_apn_aggregate_maximum_bit_rate_ie(&msg_ptr, &act_def_eps_bearer_context_req->apn_ambr);
      act_def_eps_bearer_context_req->apn_ambr_present = true;
    } else {
      act_def_eps_bearer_context_req->apn_ambr_present = false;
    }

    // ESM Cause
    if (LIBLTE_MME_ESM_CAUSE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_esm_cause_ie(&msg_ptr, &act_def_eps_bearer_context_req->esm_cause);
      act_def_eps_bearer_context_req->esm_cause_present = true;
    } else {
      act_def_eps_bearer_context_req->esm_cause_present = false;
    }

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &act_def_eps_bearer_context_req->protocol_cnfg_opts);
      act_def_eps_bearer_context_req->protocol_cnfg_opts_present = true;
    } else {
      act_def_eps_bearer_context_req->protocol_cnfg_opts_present = false;
    }

    // Connectivity Type
    if ((LIBLTE_MME_CONNECTIVITY_TYPE_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_connectivity_type_ie(&msg_ptr, 0, &act_def_eps_bearer_context_req->connectivity_type);
      msg_ptr++;
      act_def_eps_bearer_context_req->connectivity_type_present = true;
    } else {
      act_def_eps_bearer_context_req->connectivity_type_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Bearer Resource Allocation Reject

    Description: Sent by the network to the UE to reject the
                 allocation of a dedicated bearer resource.

    Document Reference: 24.301 v10.2.0 Section 8.3.7
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_bearer_resource_allocation_reject_msg(
    LIBLTE_MME_BEARER_RESOURCE_ALLOCATION_REJECT_MSG_STRUCT* bearer_res_alloc_rej,
    LIBLTE_BYTE_MSG_STRUCT*                                  msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (bearer_res_alloc_rej != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (bearer_res_alloc_rej->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = bearer_res_alloc_rej->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_ALLOCATION_REJECT;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(bearer_res_alloc_rej->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (bearer_res_alloc_rej->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&bearer_res_alloc_rej->protocol_cnfg_opts, &msg_ptr);
    }

    // T3496 Value
    if (bearer_res_alloc_rej->t3496_present) {
      *msg_ptr = LIBLTE_MME_T3496_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_3_ie(&bearer_res_alloc_rej->t3496, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_bearer_resource_allocation_reject_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                  msg,
    LIBLTE_MME_BEARER_RESOURCE_ALLOCATION_REJECT_MSG_STRUCT* bearer_res_alloc_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && bearer_res_alloc_rej != NULL) {
    // EPS Bearer ID
    bearer_res_alloc_rej->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    bearer_res_alloc_rej->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &bearer_res_alloc_rej->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &bearer_res_alloc_rej->protocol_cnfg_opts);
      bearer_res_alloc_rej->protocol_cnfg_opts_present = true;
    } else {
      bearer_res_alloc_rej->protocol_cnfg_opts_present = false;
    }

    // T3496 Value
    if (LIBLTE_MME_T3496_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_3_ie(&msg_ptr, &bearer_res_alloc_rej->t3496);
      bearer_res_alloc_rej->t3496_present = true;
    } else {
      bearer_res_alloc_rej->t3496_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Bearer Resource Allocation Request

    Description: Sent by the UE to the network to request the
                 allocation of a dedicated bearer resource.

    Document Reference: 24.301 v10.2.0 Section 8.3.8
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_bearer_resource_allocation_request_msg(
    LIBLTE_MME_BEARER_RESOURCE_ALLOCATION_REQUEST_MSG_STRUCT* bearer_res_alloc_req,
    LIBLTE_BYTE_MSG_STRUCT*                                   msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (bearer_res_alloc_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (bearer_res_alloc_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = bearer_res_alloc_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_ALLOCATION_REQUEST;
    msg_ptr++;

    // Linked EPS Bearer Identity & Spare Half Octet
    liblte_mme_pack_linked_eps_bearer_identity_ie(bearer_res_alloc_req->linked_eps_bearer_id, 0, &msg_ptr);

    // Traffic Flow Aggregate
    liblte_mme_pack_traffic_flow_aggregate_description_ie(&bearer_res_alloc_req->tfa, &msg_ptr);

    // Required Traffic Flow QoS
    liblte_mme_pack_eps_quality_of_service_ie(&bearer_res_alloc_req->req_tf_qos, &msg_ptr);

    // Protocol Configuration Options
    if (bearer_res_alloc_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&bearer_res_alloc_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Device Properties
    if (bearer_res_alloc_req->device_properties_present) {
      *msg_ptr = LIBLTE_MME_BEARER_RESOURCE_ALLOCATION_REQUEST_DEVICE_PROPERTIES_IEI << 4;
      liblte_mme_pack_device_properties_ie(bearer_res_alloc_req->device_properties, 0, &msg_ptr);
      msg_ptr++;
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_bearer_resource_allocation_request_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                   msg,
    LIBLTE_MME_BEARER_RESOURCE_ALLOCATION_REQUEST_MSG_STRUCT* bearer_res_alloc_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && bearer_res_alloc_req != NULL) {
    // EPS Bearer ID
    bearer_res_alloc_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    bearer_res_alloc_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Linked EPS Bearer Identity & Spare Half Octet
    liblte_mme_unpack_linked_eps_bearer_identity_ie(&msg_ptr, 0, &bearer_res_alloc_req->linked_eps_bearer_id);

    // Traffic Flow Aggregate
    liblte_mme_unpack_traffic_flow_aggregate_description_ie(&msg_ptr, &bearer_res_alloc_req->tfa);

    // Required Traffic Flow QoS
    liblte_mme_unpack_eps_quality_of_service_ie(&msg_ptr, &bearer_res_alloc_req->req_tf_qos);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &bearer_res_alloc_req->protocol_cnfg_opts);
      bearer_res_alloc_req->protocol_cnfg_opts_present = true;
    } else {
      bearer_res_alloc_req->protocol_cnfg_opts_present = false;
    }

    // Device Properties
    if ((LIBLTE_MME_BEARER_RESOURCE_ALLOCATION_REQUEST_DEVICE_PROPERTIES_IEI << 4) == *msg_ptr) {
      liblte_mme_unpack_device_properties_ie(&msg_ptr, 0, &bearer_res_alloc_req->device_properties);
      msg_ptr++;
      bearer_res_alloc_req->device_properties_present = true;
    } else {
      bearer_res_alloc_req->device_properties_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Bearer Resource Modification Reject

    Description: Sent by the network to the UE to reject the
                 modification of a dedicated bearer resource.

    Document Reference: 24.301 v10.2.0 Section 8.3.9
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_bearer_resource_modification_reject_msg(
    LIBLTE_MME_BEARER_RESOURCE_MODIFICATION_REJECT_MSG_STRUCT* bearer_res_mod_rej,
    LIBLTE_BYTE_MSG_STRUCT*                                    msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (bearer_res_mod_rej != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (bearer_res_mod_rej->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = bearer_res_mod_rej->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_MODIFICATION_REJECT;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(bearer_res_mod_rej->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (bearer_res_mod_rej->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&bearer_res_mod_rej->protocol_cnfg_opts, &msg_ptr);
    }

    // T3496 Value
    if (bearer_res_mod_rej->t3496_present) {
      *msg_ptr = LIBLTE_MME_T3496_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_3_ie(&bearer_res_mod_rej->t3496, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_bearer_resource_modification_reject_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                    msg,
    LIBLTE_MME_BEARER_RESOURCE_MODIFICATION_REJECT_MSG_STRUCT* bearer_res_mod_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && bearer_res_mod_rej != NULL) {
    // EPS Bearer ID
    bearer_res_mod_rej->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    bearer_res_mod_rej->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &bearer_res_mod_rej->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &bearer_res_mod_rej->protocol_cnfg_opts);
      bearer_res_mod_rej->protocol_cnfg_opts_present = true;
    } else {
      bearer_res_mod_rej->protocol_cnfg_opts_present = false;
    }

    // T3496 Value
    if (LIBLTE_MME_T3496_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_3_ie(&msg_ptr, &bearer_res_mod_rej->t3496);
      bearer_res_mod_rej->t3496_present = true;
    } else {
      bearer_res_mod_rej->t3496_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Bearer Resource Modification Request

    Description: Sent by the UE to the network to request the
                 modification of a dedicated bearer resource.

    Document Reference: 24.301 v10.2.0 Section 8.3.10
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_bearer_resource_modification_request_msg(
    LIBLTE_MME_BEARER_RESOURCE_MODIFICATION_REQUEST_MSG_STRUCT* bearer_res_mod_req,
    LIBLTE_BYTE_MSG_STRUCT*                                     msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (bearer_res_mod_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (bearer_res_mod_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = bearer_res_mod_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_MODIFICATION_REQUEST;
    msg_ptr++;

    // EPS Bearer Identity For Packet Filter & Spare Half Octet
    liblte_mme_pack_linked_eps_bearer_identity_ie(bearer_res_mod_req->eps_bearer_id_for_packet_filter, 0, &msg_ptr);

    // Traffic Flow Aggregate
    liblte_mme_pack_traffic_flow_aggregate_description_ie(&bearer_res_mod_req->tfa, &msg_ptr);

    // Required Traffic Flow QoS
    if (bearer_res_mod_req->req_tf_qos_present) {
      *msg_ptr = LIBLTE_MME_EPS_QUALITY_OF_SERVICE_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_quality_of_service_ie(&bearer_res_mod_req->req_tf_qos, &msg_ptr);
    }

    // ESM Cause
    if (bearer_res_mod_req->esm_cause_present) {
      *msg_ptr = LIBLTE_MME_ESM_CAUSE_IEI;
      msg_ptr++;
      liblte_mme_pack_esm_cause_ie(bearer_res_mod_req->esm_cause, &msg_ptr);
    }

    // Protocol Configuration Options
    if (bearer_res_mod_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&bearer_res_mod_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Device Properties
    if (bearer_res_mod_req->device_properties_present) {
      *msg_ptr = LIBLTE_MME_BEARER_RESOURCE_MODIFICATION_REQUEST_DEVICE_PROPERTIES_IEI << 4;
      liblte_mme_pack_device_properties_ie(bearer_res_mod_req->device_properties, 0, &msg_ptr);
      msg_ptr++;
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_bearer_resource_modification_request_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                     msg,
    LIBLTE_MME_BEARER_RESOURCE_MODIFICATION_REQUEST_MSG_STRUCT* bearer_res_mod_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && bearer_res_mod_req != NULL) {
    // EPS Bearer ID
    bearer_res_mod_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    bearer_res_mod_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // EPS Bearer Identity For Packet Filter & Spare Half Octet
    liblte_mme_unpack_linked_eps_bearer_identity_ie(&msg_ptr, 0, &bearer_res_mod_req->eps_bearer_id_for_packet_filter);

    // Traffic Flow Aggregate
    liblte_mme_unpack_traffic_flow_aggregate_description_ie(&msg_ptr, &bearer_res_mod_req->tfa);

    // Required Traffic Flow QoS
    if (LIBLTE_MME_EPS_QUALITY_OF_SERVICE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_quality_of_service_ie(&msg_ptr, &bearer_res_mod_req->req_tf_qos);
      bearer_res_mod_req->req_tf_qos_present = true;
    } else {
      bearer_res_mod_req->req_tf_qos_present = false;
    }

    // ESM Cause
    if (LIBLTE_MME_ESM_CAUSE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_esm_cause_ie(&msg_ptr, &bearer_res_mod_req->esm_cause);
      bearer_res_mod_req->esm_cause_present = true;
    } else {
      bearer_res_mod_req->esm_cause_present = false;
    }

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &bearer_res_mod_req->protocol_cnfg_opts);
      bearer_res_mod_req->protocol_cnfg_opts_present = true;
    } else {
      bearer_res_mod_req->protocol_cnfg_opts_present = false;
    }

    // Device Properties
    if ((LIBLTE_MME_BEARER_RESOURCE_MODIFICATION_REQUEST_DEVICE_PROPERTIES_IEI << 4) == *msg_ptr) {
      liblte_mme_unpack_device_properties_ie(&msg_ptr, 0, &bearer_res_mod_req->device_properties);
      msg_ptr++;
      bearer_res_mod_req->device_properties_present = true;
    } else {
      bearer_res_mod_req->device_properties_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Deactivate EPS Bearer Context Accept

    Description: Sent by the UE to acknowledge deactivation of the
                 EPS bearer context requested in the corresponding
                 deactivate EPS bearer context request message.

    Document Reference: 24.301 v10.2.0 Section 8.3.11
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_deactivate_eps_bearer_context_accept_msg(
    LIBLTE_MME_DEACTIVATE_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* deact_eps_bearer_context_accept,
    uint8                                                       sec_hdr_type,
    uint32                                                      count,
    LIBLTE_BYTE_MSG_STRUCT*                                     msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (deact_eps_bearer_context_accept != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (deact_eps_bearer_context_accept->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = deact_eps_bearer_context_accept->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_DEACTIVATE_EPS_BEARER_CONTEXT_ACCEPT;
    msg_ptr++;

    // Protocol Configuration Options
    if (deact_eps_bearer_context_accept->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&deact_eps_bearer_context_accept->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_deactivate_eps_bearer_context_accept_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                     msg,
    LIBLTE_MME_DEACTIVATE_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* deact_eps_bearer_context_accept)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && deact_eps_bearer_context_accept != NULL) {
    // EPS Bearer ID
    deact_eps_bearer_context_accept->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    deact_eps_bearer_context_accept->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &deact_eps_bearer_context_accept->protocol_cnfg_opts);
      deact_eps_bearer_context_accept->protocol_cnfg_opts_present = true;
    } else {
      deact_eps_bearer_context_accept->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Deactivate EPS Bearer Context Request

    Description: Sent by the network to request deactivation of an
                 EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.12
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_deactivate_eps_bearer_context_request_msg(
    LIBLTE_MME_DEACTIVATE_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* deact_eps_bearer_context_req,
    LIBLTE_BYTE_MSG_STRUCT*                                      msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (deact_eps_bearer_context_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (deact_eps_bearer_context_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = deact_eps_bearer_context_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_DEACTIVATE_EPS_BEARER_CONTEXT_REQUEST;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(deact_eps_bearer_context_req->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (deact_eps_bearer_context_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&deact_eps_bearer_context_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_deactivate_eps_bearer_context_request_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                      msg,
    LIBLTE_MME_DEACTIVATE_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* deact_eps_bearer_context_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && deact_eps_bearer_context_req != NULL) {
    // Security Header Type
    uint8_t sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 6;
    }

    // EPS Bearer ID
    deact_eps_bearer_context_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    deact_eps_bearer_context_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &deact_eps_bearer_context_req->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &deact_eps_bearer_context_req->protocol_cnfg_opts);
      deact_eps_bearer_context_req->protocol_cnfg_opts_present = true;
    } else {
      deact_eps_bearer_context_req->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: ESM Information Request

    Description: Sent by the network to the UE to request the UE to
                 provide ESM information, i.e. protocol configuration
                 options or APN or both. This function is being added
                 to support encryption and integrety protection on
                 ESM information transfer.

    Document Reference: 24.301 v10.2.0 Section 8.3.13
*********************************************************************/
LIBLTE_ERROR_ENUM
srslte_mme_pack_esm_information_request_msg(LIBLTE_MME_ESM_INFORMATION_REQUEST_MSG_STRUCT* esm_info_req,
                                            uint8                                          sec_hdr_type,
                                            uint32                                         count,
                                            LIBLTE_BYTE_MSG_STRUCT*                        msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (esm_info_req != NULL && msg != NULL) {

    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (esm_info_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = esm_info_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ESM_INFORMATION_REQUEST;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: ESM Information Request

    Description: Sent by the network to the UE to request the UE to
                 provide ESM information, i.e. protocol configuration
                 options or APN or both.

    Document Reference: 24.301 v10.2.0 Section 8.3.13
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_esm_information_request_msg(LIBLTE_MME_ESM_INFORMATION_REQUEST_MSG_STRUCT* esm_info_req,
                                            LIBLTE_BYTE_MSG_STRUCT*                        msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (esm_info_req != NULL && msg != NULL) {

    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (esm_info_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = esm_info_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ESM_INFORMATION_REQUEST;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

LIBLTE_ERROR_ENUM
liblte_mme_unpack_esm_information_request_msg(LIBLTE_BYTE_MSG_STRUCT*                        msg,
                                              LIBLTE_MME_ESM_INFORMATION_REQUEST_MSG_STRUCT* esm_info_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && esm_info_req != NULL) {
    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 6;
    }

    // EPS Bearer ID
    esm_info_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    esm_info_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: ESM Information Response

    Description: Sent by the UE to the network in response to an ESM
                 INFORMATION REQUEST message and provides the
                 requested ESM information.

    Document Reference: 24.301 v10.2.0 Section 8.3.14
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_esm_information_response_msg(LIBLTE_MME_ESM_INFORMATION_RESPONSE_MSG_STRUCT* esm_info_resp,
                                             uint8                                           sec_hdr_type,
                                             uint32                                          count,
                                             LIBLTE_BYTE_MSG_STRUCT*                         msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (esm_info_resp != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (esm_info_resp->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = esm_info_resp->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ESM_INFORMATION_RESPONSE;
    msg_ptr++;

    // Access Point Name
    if (esm_info_resp->apn_present) {
      *msg_ptr = LIBLTE_MME_ACCESS_POINT_NAME_IEI;
      msg_ptr++;
      liblte_mme_pack_access_point_name_ie(&esm_info_resp->apn, &msg_ptr);
    }

    // Protocol Configuration Options
    if (esm_info_resp->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&esm_info_resp->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

LIBLTE_ERROR_ENUM
srslte_mme_unpack_esm_information_response_msg(LIBLTE_BYTE_MSG_STRUCT*                         msg,
                                               LIBLTE_MME_ESM_INFORMATION_RESPONSE_MSG_STRUCT* esm_info_resp)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;
  uint8             sec_hdr_type;

  if (msg != NULL && esm_info_resp != NULL) {

    // Security Header Type
    sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 6;
    }
    // EPS Bearer ID
    esm_info_resp->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    esm_info_resp->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Access Point Name
    if (LIBLTE_MME_ACCESS_POINT_NAME_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_access_point_name_ie(&msg_ptr, &esm_info_resp->apn);
      esm_info_resp->apn_present = true;
    } else {
      esm_info_resp->apn_present = false;
    }

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &esm_info_resp->protocol_cnfg_opts);
      esm_info_resp->protocol_cnfg_opts_present = true;
    } else {
      esm_info_resp->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

LIBLTE_ERROR_ENUM
liblte_mme_unpack_esm_information_response_msg(LIBLTE_BYTE_MSG_STRUCT*                         msg,
                                               LIBLTE_MME_ESM_INFORMATION_RESPONSE_MSG_STRUCT* esm_info_resp)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && esm_info_resp != NULL) {
    // EPS Bearer ID
    esm_info_resp->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    esm_info_resp->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Access Point Name
    if (LIBLTE_MME_ACCESS_POINT_NAME_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_access_point_name_ie(&msg_ptr, &esm_info_resp->apn);
      esm_info_resp->apn_present = true;
    } else {
      esm_info_resp->apn_present = false;
    }

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &esm_info_resp->protocol_cnfg_opts);
      esm_info_resp->protocol_cnfg_opts_present = true;
    } else {
      esm_info_resp->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: ESM Status

    Description: Sent by the network or the UE to pass information on
                 the status of the indicated EPS bearer context and
                 report certain error conditions.

    Document Reference: 24.301 v10.2.0 Section 8.3.15
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_esm_status_msg(LIBLTE_MME_ESM_STATUS_MSG_STRUCT* esm_status,
                                                 LIBLTE_BYTE_MSG_STRUCT*           msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (esm_status != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (esm_status->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = esm_status->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ESM_STATUS;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(esm_status->esm_cause, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_esm_status_msg(LIBLTE_BYTE_MSG_STRUCT*           msg,
                                                   LIBLTE_MME_ESM_STATUS_MSG_STRUCT* esm_status)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && esm_status != NULL) {
    // EPS Bearer ID
    esm_status->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    esm_status->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &esm_status->esm_cause);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Modify EPS Bearer Context Accept

    Description: Sent by the UE to the network to acknowledge the
                 modification of an active EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.16
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_modify_eps_bearer_context_accept_msg(
    LIBLTE_MME_MODIFY_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* mod_eps_bearer_context_accept,
    uint8                                                   sec_hdr_type,
    uint32                                                  count,
    LIBLTE_BYTE_MSG_STRUCT*                                 msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (mod_eps_bearer_context_accept != NULL && msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (mod_eps_bearer_context_accept->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = mod_eps_bearer_context_accept->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_MODIFY_EPS_BEARER_CONTEXT_ACCEPT;
    msg_ptr++;

    // Protocol Configuration Options
    if (mod_eps_bearer_context_accept->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&mod_eps_bearer_context_accept->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_modify_eps_bearer_context_accept_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                 msg,
    LIBLTE_MME_MODIFY_EPS_BEARER_CONTEXT_ACCEPT_MSG_STRUCT* mod_eps_bearer_context_accept)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && mod_eps_bearer_context_accept != NULL) {
    // EPS Bearer ID
    mod_eps_bearer_context_accept->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    mod_eps_bearer_context_accept->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &mod_eps_bearer_context_accept->protocol_cnfg_opts);
      mod_eps_bearer_context_accept->protocol_cnfg_opts_present = true;
    } else {
      mod_eps_bearer_context_accept->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Modify EPS Bearer Context Reject

    Description: Sent by the UE or the network to reject a
                 modification of an active EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.17
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_modify_eps_bearer_context_reject_msg(
    LIBLTE_MME_MODIFY_EPS_BEARER_CONTEXT_REJECT_MSG_STRUCT* mod_eps_bearer_context_rej,
    LIBLTE_BYTE_MSG_STRUCT*                                 msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (mod_eps_bearer_context_rej != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (mod_eps_bearer_context_rej->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = mod_eps_bearer_context_rej->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_MODIFY_EPS_BEARER_CONTEXT_REJECT;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(mod_eps_bearer_context_rej->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (mod_eps_bearer_context_rej->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&mod_eps_bearer_context_rej->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_modify_eps_bearer_context_reject_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                 msg,
    LIBLTE_MME_MODIFY_EPS_BEARER_CONTEXT_REJECT_MSG_STRUCT* mod_eps_bearer_context_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && mod_eps_bearer_context_rej != NULL) {
    // EPS Bearer ID
    mod_eps_bearer_context_rej->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    mod_eps_bearer_context_rej->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &mod_eps_bearer_context_rej->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &mod_eps_bearer_context_rej->protocol_cnfg_opts);
      mod_eps_bearer_context_rej->protocol_cnfg_opts_present = true;
    } else {
      mod_eps_bearer_context_rej->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Modify EPS Bearer Context Request

    Description: Sent by the network to the UE to request modification
                 of an active EPS bearer context.

    Document Reference: 24.301 v10.2.0 Section 8.3.18
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_modify_eps_bearer_context_request_msg(
    LIBLTE_MME_MODIFY_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* mod_eps_bearer_context_req,
    LIBLTE_BYTE_MSG_STRUCT*                                  msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (mod_eps_bearer_context_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (mod_eps_bearer_context_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = mod_eps_bearer_context_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_MODIFY_EPS_BEARER_CONTEXT_REQUEST;
    msg_ptr++;

    // New EPS QoS
    if (mod_eps_bearer_context_req->new_eps_qos_present) {
      *msg_ptr = LIBLTE_MME_EPS_QUALITY_OF_SERVICE_IEI;
      msg_ptr++;
      liblte_mme_pack_eps_quality_of_service_ie(&mod_eps_bearer_context_req->new_eps_qos, &msg_ptr);
    }

    // TFT
    if (mod_eps_bearer_context_req->tft_present) {
      *msg_ptr = LIBLTE_MME_TRAFFIC_FLOW_TEMPLATE_IEI;
      msg_ptr++;
      liblte_mme_pack_traffic_flow_template_ie(&mod_eps_bearer_context_req->tft, &msg_ptr);
    }

    // New QoS
    if (mod_eps_bearer_context_req->new_qos_present) {
      *msg_ptr = LIBLTE_MME_QUALITY_OF_SERVICE_IEI;
      msg_ptr++;
      liblte_mme_pack_quality_of_service_ie(&mod_eps_bearer_context_req->new_qos, &msg_ptr);
    }

    // Negotiated LLC SAPI
    if (mod_eps_bearer_context_req->negotiated_llc_sapi_present) {
      *msg_ptr = LIBLTE_MME_LLC_SAPI_IEI;
      msg_ptr++;
      liblte_mme_pack_llc_service_access_point_identifier_ie(mod_eps_bearer_context_req->negotiated_llc_sapi, &msg_ptr);
    }

    // Radio Priority
    if (mod_eps_bearer_context_req->radio_prio_present) {
      *msg_ptr = LIBLTE_MME_RADIO_PRIORITY_IEI << 4;
      liblte_mme_pack_radio_priority_ie(mod_eps_bearer_context_req->radio_prio, 0, &msg_ptr);
      msg_ptr++;
    }

    // Packet Flow Identifier
    if (mod_eps_bearer_context_req->packet_flow_id_present) {
      *msg_ptr = LIBLTE_MME_PACKET_FLOW_IDENTIFIER_IEI;
      msg_ptr++;
      liblte_mme_pack_packet_flow_identifier_ie(mod_eps_bearer_context_req->packet_flow_id, &msg_ptr);
    }

    // APN-AMBR
    if (mod_eps_bearer_context_req->apn_ambr_present) {
      *msg_ptr = LIBLTE_MME_APN_AMBR_IEI;
      msg_ptr++;
      liblte_mme_pack_apn_aggregate_maximum_bit_rate_ie(&mod_eps_bearer_context_req->apn_ambr, &msg_ptr);
    }

    // Protocol Configuration Options
    if (mod_eps_bearer_context_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&mod_eps_bearer_context_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_modify_eps_bearer_context_request_msg(
    LIBLTE_BYTE_MSG_STRUCT*                                  msg,
    LIBLTE_MME_MODIFY_EPS_BEARER_CONTEXT_REQUEST_MSG_STRUCT* mod_eps_bearer_context_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && mod_eps_bearer_context_req != NULL) {
    // Security Header Type
    uint8_t sec_hdr_type = (msg->msg[0] & 0xF0) >> 4;
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS == sec_hdr_type) {
      msg_ptr++;
    } else {
      msg_ptr += 6;
    }

    // EPS Bearer ID
    mod_eps_bearer_context_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    mod_eps_bearer_context_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // New EPS QoS
    if (LIBLTE_MME_EPS_QUALITY_OF_SERVICE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_eps_quality_of_service_ie(&msg_ptr, &mod_eps_bearer_context_req->new_eps_qos);
      mod_eps_bearer_context_req->new_eps_qos_present = true;
    } else {
      mod_eps_bearer_context_req->new_eps_qos_present = false;
    }

    // TFT
    if (LIBLTE_MME_TRAFFIC_FLOW_TEMPLATE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_traffic_flow_template_ie(&msg_ptr, &mod_eps_bearer_context_req->tft);
      mod_eps_bearer_context_req->tft_present = true;
    } else {
      mod_eps_bearer_context_req->tft_present = false;
    }

    // New QoS
    if (LIBLTE_MME_QUALITY_OF_SERVICE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_quality_of_service_ie(&msg_ptr, &mod_eps_bearer_context_req->new_qos);
      mod_eps_bearer_context_req->new_qos_present = true;
    } else {
      mod_eps_bearer_context_req->new_qos_present = false;
    }

    // Negotiated LLC SAPI
    if (LIBLTE_MME_LLC_SAPI_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_llc_service_access_point_identifier_ie(&msg_ptr,
                                                               &mod_eps_bearer_context_req->negotiated_llc_sapi);
      mod_eps_bearer_context_req->negotiated_llc_sapi_present = true;
    } else {
      mod_eps_bearer_context_req->negotiated_llc_sapi_present = false;
    }

    // Radio Priority
    if ((LIBLTE_MME_RADIO_PRIORITY_IEI << 4) == *msg_ptr) {
      liblte_mme_unpack_radio_priority_ie(&msg_ptr, 0, &mod_eps_bearer_context_req->radio_prio);
      msg_ptr++;
      mod_eps_bearer_context_req->radio_prio_present = true;
    } else {
      mod_eps_bearer_context_req->radio_prio_present = false;
    }

    // Packet Flow Identifier
    if (LIBLTE_MME_PACKET_FLOW_IDENTIFIER_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_packet_flow_identifier_ie(&msg_ptr, &mod_eps_bearer_context_req->packet_flow_id);
      mod_eps_bearer_context_req->packet_flow_id_present = true;
    } else {
      mod_eps_bearer_context_req->packet_flow_id_present = false;
    }

    // APN-AMBR
    if (LIBLTE_MME_APN_AMBR_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_apn_aggregate_maximum_bit_rate_ie(&msg_ptr, &mod_eps_bearer_context_req->apn_ambr);
      mod_eps_bearer_context_req->apn_ambr_present = true;
    } else {
      mod_eps_bearer_context_req->apn_ambr_present = false;
    }

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &mod_eps_bearer_context_req->protocol_cnfg_opts);
      mod_eps_bearer_context_req->protocol_cnfg_opts_present = true;
    } else {
      mod_eps_bearer_context_req->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: Notification

    Description: Sent by the network to inform the UE about events
                 which are relevant for the upper layer using an EPS
                 bearer context or having requested a procedure
                 transaction.

    Document Reference: 24.301 v10.2.0 Section 8.3.18A
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_notification_msg(LIBLTE_MME_NOTIFICATION_MSG_STRUCT* notification,
                                                   LIBLTE_BYTE_MSG_STRUCT*             msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (notification != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (notification->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = notification->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_NOTIFICATION;
    msg_ptr++;

    // Notification Indicator
    liblte_mme_pack_notification_indicator_ie(notification->notification_ind, &msg_ptr);

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM liblte_mme_unpack_notification_msg(LIBLTE_BYTE_MSG_STRUCT*             msg,
                                                     LIBLTE_MME_NOTIFICATION_MSG_STRUCT* notification)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && notification != NULL) {
    // EPS Bearer ID
    notification->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    notification->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Notification Indicator
    liblte_mme_unpack_notification_indicator_ie(&msg_ptr, &notification->notification_ind);

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: PDN Connectivity Reject

    Description: Sent by the network to the UE to reject establishment
                 of a PDN connection.

    Document Reference: 24.301 v10.2.0 Section 8.3.19
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_pdn_connectivity_reject_msg(LIBLTE_MME_PDN_CONNECTIVITY_REJECT_MSG_STRUCT* pdn_con_rej,
                                            LIBLTE_BYTE_MSG_STRUCT*                        msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (pdn_con_rej != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (pdn_con_rej->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = pdn_con_rej->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_PDN_CONNECTIVITY_REJECT;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(pdn_con_rej->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (pdn_con_rej->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&pdn_con_rej->protocol_cnfg_opts, &msg_ptr);
    }

    // T3496 Value
    if (pdn_con_rej->t3496_present) {
      *msg_ptr = LIBLTE_MME_T3496_VALUE_IEI;
      msg_ptr++;
      liblte_mme_pack_gprs_timer_3_ie(&pdn_con_rej->t3496, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_pdn_connectivity_reject_msg(LIBLTE_BYTE_MSG_STRUCT*                        msg,
                                              LIBLTE_MME_PDN_CONNECTIVITY_REJECT_MSG_STRUCT* pdn_con_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && pdn_con_rej != NULL) {
    // EPS Bearer ID
    pdn_con_rej->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    pdn_con_rej->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &pdn_con_rej->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &pdn_con_rej->protocol_cnfg_opts);
      pdn_con_rej->protocol_cnfg_opts_present = true;
    } else {
      pdn_con_rej->protocol_cnfg_opts_present = false;
    }

    // T3496 Value
    if (LIBLTE_MME_T3496_VALUE_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_gprs_timer_3_ie(&msg_ptr, &pdn_con_rej->t3496);
      pdn_con_rej->t3496_present = true;
    } else {
      pdn_con_rej->t3496_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: PDN Connectivity Request

    Description: Sent by the UE to the network to initiate
                 establishment of a PDN connection.

    Document Reference: 24.301 v10.2.0 Section 8.3.20
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_pdn_connectivity_request_msg(LIBLTE_MME_PDN_CONNECTIVITY_REQUEST_MSG_STRUCT* pdn_con_req,
                                             LIBLTE_BYTE_MSG_STRUCT*                         msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (pdn_con_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (pdn_con_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = pdn_con_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_PDN_CONNECTIVITY_REQUEST;
    msg_ptr++;

    // Request Type & PDN Type
    *msg_ptr = 0;
    liblte_mme_pack_request_type_ie(pdn_con_req->request_type, 0, &msg_ptr);
    liblte_mme_pack_pdn_type_ie(pdn_con_req->pdn_type, 4, &msg_ptr);
    msg_ptr++;

    // ESM Information Transfer Flag
    if (pdn_con_req->esm_info_transfer_flag_present) {
      *msg_ptr = LIBLTE_MME_ESM_INFO_TRANSFER_FLAG_IEI << 4;
      liblte_mme_pack_esm_info_transfer_flag_ie(pdn_con_req->esm_info_transfer_flag, 0, &msg_ptr);
      msg_ptr++;
    }

    // Access Point Name
    if (pdn_con_req->apn_present) {
      *msg_ptr = LIBLTE_MME_ACCESS_POINT_NAME_IEI;
      msg_ptr++;
      liblte_mme_pack_access_point_name_ie(&pdn_con_req->apn, &msg_ptr);
    }

    // Protocol Configuration Options
    if (pdn_con_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&pdn_con_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Device Properties
    if (pdn_con_req->device_properties_present) {
      *msg_ptr = LIBLTE_MME_PDN_CONNECTIVITY_REQUEST_DEVICE_PROPERTIES_IEI << 4;
      liblte_mme_pack_device_properties_ie(pdn_con_req->device_properties, 0, &msg_ptr);
      msg_ptr++;
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_pdn_connectivity_request_msg(LIBLTE_BYTE_MSG_STRUCT*                         msg,
                                               LIBLTE_MME_PDN_CONNECTIVITY_REQUEST_MSG_STRUCT* pdn_con_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && pdn_con_req != NULL) {
    // EPS Bearer ID
    pdn_con_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    pdn_con_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Request Type & PDN Type
    liblte_mme_unpack_request_type_ie(&msg_ptr, 0, &pdn_con_req->request_type);
    liblte_mme_unpack_pdn_type_ie(&msg_ptr, 4, &pdn_con_req->pdn_type);
    msg_ptr++;

    // ESM Information Transfer Flag
    if ((LIBLTE_MME_ESM_INFO_TRANSFER_FLAG_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_esm_info_transfer_flag_ie(&msg_ptr, 0, &pdn_con_req->esm_info_transfer_flag);
      msg_ptr++;
      pdn_con_req->esm_info_transfer_flag_present = true;
    } else {
      pdn_con_req->esm_info_transfer_flag_present = false;
    }

    // Access Point Name
    if (LIBLTE_MME_ACCESS_POINT_NAME_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_access_point_name_ie(&msg_ptr, &pdn_con_req->apn);
      pdn_con_req->apn_present = true;
    } else {
      pdn_con_req->apn_present = false;
    }

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &pdn_con_req->protocol_cnfg_opts);
      pdn_con_req->protocol_cnfg_opts_present = true;
    } else {
      pdn_con_req->protocol_cnfg_opts_present = false;
    }

    // Device Properties
    if ((LIBLTE_MME_PDN_CONNECTIVITY_REQUEST_DEVICE_PROPERTIES_IEI << 4) == (*msg_ptr & 0xF0)) {
      liblte_mme_unpack_device_properties_ie(&msg_ptr, 0, &pdn_con_req->device_properties);
      msg_ptr++;
      pdn_con_req->device_properties_present = true;
    } else {
      pdn_con_req->device_properties_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: PDN Disconnect Reject

    Description: Sent by the network to the UE to reject release of a
                 PDN connection.

    Document Reference: 24.301 v10.2.0 Section 8.3.21
*********************************************************************/
LIBLTE_ERROR_ENUM liblte_mme_pack_pdn_disconnect_reject_msg(LIBLTE_MME_PDN_DISCONNECT_REJECT_MSG_STRUCT* pdn_discon_rej,
                                                            LIBLTE_BYTE_MSG_STRUCT*                      msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (pdn_discon_rej != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (pdn_discon_rej->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = pdn_discon_rej->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_PDN_DISCONNECT_REJECT;
    msg_ptr++;

    // ESM Cause
    liblte_mme_pack_esm_cause_ie(pdn_discon_rej->esm_cause, &msg_ptr);

    // Protocol Configuration Options
    if (pdn_discon_rej->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&pdn_discon_rej->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_pdn_disconnect_reject_msg(LIBLTE_BYTE_MSG_STRUCT*                      msg,
                                            LIBLTE_MME_PDN_DISCONNECT_REJECT_MSG_STRUCT* pdn_discon_rej)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && pdn_discon_rej != NULL) {
    // EPS Bearer ID
    pdn_discon_rej->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    pdn_discon_rej->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // ESM Cause
    liblte_mme_unpack_esm_cause_ie(&msg_ptr, &pdn_discon_rej->esm_cause);

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &pdn_discon_rej->protocol_cnfg_opts);
      pdn_discon_rej->protocol_cnfg_opts_present = true;
    } else {
      pdn_discon_rej->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*********************************************************************
    Message Name: PDN Disconnect Request

    Description: Sent by the UE to the network to initiate release of
                 a PDN connection.

    Document Reference: 24.301 v10.2.0 Section 8.3.22
*********************************************************************/
LIBLTE_ERROR_ENUM
liblte_mme_pack_pdn_disconnect_request_msg(LIBLTE_MME_PDN_DISCONNECT_REQUEST_MSG_STRUCT* pdn_discon_req,
                                           LIBLTE_BYTE_MSG_STRUCT*                       msg)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (pdn_discon_req != NULL && msg != NULL) {
    // Protocol Discriminator and EPS Bearer ID
    *msg_ptr = (pdn_discon_req->eps_bearer_id << 4) | (LIBLTE_MME_PD_EPS_SESSION_MANAGEMENT);
    msg_ptr++;

    // Procedure Transaction ID
    *msg_ptr = pdn_discon_req->proc_transaction_id;
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_PDN_DISCONNECT_REQUEST;
    msg_ptr++;

    // Linked EPS Bearer Identity & Spare Half Octet
    *msg_ptr = 0;
    liblte_mme_pack_linked_eps_bearer_identity_ie(pdn_discon_req->linked_eps_bearer_id, 0, &msg_ptr);
    msg_ptr++;

    // Protocol Configuration Options
    if (pdn_discon_req->protocol_cnfg_opts_present) {
      *msg_ptr = LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI;
      msg_ptr++;
      liblte_mme_pack_protocol_config_options_ie(&pdn_discon_req->protocol_cnfg_opts, &msg_ptr);
    }

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}
LIBLTE_ERROR_ENUM
liblte_mme_unpack_pdn_disconnect_request_msg(LIBLTE_BYTE_MSG_STRUCT*                       msg,
                                             LIBLTE_MME_PDN_DISCONNECT_REQUEST_MSG_STRUCT* pdn_discon_req)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL && pdn_discon_req != NULL) {
    // EPS Bearer ID
    pdn_discon_req->eps_bearer_id = (*msg_ptr >> 4);
    msg_ptr++;

    // Procedure Transaction ID
    pdn_discon_req->proc_transaction_id = *msg_ptr;
    msg_ptr++;

    // Skip Message Type
    msg_ptr++;

    // Linked EPS Bearer Identity & Spare Half Octet
    liblte_mme_unpack_linked_eps_bearer_identity_ie(&msg_ptr, 0, &pdn_discon_req->linked_eps_bearer_id);
    msg_ptr++;

    // Protocol Configuration Options
    if (LIBLTE_MME_PROTOCOL_CONFIGURATION_OPTIONS_IEI == *msg_ptr) {
      msg_ptr++;
      liblte_mme_unpack_protocol_config_options_ie(&msg_ptr, &pdn_discon_req->protocol_cnfg_opts);
      pdn_discon_req->protocol_cnfg_opts_present = true;
    } else {
      pdn_discon_req->protocol_cnfg_opts_present = false;
    }

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

LIBLTE_ERROR_ENUM
liblte_mme_pack_activate_test_mode_complete_msg(LIBLTE_BYTE_MSG_STRUCT* msg, uint8 sec_hdr_type, uint32 count)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and skip indicator (always 0x0F)
    *msg_ptr =
        (LIBLTE_MME_MSG_TYPE_TEST_MODE_SKIP_INDICATOR << 4) | (LIBLTE_MME_MSG_TYPE_TEST_MODE_PROTOCOL_DISCRIMINATOR);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_ACTIVATE_TEST_MODE_COMPLETE;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

LIBLTE_ERROR_ENUM
liblte_mme_pack_close_ue_test_loop_complete_msg(LIBLTE_BYTE_MSG_STRUCT* msg, uint8 sec_hdr_type, uint32 count)
{
  LIBLTE_ERROR_ENUM err     = LIBLTE_ERROR_INVALID_INPUTS;
  uint8*            msg_ptr = msg->msg;

  if (msg != NULL) {
    if (LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS != sec_hdr_type) {
      // Protocol Discriminator and Security Header Type
      *msg_ptr = (sec_hdr_type << 4) | (LIBLTE_MME_PD_EPS_MOBILITY_MANAGEMENT);
      msg_ptr++;

      // MAC will be filled in later
      msg_ptr += 4;

      // Sequence Number
      *msg_ptr = count & 0xFF;
      msg_ptr++;
    }

    // Protocol Discriminator and skip indicator (always 0x0F)
    *msg_ptr =
        (LIBLTE_MME_MSG_TYPE_TEST_MODE_SKIP_INDICATOR << 4) | (LIBLTE_MME_MSG_TYPE_TEST_MODE_PROTOCOL_DISCRIMINATOR);
    msg_ptr++;

    // Message Type
    *msg_ptr = LIBLTE_MME_MSG_TYPE_CLOSE_UE_TEST_LOOP_COMPLETE;
    msg_ptr++;

    // Fill in the number of bytes used
    msg->N_bytes = msg_ptr - msg->msg;

    err = LIBLTE_SUCCESS;
  }

  return (err);
}

/*******************************************************************************
                              HELPER FUNCTIONS
*******************************************************************************/
const char* liblte_nas_sec_hdr_type_to_string(int code)
{
  switch (code) {
    LIBLTE_CASE_STR(LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS);
    LIBLTE_CASE_STR(LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY);
    LIBLTE_CASE_STR(LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY_AND_CIPHERED);
    LIBLTE_CASE_STR(LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY_WITH_NEW_EPS_SECURITY_CONTEXT);
    LIBLTE_CASE_STR(LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY_AND_CIPHERED_WITH_NEW_EPS_SECURITY_CONTEXT);
    LIBLTE_CASE_STR(LIBLTE_MME_SECURITY_HDR_TYPE_SERVICE_REQUEST);
    default:
      return "NAS Message Type Unknown";
  }
}

const char* liblte_nas_msg_type_to_string(int code)
{
  switch (code) {
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ATTACH_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ATTACH_ACCEPT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ATTACH_COMPLETE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ATTACH_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_DETACH_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_DETACH_ACCEPT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_TRACKING_AREA_UPDATE_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_TRACKING_AREA_UPDATE_ACCEPT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_TRACKING_AREA_UPDATE_COMPLETE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_TRACKING_AREA_UPDATE_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_EXTENDED_SERVICE_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_SERVICE_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_GUTI_REALLOCATION_COMMAND);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_GUTI_REALLOCATION_COMPLETE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_AUTHENTICATION_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_AUTHENTICATION_RESPONSE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_AUTHENTICATION_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_AUTHENTICATION_FAILURE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_IDENTITY_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_IDENTITY_RESPONSE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_SECURITY_MODE_COMMAND);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_SECURITY_MODE_COMPLETE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_SECURITY_MODE_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_EMM_STATUS);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_EMM_INFORMATION);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_DOWNLINK_NAS_TRANSPORT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_UPLINK_NAS_TRANSPORT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_CS_SERVICE_NOTIFICATION);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_DOWNLINK_GENERIC_NAS_TRANSPORT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_UPLINK_GENERIC_NAS_TRANSPORT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_ACCEPT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ACTIVATE_DEFAULT_EPS_BEARER_CONTEXT_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_ACCEPT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ACTIVATE_DEDICATED_EPS_BEARER_CONTEXT_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_MODIFY_EPS_BEARER_CONTEXT_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_MODIFY_EPS_BEARER_CONTEXT_ACCEPT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_MODIFY_EPS_BEARER_CONTEXT_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_DEACTIVATE_EPS_BEARER_CONTEXT_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_DEACTIVATE_EPS_BEARER_CONTEXT_ACCEPT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_PDN_CONNECTIVITY_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_PDN_CONNECTIVITY_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_PDN_DISCONNECT_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_PDN_DISCONNECT_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_ALLOCATION_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_ALLOCATION_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_MODIFICATION_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_BEARER_RESOURCE_MODIFICATION_REJECT);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ESM_INFORMATION_REQUEST);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ESM_INFORMATION_RESPONSE);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_NOTIFICATION);
    LIBLTE_CASE_STR(LIBLTE_MME_MSG_TYPE_ESM_STATUS);
    default:
      return "NAS Message Type Unknown";
  }
}
