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

  Eurecom OpenAirInterface
  Copyright(c) 1999 - 2013 Eurecom

  This program is free software; you can redistribute it and/or modify it
  under the terms and conditions of the GNU General Public License,
  version 2, as published by the Free Software Foundation.

  This program is distributed in the hope 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
  this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.

  The full GNU General Public License is included in this distribution in
  the file called "COPYING".

  Contact Information
  Openair Admin: openair_admin@eurecom.fr
  Openair Tech : openair_tech@eurecom.fr
  Forums       : http://forums.eurecom.fr/openairinterface
  Address      : EURECOM, Campus SophiaTech, 450 Route des Chappes
                 06410 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, int *length)
{
    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;
    *length = sprintf(msisdn, "%*s", (int)hdr_msisdn->avp_value->os.len,
                      hdr_msisdn->avp_value->os.data);
    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;
}
