
#include <stdio.h>
#include <stdint.h>
#include "assertions.h"
#include "conversions.h"

#include "sgw_lite_messages_types.h"

#include "NwGtpv2c.h"
#include "NwGtpv2cIe.h"
#include "NwGtpv2cMsg.h"
#include "NwGtpv2cMsgParser.h"

#include "s11_common.h"
#include "s11_ie_formatter.h"

NwRcT s11_imsi_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    Imsi_t *imsi;
    uint8_t i;
    uint8_t mask = 0x0F;
    uint8_t imsi_length = 2 * ieLength;

    DevAssert(arg != NULL);
    imsi = (Imsi_t *)arg;

    for (i = 0; i < ieLength * 2; i++) {
        if (mask == 0x0F) {
            imsi->digit[i] = (ieValue[i / 2] & (mask));
        } else {
            imsi->digit[i] = (ieValue[i / 2] & (mask)) >> 4;
        }
        imsi->digit[i] += '0';
        mask = ~mask;
    }

    if (imsi->digit[imsi_length - 1] == (0x0f + '0')) {
        imsi->digit[imsi_length - 1] = 0;
        imsi_length--;
    }

    imsi->length = imsi_length;

    S11_DEBUG("\t- IMSI length %d\n", imsi->length);
    S11_DEBUG("\t-      value  %*s\n", imsi->length, imsi->digit);

    return NW_OK;
}

int s11_imsi_ie_set(NwGtpv2cMsgHandleT *msg, const Imsi_t *imsi)
{
    uint8_t *temp = NULL;
    uint8_t imsi_length, i;
    NwRcT   rc;

    DevAssert(msg != NULL);
    DevAssert(imsi != NULL);

    /* In case of odd/even imsi */
    imsi_length = imsi->length % 2 == 0 ? imsi->length / 2 : imsi->length / 2 + 1;

    temp = calloc(imsi_length, sizeof(uint8_t));

    DevAssert(temp != NULL);

    for (i = 0; i < imsi->length; i++) {
        temp[i / 2] |= ((imsi->digit[i] - '0') & 0x0F) << (i % 2 ? 4 : 0);
    }

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_IMSI, imsi_length, 0, temp);
    DevAssert(NW_OK == rc);

    free(temp);

    return 0;
}

NwRcT s11_msisdn_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    Msisdn_t *msisdn;
    uint8_t   i;
    uint8_t   mask = 0x0F;
    uint8_t   msisdn_length = 2 * ieLength;

    DevAssert(arg != NULL);
    msisdn = (Msisdn_t *)arg;

    for (i = 0; i < ieLength * 2; i++) {
        if (mask == 0x0F) {
            msisdn->digit[i] = (ieValue[i / 2] & (mask));
        } else {
            msisdn->digit[i] = (ieValue[i / 2] & (mask)) >> 4;
        }
        msisdn->digit[i] += '0';
        mask = ~mask;
    }

    if (msisdn->digit[msisdn_length - 1] == (0x0f + '0')) {
        msisdn->digit[msisdn_length - 1] = 0;
        msisdn_length--;
    }

    msisdn->length = msisdn_length;

    S11_DEBUG("\t- MSISDN length %d\n", msisdn->length);
    S11_DEBUG("\t-        value  %*s\n", msisdn->length, (char *)msisdn->digit);

    return NW_OK;
}

NwRcT s11_mei_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    Mei_t *mei = (Mei_t *)arg;

    DevAssert(mei != NULL);

    

    return NW_OK;
}

NwRcT s11_pdn_type_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    pdn_type_t *pdn_type = (pdn_type_t *)arg;

    DevAssert(pdn_type != NULL);

    if (*ieValue == 1) {
        /* Only IPv4 */
        *pdn_type = IPv4;
    } else if (*ieValue == 2) {
        /* Only IPv6 */
        *pdn_type = IPv6;
    } else if (*ieValue == 3) {
        /* IPv4 and/or IPv6 */
        *pdn_type = IPv4_AND_v6;
    } else {
        S11_ERROR("Received unknown value for PDN Type: %u\n", *ieValue);
        return NW_GTPV2C_IE_INCORRECT;
    }

    S11_DEBUG("\t- PDN type %u\n", *pdn_type);

    return NW_OK;
}

int s11_pdn_type_ie_set(NwGtpv2cMsgHandleT *msg, const pdn_type_t *pdn_type)
{
    NwRcT   rc;
    uint8_t value;

    DevAssert(pdn_type != NULL);
    DevAssert(msg != NULL);

    switch (*pdn_type) {
        case IPv4:
            value = 1;
            break;
        case IPv6:
            value = 2;
            break;
        case IPv4_AND_v6:
        case IPv4_OR_v6:
            value = 3;
            break;
        default:
            S11_ERROR("Invalid PDN type received: %d\n", *pdn_type);
            return -1;
    }

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_PDN_TYPE, 1, 0, (NwU8T*)&value);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_rat_type_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    rat_type_t *rat_type = (rat_type_t *)arg;

    DevAssert(rat_type != NULL);

    switch (*ieValue) {
        case 1:
            *rat_type = RAT_UTRAN;
            break;
        case 2:
            *rat_type = RAT_GERAN;
            break;
        case 3:
            *rat_type = RAT_WLAN;
            break;
        case 4:
            *rat_type = RAT_GAN;
            break;
        case 5:
            *rat_type = RAT_HSPA_EVOLUTION;
            break;
        case 6:
            *rat_type = RAT_EUTRAN;
            break;
        default:
            S11_ERROR("Can't map GTP RAT type %u to EPC definition\n"
            "\tCheck TS.29.274 #8.17 for possible values\n", *ieValue);
            return NW_GTPV2C_IE_INCORRECT;
    }

    S11_DEBUG("\t- RAT type (%d): %d\n", *ieValue, *rat_type);

    return NW_OK;
}

int s11_rat_type_ie_set(NwGtpv2cMsgHandleT *msg, const rat_type_t *rat_type)
{
    NwRcT   rc;
    uint8_t value;

    DevAssert(rat_type != NULL);
    DevAssert(msg != NULL);

    switch (*rat_type) {
        case RAT_UTRAN:
            value = 1;
            break;
        case RAT_GERAN:
            value = 2;
            break;
        case RAT_WLAN:
            value = 3;
            break;
        case RAT_GAN:
            value = 4;
            break;
        case RAT_HSPA_EVOLUTION:
            value = 5;
            break;
        case RAT_EUTRAN:
            value = 6;
            break;
        default:
            S11_ERROR("Can't map RAT type %d to GTP RAT type\n"
            "\tCheck TS.29.274 #8.17 for possible values\n", *rat_type);
            return -1;
    }

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_RAT_TYPE, 1, 0, (NwU8T*)&value);
    DevAssert(NW_OK == rc);

    return 0;
}

int s11_ebi_ie_set(NwGtpv2cMsgHandleT *msg, const unsigned ebi)
{
    NwRcT   rc;
    uint8_t value = 0;

    value = ebi & 0x0F;

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_EBI, 1, 0,
                          &value);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_ebi_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    uint8_t *ebi = (uint8_t *)arg;

    DevAssert(ebi != NULL);

    *ebi = ieValue[0] & 0x0F;

    S11_DEBUG("\t- EBI %u\n", *ebi);

    return NW_OK;
}

NwRcT s11_cause_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    SGWCause_t *cause = (SGWCause_t *)arg;

    DevAssert(cause != NULL);

    *cause = ieValue[0];

    S11_DEBUG("\t- Cause %u\n", *cause);

    return NW_OK;
}

int s11_cause_ie_set(NwGtpv2cMsgHandleT *msg,
                     const gtp_cause_t  *cause)
{
    NwRcT rc;
    uint8_t value[6];

    DevAssert(msg != NULL);
    DevAssert(cause != NULL);

    value[0] = cause->cause_value;
    value[1] = ((cause->pce & 0x1) << 2) | ((cause->bce & 0x1) << 1) | (cause->cs & 0x1);
    if (cause->offending_ie_type != 0) {
        value[2] = cause->offending_ie_type;
        value[3] = (cause->offending_ie_length & 0xFF00) >> 8;
        value[4] = cause->offending_ie_length & 0x00FF;
        value[5] = cause->offending_ie_instance & 0x0F;
        rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_CAUSE, 6, 0, value);
    } else {
        rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_CAUSE, 2, 0, value);
    }

    DevAssert(NW_OK == rc);

    return rc == NW_OK ? 0 : -1;
}

NwRcT s11_bearer_context_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    bearer_to_create_t *bearer_to_create = (bearer_to_create_t *)arg;
    uint8_t read = 0;
    NwRcT   rc;

    DevAssert(bearer_to_create != NULL);

    while (ieLength > read) {
        NwGtpv2cIeTlvT *ie_p;

        ie_p = (NwGtpv2cIeTlvT*)&ieValue[read];

        switch (ie_p->t) {
            case NW_GTPV2C_IE_EBI:
                rc = s11_ebi_ie_get(ie_p->t, ie_p->l, ie_p->i,
                                    &ieValue[read + sizeof(NwGtpv2cIeTlvT)],
                                    &bearer_to_create->eps_bearer_id);
                DevAssert(NW_OK == rc);
                break;
            case NW_GTPV2C_IE_BEARER_LEVEL_QOS:
                break;
            default:
                S11_ERROR("Received unexpected IE %u\n", ie_p->t);
                return NW_GTPV2C_IE_INCORRECT;
        }

        read += (ntohs(ie_p->l) + sizeof(NwGtpv2cIeTlvT));
    }

    return NW_OK;
}

int s11_bearer_context_to_create_ie_set(
    NwGtpv2cMsgHandleT *msg, const bearer_to_create_t *bearer_to_create)
{
    NwRcT rc;

    DevAssert(msg != NULL);
    DevAssert(bearer_to_create != NULL);

    /* Start section for grouped IE: bearer context to create */
    rc = nwGtpv2cMsgGroupedIeStart(*msg, NW_GTPV2C_IE_BEARER_CONTEXT,
                                   NW_GTPV2C_IE_INSTANCE_ZERO);
    DevAssert(NW_OK == rc);

    s11_ebi_ie_set(msg, bearer_to_create->eps_bearer_id);

    /* End section for grouped IE: bearer context to create */
    rc = nwGtpv2cMsgGroupedIeEnd(*msg);
    DevAssert(NW_OK == rc);
    return 0;
}

NwRcT s11_bearer_context_to_modifiy_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    bearer_context_to_modify_t *bearer_to_modify = (bearer_context_to_modify_t *)arg;
    uint8_t read = 0;
    NwRcT   rc;

    DevAssert(bearer_to_modify != NULL);

    while (ieLength > read) {
        NwGtpv2cIeTlvT *ie_p;

        ie_p = (NwGtpv2cIeTlvT*)&ieValue[read];

        switch (ie_p->t) {
            case NW_GTPV2C_IE_EBI:
                rc = s11_ebi_ie_get(ie_p->t, ie_p->l, ie_p->i,
                                    &ieValue[read + sizeof(NwGtpv2cIeTlvT)],
                                    &bearer_to_modify->eps_bearer_id);
                DevAssert(NW_OK == rc);
                break;
            case NW_GTPV2C_IE_FTEID:
                rc = s11_fteid_ie_get(ie_p->t, ie_p->l, ie_p->i,
                                      &ieValue[read + sizeof(NwGtpv2cIeTlvT)],
                                      &bearer_to_modify->s1_eNB_fteid);
                break;
            default:
                S11_ERROR("Received unexpected IE %u\n", ie_p->t);
                return NW_GTPV2C_IE_INCORRECT;
        }

        read += (ntohs(ie_p->l) + sizeof(NwGtpv2cIeTlvT));
    }

    return NW_OK;
}

NwRcT s11_bearer_context_created_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    bearer_context_created_t *bearer_created = (bearer_context_created_t *)arg;
    uint8_t read = 0;
    NwRcT   rc;

    DevAssert(bearer_created != NULL);

    while (ieLength > read) {
        NwGtpv2cIeTlvT *ie_p;

        ie_p = (NwGtpv2cIeTlvT*)&ieValue[read];

        switch (ie_p->t) {
            case NW_GTPV2C_IE_EBI:
                rc = s11_ebi_ie_get(ie_p->t, ie_p->l, ie_p->i,
                                    &ieValue[read + sizeof(NwGtpv2cIeTlvT)],
                                    &bearer_created->eps_bearer_id);
                DevAssert(NW_OK == rc);
                break;
            case NW_GTPV2C_IE_FTEID:
                rc = s11_fteid_ie_get(ie_p->t, ie_p->l, ie_p->i,
                                      &ieValue[read + sizeof(NwGtpv2cIeTlvT)],
                                      &bearer_created->s1u_sgw_fteid);
                break;
            case NW_GTPV2C_IE_CAUSE:
                rc = s11_cause_ie_get(ie_p->t, ie_p->l, ie_p->i,
                                      &ieValue[read + sizeof(NwGtpv2cIeTlvT)],
                                      &bearer_created->cause);
                break;
            default:
                S11_ERROR("Received unexpected IE %u\n", ie_p->t);
                return NW_GTPV2C_IE_INCORRECT;
        }

        read += (ntohs(ie_p->l) + sizeof(NwGtpv2cIeTlvT));
    }
    return NW_OK;
}

int s11_bearer_context_created_ie_set(
    NwGtpv2cMsgHandleT *msg, const bearer_context_created_t *bearer)
{
    NwRcT rc;

    DevAssert(msg != NULL);
    DevAssert(bearer != NULL);

    /* Start section for grouped IE: bearer context created */
    rc = nwGtpv2cMsgGroupedIeStart(*msg, NW_GTPV2C_IE_BEARER_CONTEXT,
                                   NW_GTPV2C_IE_INSTANCE_ZERO);
    DevAssert(NW_OK == rc);

    s11_ebi_ie_set(msg, bearer->eps_bearer_id);

    rc = nwGtpv2cMsgAddIeCause(*msg, NW_GTPV2C_IE_INSTANCE_ZERO, bearer->cause,
                               0, 0, 0);
    DevAssert(NW_OK == rc);

    rc = nwGtpv2cMsgAddIeFteid(*msg, NW_GTPV2C_IE_INSTANCE_ZERO,
                               bearer->s1u_sgw_fteid.interface_type,
                               bearer->s1u_sgw_fteid.teid,
                               bearer->s1u_sgw_fteid.ipv4 ? htonl(bearer->s1u_sgw_fteid.ipv4_address) : 0,
                               bearer->s1u_sgw_fteid.ipv6 ? (NwU8T*)bearer->s1u_sgw_fteid.ipv6_address : NULL);
    DevAssert(NW_OK == rc);

    /* End section for grouped IE: bearer context created */
    rc = nwGtpv2cMsgGroupedIeEnd(*msg);
    DevAssert(NW_OK == rc);

    return 0;
}

/* This IE shall be included in the E-UTRAN initial attach,
 * PDP Context Activation and UE Requested PDN connectivity procedures.
 * This IE denotes the most stringent restriction as required
 * by any already active bearer context. If there are no already active bearer
 * contexts, this value is set to the least restrictive type.
 */
int s11_apn_restriction_ie_set(
    NwGtpv2cMsgHandleT *msg, const uint8_t apn_restriction)
{
    NwRcT rc;

    DevAssert(msg != NULL);

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_APN_RESTRICTION, 1, 0,
                          (NwU8T *)&apn_restriction);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_serving_network_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    ServingNetwork_t *serving_net = (ServingNetwork_t *)arg;

    DevAssert(serving_net != NULL);

    serving_net->mcc[1] = (ieValue[0] & 0xF0) >> 4;
    serving_net->mcc[0] = (ieValue[0] & 0x0F);
    serving_net->mcc[2] = (ieValue[1] & 0x0F);
    if ((ieValue[1] & 0xF0) == 0xF0) {
        /* Two digits MNC */
        serving_net->mnc[0] = 0;
        serving_net->mnc[2] = (ieValue[2] & 0xF0) >> 4;
        serving_net->mnc[1] = (ieValue[2] & 0x0F);
    } else {
        serving_net->mnc[0] = (ieValue[1] & 0xF0) >> 4;
        serving_net->mnc[2] = (ieValue[2] & 0xF0) >> 4;
        serving_net->mnc[1] = (ieValue[2] & 0x0F);
    }

    S11_DEBUG("\t- Serving network %d.%d\n",
              serving_net->mcc[0] * 100 + serving_net->mcc[1] * 10
              + serving_net->mcc[2],
              serving_net->mnc[0] * 100 + serving_net->mnc[1] * 10
              + serving_net->mnc[2]);

    return NW_OK;
}

int s11_serving_network_ie_set(
    NwGtpv2cMsgHandleT     *msg,
    const ServingNetwork_t *serving_network)
{
    NwRcT   rc;
    uint8_t value[3];

    DevAssert(msg != NULL);
    DevAssert(serving_network != NULL);

    /* MCC Decimal | MCC Hundreds */
    value[0] = ((serving_network->mcc[1] & 0x0F) << 4) |
                (serving_network->mcc[2] & 0x0F);
    value[1] = serving_network->mcc[0] & 0x0F;

    if ((serving_network->mnc[0] & 0xF) == 0xF) {
        /* Only two digits */
        value[1] |= 0xF0;
        value[2] = ((serving_network->mnc[2] & 0x0F) << 4) |
        (serving_network->mnc[1] & 0x0F);
    } else {
        value[1] |= (serving_network->mnc[2] & 0x0F) << 4;
        value[2] = ((serving_network->mnc[1] & 0x0F) << 4) |
        (serving_network->mnc[0] & 0x0F);
    }

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_SERVING_NETWORK, 3, 0, value);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_fteid_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    uint8_t offset = 0;
    FTeid_t *fteid = (FTeid_t *)arg;

    DevAssert(fteid != NULL);

    fteid->ipv4 = (ieValue[0] & 0x80) >> 7;
    fteid->ipv6 = (ieValue[0] & 0x40) >> 6;

    fteid->interface_type = ieValue[0] & 0x1F;
    S11_DEBUG("\t- F-TEID type %d\n", fteid->interface_type);

    /* Copy the TEID or GRE key */
    fteid->teid = ntoh_int32_buf(&ieValue[1]);

    S11_DEBUG("\t- TEID/GRE    %08x\n", fteid->teid);

    if (fteid->ipv4 == 1) {
        /* IPv4 present: copy the 4 bytes */
        memcpy(&fteid->ipv4_address, &ieValue[5], 4);
        offset = 4;

        S11_DEBUG("\t- IPv4 addr   "IPV4_ADDR"\n",
                  IPV4_ADDR_FORMAT(fteid->ipv4_address));
    }

    if (fteid->ipv6 == 1) {
        char ipv6_ascii[40];
        /* IPv6 present: copy the 16 bytes
         * WARNING: if Ipv4 is present, 4 bytes of offset should be applied
         */
        memcpy(fteid->ipv6_address, &ieValue[5 + offset], 16);

        inet_ntop(AF_INET6, fteid->ipv6_address, ipv6_ascii, 40);
        S11_DEBUG("\t- IPv6 addr   %s\n", ipv6_ascii);
    }

    return NW_OK;
}

NwRcT s11_paa_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    uint8_t offset = 0;
    PAA_t *paa = (PAA_t *)arg;

    DevAssert(paa != NULL);

    paa->pdn_type = ieValue[0] & 0x07;
    S11_DEBUG("\t- PAA type  %d\n", paa->pdn_type);
    if (paa->pdn_type & 0x2) {
        char ipv6_ascii[40];
        /* IPv6 present: copy the 16 bytes
         * WARNING: if both ipv4 and ipv6 are present,
         *          17 bytes of offset should be applied for ipv4
         * NOTE: an ipv6 prefix length is prepend
         * NOTE: in Rel.8 the prefix length has a default value of /64
         */
        paa->ipv6_prefix_length = ieValue[1];
        memcpy(paa->ipv6_address, &ieValue[2], 16);

        inet_ntop(AF_INET6, paa->ipv6_address, ipv6_ascii, 40);
        S11_DEBUG("\t- IPv6 addr %s/%u\n", ipv6_ascii, paa->ipv6_prefix_length);
    }
    if (paa->pdn_type == 3) {
        offset = 17;
    }
    if (paa->pdn_type & 0x1) {
        memcpy(paa->ipv4_address, &ieValue[1 + offset], 4);

        S11_DEBUG("\t- IPv4 addr "IPV4_ADDR"\n",
                  paa->ipv4_address[0], paa->ipv4_address[1],
                  paa->ipv4_address[2], paa->ipv4_address[3]);
    }
    paa->pdn_type -= 1;
    return NW_OK;
}

int s11_paa_ie_set(NwGtpv2cMsgHandleT *msg, const PAA_t *paa)
{
    /* ipv4 address = 4 + ipv6 address = 16 + ipv6 prefix length = 1
     * + pdn_type = 1
     * = maximum of 22 bytes
     */
    uint8_t temp[22];
    uint8_t pdn_type;
    uint8_t offset = 0;
    NwRcT   rc;

    DevAssert(paa != NULL);

    pdn_type = paa->pdn_type + 1;

    temp[offset] = pdn_type;
    offset++;

    if (pdn_type & 0x2) {
        /* If ipv6 or ipv4v6 present */
        temp[1] = paa->ipv6_prefix_length;
        memcpy(&temp[2], paa->ipv6_address, 16);
        offset += 17;
    }

    if (pdn_type & 0x1) {
        memcpy(&temp[offset], paa->ipv4_address, 4);
        offset += 4;
    }

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_PAA, offset, 0,
                          temp);
    DevAssert(NW_OK == rc);

    return 0;
}

/* The encoding of the APN shall follow the Name Syntax defined in RFC 2181,
 * RFC 1035 and RFC 1123. The APN consists of one or more labels. Each label
 * is coded as a one octet length field followed by that number of octets
 * coded as 8 bit ASCII characters. Following RFC 1035 the labels shall consist
 * only of the alphabetic characters (A-Z and a-z), digits (0-9)
 * and the hyphen (-).
 */
NwRcT s11_apn_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    uint8_t read = 1;
    uint8_t word_length;
    char *apn = (char *)arg;

    DevAssert(apn != NULL);

    DevCheck(ieLength <= APN_MAX_LENGTH, ieLength, APN_MAX_LENGTH, 0);

    word_length = ieValue[0];

    while (read < ieLength) {
        if (word_length > 0) {
            apn[read - 1] = ieValue[read];
            word_length--;
        } else {
            /* This is not an alphanumeric character */
            word_length = ieValue[read];
            /* Replace the length attribute by '.' */
            apn[read - 1] = '.';
        }

        read ++;
    }
    apn[read - 1] = '\0';

    S11_DEBUG("\t- APN %s\n", apn);

    return NW_OK;
}

int s11_apn_ie_set(NwGtpv2cMsgHandleT *msg, const char *apn)
{
    NwRcT    rc;
    uint8_t *value;
    uint8_t  apn_length;
    uint8_t  offset = 0;
    uint8_t *last_size;
    uint8_t  word_length = 0;

    DevAssert(apn != NULL);
    DevAssert(msg != NULL);

    apn_length = strlen(apn);

    value = calloc(apn_length + 1, sizeof(uint8_t));
    last_size = &value[0];

    while (apn[offset]) {
        /* We replace the . by the length of the word */
        if (apn[offset] == '.') {
            *last_size = word_length;
            word_length = 0;
            last_size = &value[offset + 1];
        } else {
            word_length++;
            value[offset + 1] = apn[offset];
        }
        offset++;
    }

    *last_size = word_length;

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_APN, apn_length + 1, 0, value);
    DevAssert(NW_OK == rc);

    free(value);

    return 0;
}

NwRcT s11_ambr_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    ambr_t *ambr = (ambr_t *)arg;

    DevAssert(ambr != NULL);

    ambr->br_ul = ntoh_int32_buf(&ieValue[0]);
    ambr->br_dl = ntoh_int32_buf(&ieValue[4]);

    S11_DEBUG("\t- AMBR UL %u\n", ambr->br_ul);
    S11_DEBUG("\t- AMBR DL %u\n", ambr->br_dl);

    return NW_OK;
}

NwRcT s11_uli_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    Uli_t *uli = (Uli_t *)arg;

    DevAssert(uli != NULL);

    uli->present = ieValue[0];

    if (uli->present & ULI_CGI) {
        
    }
    return NW_OK;
}

int s11_bearer_qos_ie_set(NwGtpv2cMsgHandleT *msg, const BearerQOS_t *bearer_qos)
{
    NwRcT   rc;
    uint8_t value[18];

    DevAssert(msg != NULL);
    DevAssert(bearer_qos != NULL);

    value[0] = (bearer_qos->pci << 6) | (bearer_qos->pl << 2) | (bearer_qos->pvi);
    value[1] =  bearer_qos->qci;

    /* TODO: check endianness */
    memcpy(&value[2],  &bearer_qos->mbr.br_ul, 4);
    memcpy(&value[6],  &bearer_qos->mbr.br_dl, 4);
    memcpy(&value[10], &bearer_qos->gbr.br_ul, 4);
    memcpy(&value[14], &bearer_qos->gbr.br_dl, 4);

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_BEARER_LEVEL_QOS, 18, 0, value);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_ip_address_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    gtp_ip_address_t *ip_address = (gtp_ip_address_t *)arg;

    DevAssert(ip_address != NULL);

    if (ieLength == 4) {
        /* This is an IPv4 Address */
        ip_address->present = GTP_IP_ADDR_v4;
        memcpy(ip_address->address.v4, ieValue, 4);
    } else if (ieLength == 16) {
        /* This is an IPv6 Address */
        ip_address->present = GTP_IP_ADDR_v6;
        memcpy(ip_address->address.v6, ieValue, 16);
    } else {
        /* Length doesn't lie in possible values */
        return NW_GTPV2C_IE_INCORRECT;
    }
    return NW_OK;
}

int s11_ip_address_ie_set(NwGtpv2cMsgHandleT     *msg,
                           const gtp_ip_address_t *ip_address)
{
    return 0;
}

NwRcT s11_delay_value_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    DelayValue_t *delay_value = (DelayValue_t *)arg;

    DevAssert(arg != NULL);

    if (ieLength != 1) {
        return NW_GTPV2C_IE_INCORRECT;
    }

    *delay_value = ieValue[0];

    S11_DEBUG("\t - Delay Value %u\n", *delay_value);

    return NW_OK;
}

int s11_delay_value_ie_set(NwGtpv2cMsgHandleT *msg,
                           const DelayValue_t *delay_value)
{
    uint8_t value;
    NwRcT   rc;

    DevAssert(msg != NULL);
    DevAssert(delay_value != NULL);

    value = *delay_value;

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_DELAY_VALUE, 1, 0, (NwU8T*)&value);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_ue_time_zone_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    UETimeZone_t *ue_time_zone = (UETimeZone_t *)arg;

    DevAssert(ue_time_zone != NULL);

    if (ieLength != 2) {
        return NW_GTPV2C_IE_INCORRECT;
    }

    ue_time_zone->time_zone = ieValue[0];
    ue_time_zone->daylight_saving_time = ieValue[1] & 0x03;

    S11_DEBUG("\t - Time Zone    %u\n", ue_time_zone->time_zone);
    S11_DEBUG("\t - Daylight SVT %u\n", ue_time_zone->daylight_saving_time);

    return NW_OK;
}

int s11_ue_time_zone_ie_set(NwGtpv2cMsgHandleT *msg,
                            const UETimeZone_t *ue_time_zone)
{
    uint8_t value[2];
    NwRcT   rc;

    DevAssert(msg != NULL);
    DevAssert(ue_time_zone != NULL);

    value[0] = ue_time_zone->time_zone;
    value[1] = ue_time_zone->daylight_saving_time;

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_UE_TIME_ZONE, 2, 0, value);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_target_identification_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    target_identification_t *target_identification = (target_identification_t *)arg;

    DevAssert(target_identification != NULL);

    target_identification->target_type = ieValue[0];
    switch (target_identification->target_type) {
        case TARGET_ID_RNC_ID:
        {
            target_identification->target_id.rnc_id.lac = (ieValue[4] << 8) |
            ieValue[5];
            target_identification->target_id.rnc_id.rac = ieValue[6];
            if (ieLength == 11) {
                /* Extended RNC id */
                target_identification->target_id.rnc_id.rnc_id = (ieValue[7] << 24) |
                (ieValue[8] << 16) | (ieValue[9] << 8) | (ieValue[10]);
            } else if (ieLength == 9) {
                /* Normal RNC id */
                target_identification->target_id.rnc_id.rnc_id = (ieValue[7] << 8) |
                ieValue[8];
            } else {
                /* This case is not possible */
                return NW_GTPV2C_IE_INCORRECT;
            }
            S11_DEBUG("\t\t- LAC 0x%04x\n", target_identification->target_id.rnc_id.lac);
            S11_DEBUG("\t\t- RAC 0x%02x\n", target_identification->target_id.rnc_id.rac);
            S11_DEBUG("\t\t- RNC 0x%08x\n", target_identification->target_id.rnc_id.rnc_id);
        } break;
        case TARGET_ID_MACRO_ENB_ID: {
            if (ieLength != 9) {
                return NW_GTPV2C_IE_INCORRECT;
            }
            target_identification->target_id.macro_enb_id.enb_id = ((ieValue[4] & 0x0F) << 16) |
            (ieValue[5] << 8) | ieValue[6];
            target_identification->target_id.macro_enb_id.tac = (ieValue[7] << 8) | ieValue[8];
            S11_DEBUG("\t\t- ENB Id 0x%06x\n", target_identification->target_id.macro_enb_id.enb_id);
            S11_DEBUG("\t\t- TAC    0x%04x\n", target_identification->target_id.macro_enb_id.tac);
        } break;
        case TARGET_ID_HOME_ENB_ID: {
            if (ieLength != 10) {
                return NW_GTPV2C_IE_INCORRECT;
            }
            target_identification->target_id.home_enb_id.enb_id = ((ieValue[4] & 0x0F) << 14) |
            (ieValue[5] << 16) | (ieValue[6] << 8) | ieValue[7];
            target_identification->target_id.home_enb_id.tac = (ieValue[8] << 8) | ieValue[9];
            S11_DEBUG("\t\t- ENB Id 0x%07x\n", target_identification->target_id.home_enb_id.enb_id);
            S11_DEBUG("\t\t- TAC    0x%04x\n", target_identification->target_id.home_enb_id.tac);
        } break;
        default:
            return NW_GTPV2C_IE_INCORRECT;
    }

    return NW_OK;
}

NwRcT s11_bearer_flags_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    bearer_flags_t *bearer_flags = (bearer_flags_t *)arg;

    DevAssert(arg != NULL);

    if (ieLength != 1) {
        return NW_GTPV2C_IE_INCORRECT;
    }

    bearer_flags->ppc = ieValue[0] & 0x01;
    bearer_flags->vb  = ieValue[0] & 0x02;

    return NW_OK;
}

int s11_bearer_flags_ie_set(NwGtpv2cMsgHandleT   *msg,
                            const bearer_flags_t *bearer_flags)
{
    NwRcT   rc;
    uint8_t value;

    DevAssert(msg != NULL);
    DevAssert(bearer_flags != NULL);

    value = (bearer_flags->vb << 1) | bearer_flags->ppc;

    rc = nwGtpv2cMsgAddIe(*msg, NW_GTPV2C_IE_BEARER_FLAGS, 1, 0, (NwU8T*)&value);
    DevAssert(NW_OK == rc);

    return 0;
}

NwRcT s11_indication_flags_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    indication_flags_t *indication_flags = (indication_flags_t*)arg;

    DevAssert(indication_flags != NULL);

    if (ieLength < 3) {
        return NW_GTPV2C_IE_INCORRECT;
    }

    *indication_flags = ieValue[0] | (ieValue[1] << 8) | (ieValue[2] << 16);

    S11_DEBUG("\t- Indication Flags %06x\n", *indication_flags);

    return NW_OK;
}

NwRcT s11_fqcsid_ie_get(
    NwU8T ieType, NwU8T ieLength, NwU8T ieInstance, NwU8T *ieValue, void *arg)
{
    FQ_CSID_t *fq_csid = (FQ_CSID_t *)arg;

    DevAssert(fq_csid != NULL);

    fq_csid->node_id_type = (ieValue[0] & 0xF0) >> 4;
    S11_DEBUG("\t- FQ-CSID type %u\n", fq_csid->node_id_type);

    /* NOTE: Values of Number of CSID other than 1 are only employed in the
     * Delete PDN Connection Set Request and Response.
     */
    if ((ieValue[0] & 0x0F) != 1) {
        return NW_GTPV2C_IE_INCORRECT;
    }
    switch (fq_csid->node_id_type) {
        case GLOBAL_UNICAST_IPv4: {
            if (ieLength != 7) {
                return NW_GTPV2C_IE_INCORRECT;
            }
            fq_csid->node_id.unicast_ipv4 = (ieValue[1] << 24) |
            (ieValue[2] << 16) | (ieValue[3] << 8) | (ieValue[4]);
            fq_csid->csid = (ieValue[5] << 8) | ieValue[6];

            S11_DEBUG("\t- v4 address ["IPV4_ADDR"]\n",
                      IPV4_ADDR_FORMAT(fq_csid->node_id.unicast_ipv4));
        } break;
        case GLOBAL_UNICAST_IPv6: {
            char ipv6[40];
            if (ieLength != 19) {
                return NW_GTPV2C_IE_INCORRECT;
            }
            memcpy(fq_csid->node_id.unicast_ipv6, &ieValue[1], 16);
            fq_csid->csid = (ieValue[17] << 8) | ieValue[18];

            /* Convert the ipv6 to printable string */
            inet_ntop(AF_INET6, fq_csid->node_id.unicast_ipv6, ipv6, 40);

            S11_DEBUG("\t- v6 address [%s]\n", fq_csid->node_id.unicast_ipv6);
        } break;
        default:
            return NW_GTPV2C_IE_INCORRECT;
    }

    S11_DEBUG("\t- CSID 0x%04x\n", fq_csid->csid);
    return NW_OK;
}
