/*******************************************************************************
    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;
}
