/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*****************************************************************************
Source      esm_main.c

Version     0.1

Date        2012/12/04

Product     NAS stack

Subsystem   EPS Session Management

Author      Frederic Maurel

Description Defines the EPS Session Management procedure call manager,
        the main entry point for elementary ESM processing.

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

#include "esm_main.h"
#include "commonDef.h"
#include "nas_log.h"
#include "utils.h"

#include "emmData.h"
#include "esmData.h"
#include "esm_pt.h"
#include "esm_ebr.h"
#include "user_defs.h"

/****************************************************************************/
/****************  E X T E R N A L    D E F I N I T I O N S  ****************/
/****************************************************************************/

/****************************************************************************/
/*******************  L O C A L    D E F I N I T I O N S  *******************/
/****************************************************************************/

/****************************************************************************/
/******************  E X P O R T E D    F U N C T I O N S  ******************/
/****************************************************************************/

/****************************************************************************
 **                                                                        **
 ** Name:    esm_main_initialize()                                     **
 **                                                                        **
 ** Description: Initializes ESM internal data                             **
 **                                                                        **
 ** Inputs:  cb:        The user notification callback             **
 **      Others:    None                                       **
 **                                                                        **
 ** Outputs:     None                                                      **
 **      Return:    None                                       **
 **                                                                        **
 ***************************************************************************/
void esm_main_initialize(nas_user_t *user, esm_indication_callback_t cb)
{
  LOG_FUNC_IN;

  int i;

  esm_data_t *esm_data = calloc_or_fail(sizeof(esm_data_t));
  user->esm_data = esm_data;

  default_eps_bearer_context_data_t *default_eps_bearer_context = calloc(1, sizeof(default_eps_bearer_context_data_t));
  if ( default_eps_bearer_context == NULL ) {
    LOG_TRACE(ERROR, "ESM-MAIN  - Can't malloc default_eps_bearer_context");
    exit(EXIT_FAILURE);
  }
  default_eps_bearer_context->ebi = ESM_EBI_UNASSIGNED;
  user->default_eps_bearer_context_data = default_eps_bearer_context;
  /* Total number of active EPS bearer contexts */
  esm_data->n_ebrs = 0;
  /* List of active PDN connections */
  esm_data->n_pdns = 0;

  for (i = 0; i < ESM_DATA_PDN_MAX + 1; i++) {
    esm_data->pdn[i].pid = -1;
    esm_data->pdn[i].is_active = false;
    esm_data->pdn[i].data = NULL;
  }

  /* Emergency bearer services indicator */
  esm_data->emergency = false;

  /* Initialize the procedure transaction identity manager */

  user->esm_pt_data = esm_pt_initialize();

  /* Initialize the EPS bearer context manager */
  user->esm_ebr_data = esm_ebr_initialize();
  // FIXME only one callback for all user or many for many ?
  esm_ebr_register_callback(cb);
  LOG_FUNC_OUT;
}


/****************************************************************************
 **                                                                        **
 ** Name:        esm_main_cleanup()                                        **
 **                                                                        **
 ** Description: Performs the EPS Session Management clean up procedure    **
 **                                                                        **
 ** Inputs:      None                                                      **
 **                  Others:    None                                       **
 **                                                                        **
 ** Outputs:     None                                                      **
 **                  Return:    None                                       **
 **                  Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
void esm_main_cleanup(esm_data_t *esm_data)
{
  LOG_FUNC_IN;

  {
    int i;
    int pid;
    int bid;

    /* De-activate EPS bearers and clean up PDN connections */
    for (pid = 0; pid < ESM_DATA_PDN_MAX; pid++) {
      if (esm_data->pdn[pid].data) {
        esm_pdn_t *pdn = esm_data->pdn[pid].data;

        if (pdn->apn.length > 0) {
          free(pdn->apn.value);
        }

        /* Release EPS bearer contexts */
        for (bid = 0; bid < pdn->n_bearers; bid++) {
          if (pdn->bearer[bid]) {
            LOG_TRACE(WARNING, "ESM-MAIN  - Release EPS bearer "
                      "context (ebi=%d)", pdn->bearer[bid]->ebi);

            /* Delete the TFT */
            for (i = 0; i < pdn->bearer[bid]->tft.n_pkfs; i++) {
              if (pdn->bearer[bid]->tft.pkf[i]) {
                free(pdn->bearer[bid]->tft.pkf[i]);
              }
            }

            free(pdn->bearer[bid]);
          }
        }

        /* Release the PDN connection */
        free(esm_data->pdn[pid].data);
      }
    }
  }

  LOG_FUNC_OUT;
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_main_get_nb_pdn_max()                                 **
 **                                                                        **
 ** Description: Get the maximum number of PDN connections that may be in  **
 **      a defined state at the same time                          **
 **                                                                        **
 ** Inputs:  None                                                      **
 **                                                                        **
 ** Outputs:     None                                                      **
 **      Return:    The maximum number of PDN connections that **
 **             may be defined                             **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
int esm_main_get_nb_pdns_max(esm_data_t *esm_data)
{
  LOG_FUNC_IN;

  LOG_FUNC_RETURN (ESM_DATA_PDN_MAX);
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_main_get_nb_pdns()                                    **
 **                                                                        **
 ** Description: Get the number of active PDN connections                  **
 **                                                                        **
 ** Inputs:  None                                                      **
 **                                                                        **
 ** Outputs:     None                                                      **
 **      Return:    The number of active PDN connections       **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
int esm_main_get_nb_pdns(esm_data_t *esm_data)
{
  LOG_FUNC_IN;

  LOG_FUNC_RETURN (esm_data->n_pdns);
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_main_has_emergency()                                  **
 **                                                                        **
 ** Description: Check whether a PDN connection for emergency bearer ser-  **
 **      vices is established                                      **
 **                                                                        **
 ** Inputs:  None                                                      **
 **                                                                        **
 ** Outputs:     None                                                      **
 **      Return:    true if a PDN connection for emergency     **
 **             bearer services is established             **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
bool esm_main_has_emergency(esm_data_t *esm_data)
{
  LOG_FUNC_IN;

  LOG_FUNC_RETURN (esm_data->emergency);
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_main_get_pdn_status()                                 **
 **                                                                        **
 ** Description: Get the status of the specified PDN connection            **
 **                                                                        **
 ** Inputs:  cid:       PDN connection identifier                  **
 **                                                                        **
 ** Outputs:     state:     true if the current state of the PDN con-  **
 **             nection is ACTIVE; false otherwise.        **
 **      Return:    true if the specified PDN connection has a **
 **             PDN context defined; false if no any PDN   **
 **             context has been defined for the specified **
 **             connection.                                **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
bool esm_main_get_pdn_status(nas_user_t *user, int cid, bool *state)
{
  LOG_FUNC_IN;

  unsigned int pid = cid - 1;
  esm_data_t *esm_data = user->esm_data;
  esm_ebr_data_t *esm_ebr_data = user-> esm_ebr_data;

  if (pid >= ESM_DATA_PDN_MAX) {
    return (false);
  } else if (pid != esm_data->pdn[pid].pid) {
    LOG_TRACE(WARNING, "ESM-MAIN  - PDN connection %d is not defined", cid);
    return (false);
  } else if (esm_data->pdn[pid].data == NULL) {
    LOG_TRACE(ERROR, "ESM-MAIN  - PDN connection %d has not been allocated",
              cid);
    return (false);
  }

  if (esm_data->pdn[pid].data->bearer[0] != NULL) {
    /* The status of a PDN connection is the status of the default EPS bearer
     * that has been assigned to this PDN connection at activation time */
    int ebi = esm_data->pdn[pid].data->bearer[0]->ebi;
    *state = (esm_ebr_get_status(esm_ebr_data, ebi) == ESM_EBR_ACTIVE);
  }

  /* The PDN connection has not been activated yet */
  LOG_FUNC_RETURN (true);
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_main_get_pdn()                                        **
 **                                                                        **
 ** Description: Get parameters defined for the specified PDN connection   **
 **                                                                        **
 ** Inputs:  cid:       PDN connection identifier                  **
 **                                                                        **
 ** Outputs:     type:      PDN connection type (IPv4, IPv6, IPv4v6)   **
 **      apn:       Access Point logical Name in used          **
 **      is_emergency:  Emergency bearer services indicator        **
 **      is_active: Active PDN connection indicator            **
 **      Return:    RETURNok, RETURNerror                      **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
int esm_main_get_pdn(esm_data_t *esm_data, int cid, int *type, const char **apn,
                     bool *is_emergency, bool *is_active)
{
  LOG_FUNC_IN;

  unsigned int pid = cid - 1;

  if (pid >= ESM_DATA_PDN_MAX) {
    return (RETURNerror);
  } else if (pid != esm_data->pdn[pid].pid) {
    LOG_TRACE(WARNING, "ESM-MAIN  - PDN connection %d is not defined", cid);
    return (RETURNerror);
  } else if (esm_data->pdn[pid].data == NULL) {
    LOG_TRACE(ERROR, "ESM-MAIN  - PDN connection %d has not been allocated",
              cid);
    return (RETURNerror);
  }

  /* Get the PDN type */
  *type = esm_data->pdn[pid].data->type;

  /* Get the Access Point Name */
  if (esm_data->pdn[pid].data->apn.length > 0) {
    *apn = (char *)(esm_data->pdn[pid].data->apn.value);
  } else {
    *apn = NULL;
  }

  /* Get the emergency bearer services indicator */
  *is_emergency = esm_data->pdn[pid].data->is_emergency;
  /* Get the active PDN connection indicator */
  *is_active = esm_data->pdn[pid].is_active;

  LOG_FUNC_RETURN (RETURNok);
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_main_get_pdn_addr()                                   **
 **                                                                        **
 ** Description: Get IP address(es) assigned to the specified PDN connec-  **
 **      tion                                                      **
 **                                                                        **
 ** Inputs:  cid:       PDN connection identifier                  **
 **                                                                        **
 ** Outputs:     ipv4adddr: IPv4 address                               **
 **      ipv6adddr: IPv6 address                               **
 **      Return:    RETURNok, RETURNerror                      **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
int esm_main_get_pdn_addr(esm_data_t *esm_data, int cid, const char **ipv4addr, const char **ipv6addr)
{
  LOG_FUNC_IN;

  unsigned int pid = cid - 1;

  if (pid >= ESM_DATA_PDN_MAX) {
    return (RETURNerror);
  } else if (pid != esm_data->pdn[pid].pid) {
    LOG_TRACE(WARNING, "ESM-MAIN  - PDN connection %d is not defined", cid);
    return (RETURNerror);
  } else if (esm_data->pdn[pid].data == NULL) {
    LOG_TRACE(ERROR, "ESM-MAIN  - PDN connection %d has not been allocated",
              cid);
    return (RETURNerror);
  } else if (!esm_data->pdn[pid].is_active) {
    /* No any IP address has been assigned to this PDN connection */
    return (RETURNok);
  }

  if (esm_data->pdn[pid].data->type == NET_PDN_TYPE_IPV4) {
    /* Get IPv4 address */
    *ipv4addr = esm_data->pdn[pid].data->ip_addr;
  } else if (esm_data->pdn[pid].data->type == NET_PDN_TYPE_IPV6) {
    /* Get IPv6 address */
    *ipv6addr = esm_data->pdn[pid].data->ip_addr;
  } else {
    /* IPv4v6 dual-stack terminal */
    *ipv4addr = esm_data->pdn[pid].data->ip_addr;
    *ipv6addr = esm_data->pdn[pid].data->ip_addr+ESM_DATA_IPV4_ADDRESS_SIZE;
  }

  LOG_FUNC_RETURN (RETURNok);
}

