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

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


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

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

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

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

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

#include <stdint.h>

#include "common_types.h"
#include "s6a_defs.h"

#include "assertions.h"

static inline
int s6a_parse_subscriber_status(struct avp_hdr *hdr_sub_status,
                                subscriber_status_t *sub_status)
{
    DevCheck(hdr_sub_status->avp_value->u32 < SS_MAX,
             hdr_sub_status->avp_value->u32, SS_MAX, 0);

    *sub_status = hdr_sub_status->avp_value->u32;

    return 0;
}

static inline
int s6a_parse_msisdn(struct avp_hdr *hdr_msisdn, char *msisdn, uint8_t *length)
{
    int ret;

    DevCheck(hdr_msisdn->avp_value->os.len <= MSISDN_LENGTH,
             hdr_msisdn->avp_value->os.len, MSISDN_LENGTH, 0);

    if (hdr_msisdn->avp_value->os.len == 0)
        return 0;
    ret = sprintf(msisdn, "%*s", (int)hdr_msisdn->avp_value->os.len,
                  hdr_msisdn->avp_value->os.data);

    *length = ret;

    return 0;
}

static inline
int s6a_parse_network_access_mode(struct avp_hdr *hdr_network_am,
                                  network_access_mode_t *access_mode)
{
    DevCheck(hdr_network_am->avp_value->u32 < NAM_MAX &&
             hdr_network_am->avp_value->u32 != NAM_RESERVED,
             hdr_network_am->avp_value->u32, NAM_MAX, NAM_RESERVED);

    *access_mode = hdr_network_am->avp_value->u32;

    return 0;
}

static inline
int s6a_parse_access_restriction_data(struct avp_hdr *hdr_access_restriction,
                                      access_restriction_t *access_restriction)
{
    DevCheck(hdr_access_restriction->avp_value->u32 < ARD_MAX,
             hdr_access_restriction->avp_value->u32, ARD_MAX, 0);
    *access_restriction = hdr_access_restriction->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_bitrate(struct avp_hdr *hdr_bitrate, bitrate_t *bitrate)
{
    *bitrate = hdr_bitrate->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_ambr(struct avp *avp_ambr, ambr_t *ambr)
{
    struct avp *avp = NULL;
    struct avp_hdr *hdr;

    CHECK_FCT(fd_msg_browse(avp_ambr, MSG_BRW_FIRST_CHILD, &avp, NULL));
    if (!avp) {
        /* Child avps for ambr are mandatory */
        return -1;
    }
    while(avp) {
        CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
        switch(hdr->avp_code) {
            case AVP_CODE_BANDWIDTH_UL:
                CHECK_FCT(s6a_parse_bitrate(hdr, &ambr->br_ul));
                break;
            case AVP_CODE_BANDWIDTH_DL:
                CHECK_FCT(s6a_parse_bitrate(hdr, &ambr->br_dl));
                break;
            default:
                return -1;
        }
        /* Go to next AVP in the grouped AVP */
        CHECK_FCT(fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL));
    }
    return 0;
}

static inline
int s6a_parse_all_apn_conf_inc_ind(struct avp_hdr *hdr, all_apn_conf_ind_t *ptr)
{
    DevCheck(hdr->avp_value->u32 < ALL_APN_MAX,
             hdr->avp_value->u32, ALL_APN_MAX, 0);
    *ptr = hdr->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_pdn_type(struct avp_hdr *hdr, pdn_type_t *pdn_type)
{
    DevCheck(hdr->avp_value->u32 < IP_MAX,
             hdr->avp_value->u32, IP_MAX, 0);
    *pdn_type = hdr->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_service_selection(struct avp_hdr *hdr_service_selection,
                                char *service_selection, int *length)
{
    DevCheck(hdr_service_selection->avp_value->os.len <= APN_MAX_LENGTH,
             hdr_service_selection->avp_value->os.len, APN_MAX_LENGTH, 0);
    *length = sprintf(service_selection, "%*s",
                      (int)hdr_service_selection->avp_value->os.len,
                      hdr_service_selection->avp_value->os.data);
    return 0;
}

static inline
int s6a_parse_qci(struct avp_hdr *hdr, qci_t *qci)
{
    DevCheck(hdr->avp_value->u32 < QCI_MAX,
             hdr->avp_value->u32, QCI_MAX, 0);
    *qci = hdr->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_priority_level(struct avp_hdr *hdr, priority_level_t *priority_level)
{
    DevCheck(hdr->avp_value->u32 <= PRIORITY_LEVEL_MAX &&
             hdr->avp_value->u32 >= PRIORITY_LEVEL_MIN, hdr->avp_value->u32,
             PRIORITY_LEVEL_MAX, PRIORITY_LEVEL_MIN);
    *priority_level = (priority_level_t)hdr->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_pre_emp_capability(struct avp_hdr *hdr, pre_emp_capability_t *pre_emp_capability)
{
    DevCheck(hdr->avp_value->u32 < PRE_EMPTION_CAPABILITY_MAX, hdr->avp_value->u32,
             PRE_EMPTION_CAPABILITY_MAX, 0);
    *pre_emp_capability = hdr->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_pre_emp_vulnerability(struct avp_hdr *hdr, pre_emp_vulnerability_t *pre_emp_vulnerability)
{
    DevCheck(hdr->avp_value->u32 < PRE_EMPTION_VULNERABILITY_MAX, hdr->avp_value->u32,
             PRE_EMPTION_VULNERABILITY_MAX, 0);
    *pre_emp_vulnerability = hdr->avp_value->u32;
    return 0;
}

static inline
int s6a_parse_allocation_retention_priority(struct avp *avp_arp,
                                            allocation_retention_priority_t *ptr)
{
    struct avp *avp = NULL;
    struct avp_hdr *hdr;

    /* If the Pre-emption-Capability AVP is not present in the
     * Allocation-Retention-Priority AVP, the default value shall be
     * PRE-EMPTION_CAPABILITY_DISABLED (1).
     */
    ptr->pre_emp_capability = PRE_EMPTION_CAPABILITY_DISABLED;

    /* If the Pre-emption-Vulnerability AVP is not present in the
     * Allocation-Retention-Priority AVP, the default value shall be
     * PRE-EMPTION_VULNERABILITY_ENABLED (0).
     */
    ptr->pre_emp_vulnerability = PRE_EMPTION_VULNERABILITY_ENABLED;

    CHECK_FCT(fd_msg_browse(avp_arp, MSG_BRW_FIRST_CHILD, &avp, NULL));
    while(avp) {
        CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
        switch(hdr->avp_code) {
            case AVP_CODE_PRIORITY_LEVEL:
                CHECK_FCT(s6a_parse_priority_level(hdr, &ptr->priority_level));
                break;
            case AVP_CODE_PRE_EMPTION_CAPABILITY:
                CHECK_FCT(s6a_parse_pre_emp_capability(hdr, &ptr->pre_emp_capability));
                break;
            case AVP_CODE_PRE_EMPTION_VULNERABILITY:
                CHECK_FCT(s6a_parse_pre_emp_vulnerability(hdr, &ptr->pre_emp_vulnerability));
                break;
            default:
                return -1;
        }
        /* Go to next AVP in the grouped AVP */
        CHECK_FCT(fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL));
    }
    return 0;
}

static inline
int s6a_parse_eps_subscribed_qos_profile(struct avp *avp_qos,
                                         eps_subscribed_qos_profile_t *ptr)
{
    struct avp *avp = NULL;
    struct avp_hdr *hdr;

    CHECK_FCT(fd_msg_browse(avp_qos, MSG_BRW_FIRST_CHILD, &avp, NULL));
    while(avp) {
        CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
        switch(hdr->avp_code) {
            case AVP_CODE_QCI:
                CHECK_FCT(s6a_parse_qci(hdr, &ptr->qci));
                break;
            case AVP_CODE_ALLOCATION_RETENTION_PRIORITY:
                CHECK_FCT(s6a_parse_allocation_retention_priority(avp, &ptr->allocation_retention_priority));
                break;
            default:
                return -1;
        }
        /* Go to next AVP in the grouped AVP */
        CHECK_FCT(fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL));
    }
    return 0;
}

static inline
int s6a_parse_ip_address(struct avp_hdr *hdr, ip_address_t *ip_address)
{
    uint16_t ip_type;

    DevCheck(hdr->avp_value->os.len >= 2, hdr->avp_value->os.len, 2, 0);

    ip_type = (hdr->avp_value->os.data[0] << 8) | (hdr->avp_value->os.data[1]);

    if (ip_type == IANA_IPV4) {
        /* This is an IPv4 address */
        ip_address->pdn_type = IPv4;
        DevCheck(hdr->avp_value->os.len == 6, hdr->avp_value->os.len, 6, ip_type);
        memcpy(ip_address->address.ipv4_address, &hdr->avp_value->os.data[2], 4);
    } else if (ip_type == IANA_IPV6) {
        /* This is an IPv6 address */
        ip_address->pdn_type = IPv6;
        DevCheck(hdr->avp_value->os.len == 18, hdr->avp_value->os.len, 18, ip_type);
        memcpy(ip_address->address.ipv6_address, &hdr->avp_value->os.data[2], 16);
    } else {
        /* unhandled case... */
        return -1;
    }
    return 0;
}

static inline
int s6a_parse_apn_configuration(struct avp *avp_apn_conf_prof, apn_configuration_t *apn_config)
{
    struct avp *avp = NULL;
    struct avp_hdr *hdr;

    CHECK_FCT(fd_msg_browse(avp_apn_conf_prof, MSG_BRW_FIRST_CHILD, &avp, NULL));
    while(avp) {
        CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
        switch(hdr->avp_code) {
            case AVP_CODE_CONTEXT_IDENTIFIER:
                apn_config->context_identifier = hdr->avp_value->u32;
                break;
            case AVP_CODE_SERVED_PARTY_IP_ADDRESS:
                if (apn_config->nb_ip_address == 2) {
                    DevMessage("Only two IP addresses can be provided");
                }
                CHECK_FCT(s6a_parse_ip_address(hdr, &apn_config->ip_address[apn_config->nb_ip_address]));
                apn_config->nb_ip_address++;
                break;
            case AVP_CODE_PDN_TYPE:
                CHECK_FCT(s6a_parse_pdn_type(hdr, &apn_config->pdn_type));
                break;
            case AVP_CODE_SERVICE_SELECTION:
                CHECK_FCT(s6a_parse_service_selection(hdr, apn_config->service_selection,
                                                      &apn_config->service_selection_length));
                break;
            case AVP_CODE_EPS_SUBSCRIBED_QOS_PROFILE:
                CHECK_FCT(s6a_parse_eps_subscribed_qos_profile(avp, &apn_config->subscribed_qos));
                break;
            case AVP_CODE_AMBR:
                CHECK_FCT(s6a_parse_ambr(avp, &apn_config->ambr));
                break;
        }
        /* Go to next AVP in the grouped AVP */
        CHECK_FCT(fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL));
    }
    return 0;
}

static inline
int s6a_parse_apn_configuration_profile(struct avp *avp_apn_conf_prof,
                                        apn_config_profile_t *apn_config_profile)
{
    struct avp *avp = NULL;
    struct avp_hdr *hdr;

    CHECK_FCT(fd_msg_browse(avp_apn_conf_prof, MSG_BRW_FIRST_CHILD, &avp, NULL));
    while(avp) {
        CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
        switch(hdr->avp_code) {
            case AVP_CODE_CONTEXT_IDENTIFIER:
                apn_config_profile->context_identifier = hdr->avp_value->u32;
                break;
            case AVP_CODE_ALL_APN_CONFIG_INC_IND:
                CHECK_FCT(s6a_parse_all_apn_conf_inc_ind(hdr, &apn_config_profile->all_apn_conf_ind));
                break;
            case AVP_CODE_APN_CONFIGURATION: {
                DevCheck(apn_config_profile->nb_apns < MAX_APN_PER_UE,
                         apn_config_profile->nb_apns, MAX_APN_PER_UE, 0);
                CHECK_FCT(s6a_parse_apn_configuration(
                    avp, &apn_config_profile->apn_configuration[apn_config_profile->nb_apns]));
                apn_config_profile->nb_apns++;
            } break;
        }
        /* Go to next AVP in the grouped AVP */
        CHECK_FCT(fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL));
    }
    return 0;
}

int s6a_parse_subscription_data(struct avp *avp_subscription_data,
                                subscription_data_t *subscription_data)
{
    struct avp *avp = NULL;
    struct avp_hdr *hdr;

    CHECK_FCT(fd_msg_browse(avp_subscription_data, MSG_BRW_FIRST_CHILD, &avp, NULL));
    while(avp) {
        CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));

        switch(hdr->avp_code) {
            case AVP_CODE_SUBSCRIBER_STATUS:
                CHECK_FCT(s6a_parse_subscriber_status(hdr, &subscription_data->subscriber_status));
                break;
            case AVP_CODE_MSISDN:
                CHECK_FCT(s6a_parse_msisdn(hdr, subscription_data->msisdn,
                                           &subscription_data->msisdn_length));
                break;
            case AVP_CODE_NETWORK_ACCESS_MODE:
                CHECK_FCT(s6a_parse_network_access_mode(hdr, &subscription_data->access_mode));
                break;
            case AVP_CODE_ACCESS_RESTRICTION_DATA:
                CHECK_FCT(s6a_parse_access_restriction_data(hdr, &subscription_data->access_restriction));
                break;
            case AVP_CODE_AMBR:
                CHECK_FCT(s6a_parse_ambr(avp, &subscription_data->subscribed_ambr));
                break;
            case AVP_CODE_APN_CONFIGURATION_PROFILE:
                CHECK_FCT(s6a_parse_apn_configuration_profile(avp, &subscription_data->apn_config_profile));
                break;
            case AVP_CODE_SUBSCRIBED_PERIODIC_RAU_TAU_TIMER:
                subscription_data->rau_tau_timer = hdr->avp_value->u32;
                break;
            default:
                return -1;
        }
        /* Go to next AVP in the grouped AVP */
        CHECK_FCT(fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL));
    }
    return 0;
}