/*******************************************************************************
    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.

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

/*! \file mme_app_config.c
 * \brief
 * \author Lionel GAUTHIER
 * \version 1.0
 * \company Eurecom
 * \email: lionel.gauthier@eurecom.fr
 */
#define MME_APP
#define MME_APP_CONFIG_C

#include <string.h>
#include <libconfig.h>
#include "mme_app_config.h"

int mme_app_config_init(char* lib_config_file_name_pP, mme_app_config_t* config_pP)
{

  config_t          cfg;
  config_setting_t *setting_sgw                          = NULL;
  char             *sgw_interface_name_for_S1u_S12_S4_up = NULL;
  char             *sgw_ipv4_address_for_S1u_S12_S4_up   = NULL;
  char             *sgw_interface_name_for_S5_S8_up      = NULL;
  char             *sgw_ipv4_address_for_S5_S8_up        = NULL;
  char             *sgw_interface_name_for_S11           = NULL;
  char             *sgw_ipv4_address_for_S11             = NULL;

  config_setting_t *setting_pgw                  = NULL;
  config_setting_t *subsetting                   = NULL;
  config_setting_t *sub2setting                  = NULL;
  char             *pgw_interface_name_for_S5_S8 = NULL;
  char             *pgw_ipv4_address_for_S5_S8   = NULL;
  char             *pgw_interface_name_for_SGI   = NULL;
  char             *pgw_ipv4_address_for_SGI     = NULL;

  char             *delimiters=NULL;
  char             *saveptr1= NULL;
  char             *astring = NULL;
  char             *atoken  = NULL;
  char             *atoken2 = NULL;
  char             *address = NULL;
  char             *cidr    = NULL;
  char             *mask    = NULL;
  int               num     = 0;
  int               i       = 0;
  int               jh, jn;
  unsigned char     buf_in6_addr[sizeof(struct in6_addr)];
  struct in6_addr   addr6_start;
  struct in6_addr   addr6_mask;
  int               prefix_mask;
  uint64_t          counter64;
  unsigned char     buf_in_addr[sizeof(struct in_addr)];
  struct in_addr    addr_start;
  struct in_addr    addr_end;


  memset((char*)config_pP, 0 , sizeof(mme_app_config_t));

  config_init(&cfg);

  if(lib_config_file_name_pP != NULL) {
    /* Read the file. If there is an error, report it and exit. */
    if(! config_read_file(&cfg, lib_config_file_name_pP)) {
      MME_APP_ERROR("%s:%d - %s\n", lib_config_file_name_pP, config_error_line(&cfg), config_error_text(&cfg));
      config_destroy(&cfg);
      AssertFatal (1 == 0, "Failed to parse eNB configuration file %s!\n", lib_config_file_name_pP);
    }
  } else {
    SPGW_APP_ERROR("No SP-GW configuration file provided!\n");
    config_destroy(&cfg);
    AssertFatal (0, "No SP-GW configuration file provided!\n");
  }

  setting_sgw = config_lookup(&cfg, SGW_CONFIG_STRING_SGW_CONFIG);

  if(setting_sgw != NULL) {
    subsetting = config_setting_get_member (setting_sgw, SGW_CONFIG_STRING_NETWORK_INTERFACES_CONFIG);

    if(subsetting != NULL) {
      if(  (
             config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S1U_S12_S4_UP, (const char **)&sgw_interface_name_for_S1u_S12_S4_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S1U_S12_S4_UP,   (const char **)&sgw_ipv4_address_for_S1u_S12_S4_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S5_S8_UP,      (const char **)&sgw_interface_name_for_S5_S8_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S5_S8_UP,        (const char **)&sgw_ipv4_address_for_S5_S8_up)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S11,           (const char **)&sgw_interface_name_for_S11)
             && config_setting_lookup_string( subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S11,             (const char **)&sgw_ipv4_address_for_S11)
           )
        ) {
        config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up = strdup(sgw_interface_name_for_S1u_S12_S4_up);
        cidr = strdup(sgw_ipv4_address_for_S1u_S12_S4_up);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S1u_S12_S4_up, "BAD IP ADDRESS FORMAT FOR S1u_S12_S4 !\n" )
        config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S1u_S12_S4_up = atoi(mask);
        free(cidr);

        config_pP->sgw_config.ipv4.sgw_interface_name_for_S5_S8_up = strdup(sgw_interface_name_for_S5_S8_up);
        cidr = strdup(sgw_ipv4_address_for_S5_S8_up);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S5_S8_up, "BAD IP ADDRESS FORMAT FOR S5_S8 !\n" )
        config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S5_S8_up = atoi(mask);
        free(cidr);

        config_pP->sgw_config.ipv4.sgw_interface_name_for_S11 = strdup(sgw_interface_name_for_S11);
        cidr = strdup(sgw_ipv4_address_for_S11);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S11, "BAD IP ADDRESS FORMAT FOR S11 !\n" )
        config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S11 = atoi(mask);
        free(cidr);
      }
    }
  }

  setting_pgw = config_lookup(&cfg, PGW_CONFIG_STRING_PGW_CONFIG);

  if(setting_pgw != NULL) {
    subsetting = config_setting_get_member (setting_pgw, SGW_CONFIG_STRING_NETWORK_INTERFACES_CONFIG);

    if(subsetting != NULL) {
      if(  (
             config_setting_lookup_string(subsetting,
                                          PGW_CONFIG_STRING_PGW_INTERFACE_NAME_FOR_S5_S8,
                                          (const char **)&pgw_interface_name_for_S5_S8)
             && config_setting_lookup_string(subsetting,
                                             PGW_CONFIG_STRING_PGW_IPV4_ADDRESS_FOR_S5_S8,
                                             (const char **)&pgw_ipv4_address_for_S5_S8)
             && config_setting_lookup_string(subsetting,
                                             PGW_CONFIG_STRING_PGW_INTERFACE_NAME_FOR_SGI,
                                             (const char **)&pgw_interface_name_for_SGI)
             && config_setting_lookup_string(subsetting,
                                             PGW_CONFIG_STRING_PGW_IPV4_ADDR_FOR_SGI,
                                             (const char **)&pgw_ipv4_address_for_SGI)
           )
        ) {
        config_pP->pgw_config.ipv4.pgw_interface_name_for_S5_S8 = strdup(pgw_interface_name_for_S5_S8);
        cidr = strdup(pgw_ipv4_address_for_S5_S8);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->pgw_config.ipv4.pgw_ipv4_address_for_S5_S8, "BAD IP ADDRESS FORMAT FOR S5_S8 !\n" )
        config_pP->pgw_config.ipv4.pgw_ip_netmask_for_S5_S8 = atoi(mask);
        free(cidr);

        config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI = strdup(pgw_interface_name_for_SGI);
        cidr = strdup(pgw_ipv4_address_for_SGI);
        address = strtok(cidr, "/");
        mask    = strtok(NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO ( address, config_pP->pgw_config.ipv4.pgw_ipv4_address_for_SGI, "BAD IP ADDRESS FORMAT FOR SGI !\n" )
        config_pP->pgw_config.ipv4.pgw_ip_netmask_for_SGI = atoi(mask);
        free(cidr);
      }
    }

    subsetting = config_setting_get_member (setting_pgw, PGW_CONFIG_STRING_IP_ADDRESS_POOL);

    if(subsetting != NULL) {
      sub2setting = config_setting_get_member (subsetting, PGW_CONFIG_STRING_IPV4_ADDRESS_LIST);

      if(sub2setting != NULL) {
        num     = config_setting_length(sub2setting);

        for (i = 0; i < num; i++) {
          astring = config_setting_get_string_elem(sub2setting,i);

          if (astring != NULL) {
            trim(astring, strlen(astring)+1);

            if (inet_pton(AF_INET, astring, buf_in_addr) < 1) {
              // failure, test if there is a range specified in the string
              atoken = strtok(astring, PGW_CONFIG_STRING_IP_ADDRESS_RANGE_DELIMITERS);

              if (inet_pton(AF_INET, astring, buf_in_addr) == 1) {
                memcpy (&addr_start, buf_in_addr, sizeof(struct in_addr));
                // valid address
                atoken2 = strtok(NULL, PGW_CONFIG_STRING_IP_ADDRESS_RANGE_DELIMITERS);

                if (inet_pton(AF_INET, atoken2, buf_in_addr) == 1) {
                  memcpy (&addr_end, buf_in_addr, sizeof(struct in_addr));

                  // valid address
                  for (jh = ntohl(addr_start.s_addr); jh <= ntohl(addr_end.s_addr); jh++) {
                    DevAssert(PGW_MAX_ALLOCATED_PDN_ADDRESSES > config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses);
                    jn = htonl(jh);

                    if (IN_CLASSA(addr_start.s_addr)) {
                      if ((jh & 0xFF) && (jh & 0xFF) != 0xFF) {
                        config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = jn;
                      }
                    } else if (IN_CLASSB(addr_start.s_addr)) {
                      if ((jh & 0xFF) && (jh & 0xFF) != 0xFF) {
                        config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = jn;
                      }
                    } else if (IN_CLASSC(addr_start.s_addr)) {
                      if ((jh & 0xFF) && (jh & 0xFF) != 0xFF) {
                        config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = jn;
                      }
                    } else {
                      printf("ERROR ON ADDRESS CLASS %d.%d.%d.%d\n", NIPADDR(jn));
                    }
                  }
                }
              }
            } else {
              DevAssert(PGW_MAX_ALLOCATED_PDN_ADDRESSES > config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses);
              memcpy (&addr_start, buf_in_addr, sizeof(struct in_addr));
              config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = addr_start.s_addr;
            }
          }
        }
      }

      sub2setting = config_setting_get_member (subsetting, PGW_CONFIG_STRING_IPV6_ADDRESS_LIST);

      if(sub2setting != NULL) {
        num     = config_setting_length(sub2setting);

        for (i = 0; i < num; i++) {
          astring = config_setting_get_string_elem(sub2setting,i);

          if (astring != NULL) {
            trim(astring, strlen(astring)+1);

            if (inet_pton(AF_INET6, astring, buf_in6_addr) < 1) {
              // failure, test if there is a range specified in the string
              atoken = strtok(astring, PGW_CONFIG_STRING_IPV6_PREFIX_DELIMITER);

              if (inet_pton(AF_INET6, astring, buf_in6_addr) == 1) {
                atoken2 = strtok(NULL, PGW_CONFIG_STRING_IPV6_PREFIX_DELIMITER);
                prefix_mask = atoi(atoken2);
                // arbitrary values
                DevAssert((prefix_mask < 128) && (prefix_mask >= 64));

                memcpy (&addr6_start, buf_in6_addr, sizeof(struct in6_addr));
                memcpy (&addr6_mask,  buf_in6_addr, sizeof(struct in6_addr));
                sgw_ipv6_mask_in6_addr(&addr6_mask, prefix_mask);

                if (memcmp(&addr6_start, &addr6_mask, sizeof(struct in6_addr)) != 0) {
                  AssertFatal(0, "BAD IPV6 ADDR CONFIG/MASK PAIRING %s/%d\n", astring, prefix_mask);
                }

                counter64 = 0xFFFFFFFFFFFFFFFF >> prefix_mask; // address Prefix_mask/0..0 not valid

                do {
                  addr6_start.s6_addr32[3] = addr6_start.s6_addr32[3] + htonl(1);

                  if (addr6_start.s6_addr32[3] == 0) {
                    addr6_start.s6_addr32[2] = addr6_start.s6_addr32[2] + htonl(1);

                    if (addr6_start.s6_addr32[2] == 0) {
                      // should not happen since mask is no less than 64
                      addr6_start.s6_addr32[1] = addr6_start.s6_addr32[1] + htonl(1);

                      if (addr6_start.s6_addr32[1] == 0) {
                        addr6_start.s6_addr32[0] = addr6_start.s6_addr32[0] + htonl(1);
                      }
                    }
                  }

                  memcpy (&config_pP->pgw_config.pool_pdn_addresses.ipv6_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv6_addresses++],
                          &addr6_start,
                          sizeof(struct in6_addr));
                  counter64 = counter64 - 1;
                } while (counter64 > 0);
              }
            } else {
              DevAssert(PGW_MAX_ALLOCATED_PDN_ADDRESSES > config_pP->pgw_config.pool_pdn_addresses.num_ipv6_addresses);
              memcpy (&config_pP->pgw_config.pool_pdn_addresses.ipv6_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv6_addresses++],
                      buf_in6_addr,
                      sizeof(struct in6_addr));
            }
          }
        }
      }
    }
  }

  return 0;
}