/*
 * Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
 *
 * This file is part of Open5GS.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef MME_CONTEXT_H
#define MME_CONTEXT_H

#include "ogs-crypt.h"

#include "ogs-s1ap.h"
#include "ogs-diameter-s6a.h"
#include "ogs-gtp.h"
#include "ogs-nas-eps.h"
#include "ogs-app.h"
#include "ogs-sctp.h"
#include "metrics.h"

/* S1AP */
#include "S1AP_Cause.h"

#ifdef __cplusplus
extern "C" {
#endif

#define GRP_PER_MME                 256    /* According to spec it is 65535 */
#define CODE_PER_MME                256    /* According to spec it is 256 */

extern int __mme_log_domain;
extern int __emm_log_domain;
extern int __esm_log_domain;

#undef OGS_LOG_DOMAIN
#define OGS_LOG_DOMAIN __mme_log_domain

typedef struct mme_sgsn_s mme_sgsn_t;
typedef struct mme_sgw_s mme_sgw_t;
typedef struct mme_pgw_s mme_pgw_t;
typedef struct mme_vlr_s mme_vlr_t;
typedef struct mme_csmap_s mme_csmap_t;

typedef struct enb_ue_s enb_ue_t;
typedef struct sgw_ue_s sgw_ue_t;
typedef struct mme_ue_s mme_ue_t;
typedef struct mme_sess_s mme_sess_t;
typedef struct mme_bearer_s mme_bearer_t;

typedef struct ogs_diam_config_s ogs_diam_config_t;

typedef uint32_t mme_m_tmsi_t;
typedef uint32_t mme_p_tmsi_t;

typedef struct served_gummei_s {
    int             num_of_plmn_id;
    ogs_plmn_id_t   plmn_id[OGS_MAX_NUM_OF_PLMN_PER_MME];

    int             num_of_mme_gid;
    uint16_t        mme_gid[GRP_PER_MME];
    int             num_of_mme_code;
    uint8_t         mme_code[CODE_PER_MME];
} served_gummei_t;

typedef struct mme_context_s {
    const char          *diam_conf_path;  /* MME Diameter conf path */
    ogs_diam_config_t   *diam_config;     /* MME Diameter config */

    uint16_t        s1ap_port;      /* Default S1AP Port */
    uint16_t        sgsap_port;     /* Default SGsAP Port */

    ogs_list_t      s1ap_list;      /* MME S1AP IPv4 Server List */
    ogs_list_t      s1ap_list6;     /* MME S1AP IPv6 Server List */

    ogs_list_t      sgw_list;       /* SGW GTPv2C Client List */
    mme_sgw_t       *sgw;           /* Iterator for SGW round-robin */

    ogs_list_t      sgsn_list;       /* SGW GTPv1C Client List */

    ogs_list_t      pgw_list;       /* PGW GTPC Client List */
    ogs_sockaddr_t  *pgw_addr;      /* First IPv4 Address Selected */
    ogs_sockaddr_t  *pgw_addr6;     /* First IPv6 Address Selected */

    ogs_list_t      enb_list;       /* ENB S1AP Client List */

    ogs_list_t      vlr_list;       /* VLR SGsAP Client List */
    ogs_list_t      csmap_list;     /* TAI-LAI Map List */

    /* Served GUMME */
    int             num_of_served_gummei;
    served_gummei_t served_gummei[OGS_MAX_NUM_OF_SERVED_GUMMEI];

    /* Served TAI */
    int             num_of_served_tai;
    struct {
        ogs_eps_tai0_list_t list0;
        ogs_eps_tai1_list_t list1;
        ogs_eps_tai2_list_t list2;
    } served_tai[OGS_MAX_NUM_OF_SUPPORTED_TA];

    /* Access Control */
    int             default_reject_cause;
    int             num_of_access_control;
    struct {
        int reject_cause;
        ogs_plmn_id_t plmn_id;
    } access_control[OGS_MAX_NUM_OF_PLMN_PER_MME];

    /* defined in 'nas_ies.h'
     * #define NAS_SECURITY_ALGORITHMS_EIA0        0
     * #define NAS_SECURITY_ALGORITHMS_128_EEA1    1
     * #define NAS_SECURITY_ALGORITHMS_128_EEA2    2
     * #define NAS_SECURITY_ALGORITHMS_128_EEA3    3 */
    int             num_of_ciphering_order;
    uint8_t         ciphering_order[OGS_MAX_NUM_OF_ALGORITHM];
    /* defined in 'nas_ies.h'
     * #define NAS_SECURITY_ALGORITHMS_EIA0        0
     * #define NAS_SECURITY_ALGORITHMS_128_EIA1    1
     * #define NAS_SECURITY_ALGORITHMS_128_EIA1    2
     * #define NAS_SECURITY_ALGORITHMS_128_EIA3    3 */
    int             num_of_integrity_order;
    uint8_t         integrity_order[OGS_MAX_NUM_OF_ALGORITHM];

    /* Network Name */
    ogs_nas_network_name_t short_name; /* Network short name */
    ogs_nas_network_name_t full_name; /* Network Full Name */

    /* MME Name */
    const char *mme_name;

    /* S1SetupResponse */
    uint8_t         relative_capacity;

    /* Generator for unique identification */
    uint32_t        mme_ue_s1ap_id;         /* mme_ue_s1ap_id generator */

#define MME_UE_CHECK(level, __mME) \
    do { \
        sgw_ue_t *__sGW = (((mme_ue_t *)__mME)->sgw_ue); \
        mme_sess_t *__sESS = NULL; \
        mme_bearer_t *__bEARER = NULL; \
        \
        ogs_log_message(level, 0, "MME-UE Context Memory[%p:%p]", \
                (__mME), mme_ue_cycle((__mME))); \
        ogs_log_message(level, 0, "SGW-UE Context Memory[%p:%p]", \
                (__sGW), sgw_ue_cycle((__sGW))); \
        ogs_log_message(level, 0, \
                "IMSI [%s] NAS-EPS Type[%d]", \
                (__mME) ? ((mme_ue_t *)__mME)->imsi_bcd : "No MME_UE", \
                (__mME) ? ((mme_ue_t *)__mME)->nas_eps.type : 0); \
        ogs_log_message(level, 0, \
                "MME_S11_TEID[%d] SGW_S11_TEID[%d]", \
                (__mME) ? ((mme_ue_t *)__mME)->mme_s11_teid : 0, \
                (__sGW) ? (__sGW)->sgw_s11_teid : 0); \
        if (!mme_ue_cycle(__mME)) { \
            ogs_log_message(level, 0, \
                    "MME-UE Context has already been removed"); \
            break; \
        } \
        ogs_list_for_each(&((mme_ue_t *)__mME)->sess_list, (__sESS)) { \
            ogs_log_message(level, 0, "SESS(%p) [%s:%d]", (__sESS), \
                    (__sESS)->session ? (__sESS)->session->name : "Unknown", \
                    (__sESS)->pti); \
            ogs_list_for_each(&(__sESS)->bearer_list, (__bEARER)) { \
                ogs_log_message(level, 0, \
                        "BEARER(%p) [%d] ENB_S1U_TEID[%d] SGW_S1U_TEID[%d]", \
                        (__bEARER), (__bEARER)->ebi, \
                        (__bEARER)->enb_s1u_teid, (__bEARER)->sgw_s1u_teid); \
                ogs_assert((__bEARER)->sess == (__sESS)); \
                ogs_assert((__bEARER)->mme_ue == (__mME)); \
            } \
            ogs_assert((__sESS)->mme_ue == (__mME)); \
        } \
    } while(0)

    ogs_list_t      mme_ue_list;

    ogs_hash_t *enb_addr_hash;  /* hash table for ENB Address */
    ogs_hash_t *enb_id_hash;    /* hash table for ENB-ID */
    ogs_hash_t *imsi_ue_hash;   /* hash table (IMSI : MME_UE) */
    ogs_hash_t *guti_ue_hash;   /* hash table (GUTI : MME_UE) */

    ogs_hash_t *mme_s11_teid_hash;  /* hash table (MME-S11-TEID : MME_UE) */
    ogs_hash_t *mme_gn_teid_hash;  /* hash table (MME-GN-TEID : MME_UE) */

    struct {
        struct {
            ogs_time_t value;       /* Timer Value(Seconds) */
        } t3402, t3412, t3423;
    } time;
} mme_context_t;

typedef struct mme_sgsn_route_s {
    ogs_list_t    list;       /* listed in mme_sgsn_t->route_list */
    ogs_nas_rai_t rai;
    uint16_t cell_id;
} mme_sgsn_route_t;

typedef struct mme_sgsn_s {
    ogs_gtp_node_t  gnode;
    ogs_list_t      route_list; /* list of mme_sgsn_route_t */
    bool            default_route; /* use this SGSN as default route */
} mme_sgsn_t;

typedef struct mme_sgw_s {
    ogs_gtp_node_t  gnode;

    uint16_t        tac[OGS_MAX_NUM_OF_TAI];
    int             num_of_tac;
    uint32_t        e_cell_id[OGS_MAX_NUM_OF_CELL_ID];
    int             num_of_e_cell_id;

    ogs_list_t      sgw_ue_list;
} mme_sgw_t;

typedef struct mme_pgw_s {
    ogs_lnode_t     lnode;

    ogs_sockaddr_t  *sa_list;

    const char      *apn[OGS_MAX_NUM_OF_APN];
    uint8_t         num_of_apn;
    uint16_t        tac[OGS_MAX_NUM_OF_TAI];
    uint8_t         num_of_tac;
    uint32_t        e_cell_id[OGS_MAX_NUM_OF_CELL_ID];
    uint8_t         num_of_e_cell_id;
} mme_pgw_t;

#define MME_SGSAP_IS_CONNECTED(__mME) \
    ((__mME) && ((__mME)->csmap) && ((__mME)->csmap->vlr) && \
     (OGS_FSM_CHECK(&(__mME)->csmap->vlr->sm, sgsap_state_connected)))
#define MME_P_TMSI_IS_AVAILABLE(__mME) \
    (MME_SGSAP_IS_CONNECTED(__mME) && (__mME)->p_tmsi)

typedef struct mme_vlr_s {
    ogs_lnode_t     lnode;

    ogs_fsm_t       sm;          /* A state machine */

    ogs_timer_t     *t_conn;     /* client timer to connect to server */

    int             max_num_of_ostreams;/* SCTP Max num of outbound streams */
    uint16_t        ostream_id;     /* vlr_ostream_id generator */

    ogs_sockaddr_t  *sa_list;   /* VLR SGsAP Socket Address List */

    ogs_sock_t      *sock;      /* VLR SGsAP Socket */
    ogs_sockaddr_t  *addr;      /* VLR SGsAP Connected Socket Address */
    ogs_sockopt_t   *option;    /* VLR SGsAP Socket Option */
    ogs_poll_t      *poll;      /* VLR SGsAP Poll */
} mme_vlr_t;

typedef struct mme_csmap_s {
    ogs_lnode_t     lnode;

    ogs_nas_eps_tai_t tai;
    ogs_nas_lai_t   lai;

    mme_vlr_t       *vlr;
} mme_csmap_t;

typedef struct mme_enb_s {
    ogs_lnode_t     lnode;

    ogs_fsm_t       sm;         /* A state machine */

    uint32_t        enb_id;     /* eNB_ID received from eNB */
    ogs_plmn_id_t   plmn_id;    /* eNB PLMN-ID received from eNB */
    ogs_sctp_sock_t sctp;       /* SCTP socket */

    struct {
        bool s1_setup_success;  /* eNB S1AP Setup complete successfuly */
    } state;

    int             max_num_of_ostreams;/* SCTP Max num of outbound streams */
    uint16_t        ostream_id;         /* enb_ostream_id generator */

    int             num_of_supported_ta_list;
    ogs_eps_tai_t   supported_ta_list[OGS_MAX_NUM_OF_SUPPORTED_TA];

    ogs_pkbuf_t     *s1_reset_ack; /* Reset message */

    ogs_list_t      enb_ue_list;

} mme_enb_t;

struct enb_ue_s {
    ogs_lnode_t     lnode;
    uint32_t        index;

    /* UE identity */
#define INVALID_UE_S1AP_ID      0xffffffff /* Initial value of enb_ue_s1ap_id */
    uint32_t        enb_ue_s1ap_id; /* eNB-UE-S1AP-ID received from eNB */
    uint32_t        mme_ue_s1ap_id; /* MME-UE-S1AP-ID received from MME */

    uint16_t        enb_ostream_id; /* SCTP output stream id for eNB */

    /* Handover Info */
    S1AP_HandoverType_t handover_type;
    enb_ue_t        *source_ue;
    enb_ue_t        *target_ue;

    /* Use mme_ue->tai, mme_ue->e_cgi.
     * Do not access enb_ue->saved.tai enb_ue->saved.e_cgi.
     *
     * Save TAI and ECGI. And then, this will copy 'mme_ue_t' context later */
    struct {
        ogs_eps_tai_t   tai;
        ogs_e_cgi_t     e_cgi;
    } saved;

    /* S1 Holding timer for removing this context */
    ogs_timer_t     *t_s1_holding;

    /* Store by UE Context Release Command
     * Retrieve by UE Context Release Complete */
#define S1AP_UE_CTX_REL_INVALID_ACTION                      0
#define S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE                   1
#define S1AP_UE_CTX_REL_S1_REMOVE_AND_UNLINK                2
#define S1AP_UE_CTX_REL_UE_CONTEXT_REMOVE                   3
#define S1AP_UE_CTX_REL_S1_HANDOVER_COMPLETE                4
#define S1AP_UE_CTX_REL_S1_HANDOVER_CANCEL                  5
#define S1AP_UE_CTX_REL_S1_HANDOVER_FAILURE                 6
#define S1AP_UE_CTX_REL_S1_PAGING                           7
    uint8_t         ue_ctx_rel_action;

    bool            part_of_s1_reset_requested;

    /* Related Context */
    mme_enb_t       *enb;
    mme_ue_t        *mme_ue;
};

struct sgw_ue_s {
    ogs_lnode_t     lnode;

    sgw_ue_t        *source_ue;
    sgw_ue_t        *target_ue;

    /* UE identity */
    uint32_t        sgw_s11_teid;   /* SGW-S11-TEID is received from SGW */

    /* S11 Holding timer for removing this context */
    ogs_timer_t     *t_s11_holding;

    /* Related Context */
    union {
        mme_sgw_t       *sgw;
        ogs_gtp_node_t  *gnode;
    };
    mme_ue_t        *mme_ue;
};

struct mme_ue_s {
    ogs_lnode_t     lnode;
    ogs_fsm_t       sm;     /* A state machine */

    struct {
#define MME_EPS_TYPE_ATTACH_REQUEST                 1
#define MME_EPS_TYPE_TAU_REQUEST                    2
#define MME_EPS_TYPE_SERVICE_REQUEST                3
#define MME_EPS_TYPE_EXTENDED_SERVICE_REQUEST       4
#define MME_EPS_TYPE_DETACH_REQUEST_FROM_UE         5
#define MME_EPS_TYPE_DETACH_REQUEST_TO_UE           6
        uint8_t     type;
        uint8_t     ksi;
        ogs_nas_eps_attach_type_t attach;
        ogs_nas_eps_update_type_t update;
        ogs_nas_service_type_t service;
        ogs_nas_detach_type_t detach;
    } nas_eps;

    /* 1. MME initiated detach request to the UE.
     *    (nas_eps.type = MME_EPS_TYPE_DETACH_REQUEST_TO_UE)
     * 2. If UE is IDLE, Paging sent to the UE
     * 3. If UE is wake-up, UE will send Server Request.
     *    (nas_eps.type = MME_EPS_TYPE_SERVICE_REQUEST)
     *
     * So, we will lose the MME_EPS_TYPE_DETACH_REQUEST_TO_UE.
     *
     * We need more variable(detach_type)
     * to keep Detach-Type whether UE-initiated or MME-initiaed.  */
#define MME_DETACH_TYPE_REQUEST_FROM_UE             1
#define MME_DETACH_TYPE_MME_EXPLICIT                2
#define MME_DETACH_TYPE_HSS_EXPLICIT                3
#define MME_DETACH_TYPE_MME_IMPLICIT                4
#define MME_DETACH_TYPE_HSS_IMPLICIT                5
    uint8_t     detach_type;

    /* UE identity */
#define MME_UE_HAVE_IMSI(__mME) \
    ((__mME) && ((__mME)->imsi_len))
    uint8_t         imsi[OGS_MAX_IMSI_LEN];
    int             imsi_len;
    char            imsi_bcd[OGS_MAX_IMSI_BCD_LEN+1];
    ogs_nas_mobile_identity_imsi_t nas_mobile_identity_imsi;

    uint8_t         imeisv[OGS_MAX_IMEISV_LEN];
    int             imeisv_len;
    uint8_t         masked_imeisv[OGS_MAX_IMEISV_LEN];
    int             masked_imeisv_len;
    char            imeisv_bcd[OGS_MAX_IMEISV_BCD_LEN+1];
    ogs_nas_mobile_identity_imeisv_t nas_mobile_identity_imeisv;

    uint8_t         msisdn[OGS_MAX_MSISDN_LEN];
    int             msisdn_len;
    char            msisdn_bcd[OGS_MAX_MSISDN_BCD_LEN+1];

    uint8_t         a_msisdn[OGS_MAX_MSISDN_LEN];
    int             a_msisdn_len;
    char            a_msisdn_bcd[OGS_MAX_MSISDN_BCD_LEN+1];

    mme_p_tmsi_t    p_tmsi;
    struct {
        ogs_pool_id_t   *mme_gn_teid_node; /* A node of MME-Gn-TEID */
        uint32_t        mme_gn_teid;   /* MME-Gn-TEID is derived from NODE */
        uint32_t        sgsn_gn_teid;
        ogs_ip_t        sgsn_gn_ip;
        ogs_ip_t        sgsn_gn_ip_alt;
        /* Unnamed timer in 3GPP TS 23.401 D.3.5 step 2), see also 3GPP TS 23.060 6.9.1.2.2 */
        ogs_timer_t     *t_gn_holding;
    } gn;

    struct {
        mme_m_tmsi_t *m_tmsi;
        ogs_nas_eps_guti_t guti;
    } current, next;

    ogs_pool_id_t   *mme_s11_teid_node; /* A node of MME-S11-TEID */
    uint32_t        mme_s11_teid;   /* MME-S11-TEID is derived from NODE */

    uint16_t        vlr_ostream_id; /* SCTP output stream id for VLR */

    /* UE Info */
    uint16_t        enb_ostream_id;
    ogs_eps_tai_t   tai;
    ogs_e_cgi_t     e_cgi;
    ogs_time_t      ue_location_timestamp;
    ogs_plmn_id_t   last_visited_plmn_id;

#define SECURITY_CONTEXT_IS_VALID(__mME) \
    ((__mME) && \
    ((__mME)->security_context_available == 1) && \
     ((__mME)->mac_failed == 0) && \
     ((__mME)->nas_eps.ksi != OGS_NAS_KSI_NO_KEY_IS_AVAILABLE))
#define CLEAR_SECURITY_CONTEXT(__mME) \
    do { \
        ogs_assert((__mME)); \
        (__mME)->security_context_available = 0; \
        (__mME)->mac_failed = 0; \
        (__mME)->nas_eps.ksi = 0; \
    } while(0)
    int             security_context_available;
    int             mac_failed;

    /* Security Context */
    ogs_nas_ue_network_capability_t ue_network_capability;
    ogs_nas_ms_network_capability_t ms_network_capability;
    ogs_nas_ue_additional_security_capability_t
        ue_additional_security_capability;
    uint8_t         xres[OGS_MAX_RES_LEN];
    uint8_t         xres_len;
    uint8_t         kasme[OGS_SHA256_DIGEST_SIZE];
    uint8_t         rand[OGS_RAND_LEN];
    uint8_t         autn[OGS_AUTN_LEN];
    uint8_t         knas_int[OGS_SHA256_DIGEST_SIZE/2];
    uint8_t         knas_enc[OGS_SHA256_DIGEST_SIZE/2];
    uint32_t        dl_count;
    union {
        struct {
        ED3(uint8_t spare;,
            uint16_t overflow;,
            uint8_t sqn;)
        } __attribute__ ((packed));
        uint32_t i32;
    } ul_count;
    uint8_t         kenb[OGS_SHA256_DIGEST_SIZE];
    uint8_t         hash_mme[OGS_HASH_MME_LEN];
    uint32_t        nonceue, noncemme;
    uint8_t gprs_ciphering_key_sequence_number;

    struct {
    ED2(uint8_t nhcc_spare:5;,
        uint8_t nhcc:3;) /* Next Hop Channing Counter */
    };
    uint8_t         nh[OGS_SHA256_DIGEST_SIZE]; /* NH Security Key */

    /* defined in 'nas_ies.h'
     * #define NAS_SECURITY_ALGORITHMS_EIA0        0
     * #define NAS_SECURITY_ALGORITHMS_128_EEA1    1
     * #define NAS_SECURITY_ALGORITHMS_128_EEA2    2
     * #define NAS_SECURITY_ALGORITHMS_128_EEA3    3 */
    uint8_t         selected_enc_algorithm;
    /* defined in 'nas_ies.h'
     * #define NAS_SECURITY_ALGORITHMS_EIA0        0
     * #define NAS_SECURITY_ALGORITHMS_128_EIA1    1
     * #define NAS_SECURITY_ALGORITHMS_128_EIA1    2
     * #define NAS_SECURITY_ALGORITHMS_128_EIA3    3 */
    uint8_t         selected_int_algorithm;

    /* HSS Info */
    ogs_bitrate_t   ambr; /* UE-AMBR */
    uint32_t        network_access_mode; /* Permitted EPS Attach Type */
    uint8_t         charging_characteristics[OGS_CHRGCHARS_LEN]; /* Subscription Level Charging Characteristics */
    bool            charging_characteristics_presence;

    uint32_t        context_identifier; /* default APN */

    int num_of_session;
    ogs_session_t session[OGS_MAX_NUM_OF_SESS];

    /* ESM Info */
    ogs_list_t      sess_list;

#define MIN_EPS_BEARER_ID           5
#define MAX_EPS_BEARER_ID           15

#define CLEAR_EPS_BEARER_ID(__mME) \
    do { \
        ogs_assert((__mME)); \
        mme_ebi_pool_clear(__mME); \
    } while(0)
    OGS_POOL(ebi_pool, uint8_t);

    /* Paging Info */
#define ECM_CONNECTED(__mME) \
    ((__mME) && ((__mME)->enb_ue != NULL) && enb_ue_cycle((__mME)->enb_ue))
#define ECM_IDLE(__mME) \
    ((__mME) && \
     (((__mME)->enb_ue == NULL) || (enb_ue_cycle((__mME)->enb_ue) == NULL)))
    enb_ue_t        *enb_ue;    /* S1 UE context */

#define HOLDING_S1_CONTEXT(__mME) \
    do { \
        enb_ue_deassociate((__mME)->enb_ue); \
        \
        (__mME)->enb_ue_holding = enb_ue_cycle((__mME)->enb_ue); \
        if ((__mME)->enb_ue_holding) { \
            ogs_warn("[%s] Holding S1 Context", (__mME)->imsi_bcd); \
            ogs_warn("[%s]    ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]", \
                    (__mME)->imsi_bcd, (__mME)->enb_ue_holding->enb_ue_s1ap_id, \
                    (__mME)->enb_ue_holding->mme_ue_s1ap_id); \
            \
            (__mME)->enb_ue_holding->ue_ctx_rel_action = \
                S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE; \
            ogs_timer_start((__mME)->enb_ue_holding->t_s1_holding, \
                    mme_timer_cfg(MME_TIMER_S1_HOLDING)->duration); \
        } else \
            ogs_error("[%s] S1 Context has already been removed", \
                    (__mME)->imsi_bcd); \
    } while(0)
#define CLEAR_S1_CONTEXT(__mME) \
    do { \
        if (enb_ue_cycle((__mME)->enb_ue_holding)) { \
            int r; \
            ogs_warn("[%s] Clear S1 Context", (__mME)->imsi_bcd); \
            ogs_warn("[%s]    ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]", \
                    (__mME)->imsi_bcd, (__mME)->enb_ue_holding->enb_ue_s1ap_id, \
                    (__mME)->enb_ue_holding->mme_ue_s1ap_id); \
            \
            r = s1ap_send_ue_context_release_command( \
                    (__mME)->enb_ue_holding, \
                    S1AP_Cause_PR_nas, S1AP_CauseNas_normal_release, \
                    S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0); \
            ogs_expect(r == OGS_OK); \
            ogs_assert(r != OGS_ERROR); \
        } \
        (__mME)->enb_ue_holding = NULL; \
    } while(0)
    enb_ue_t        *enb_ue_holding;

    struct {
#define MME_CLEAR_PAGING_INFO(__mME) \
    do { \
        ogs_assert(__mME); \
        ogs_debug("[%s] Clear Paging Info", (__mME)->imsi_bcd); \
        (__mME)->paging.type = 0; \
        (__mME)->paging.failed = false; \
    } while(0)

#define MME_STORE_PAGING_INFO(__mME, __tYPE, __dATA) \
    do { \
        ogs_assert(__mME); \
        ogs_assert(__tYPE); \
        ogs_debug("[%s] Store Paging Info", mme_ue->imsi_bcd); \
        (__mME)->paging.type = __tYPE; \
        (__mME)->paging.data = __dATA; \
    } while(0)

#define MME_PAGING_ONGOING(__mME) ((__mME) && ((__mME)->paging.type))

#define MME_PAGING_TYPE_DOWNLINK_DATA_NOTIFICATION 1
#define MME_PAGING_TYPE_CREATE_BEARER 2
#define MME_PAGING_TYPE_UPDATE_BEARER 3
#define MME_PAGING_TYPE_DELETE_BEARER 4
#define MME_PAGING_TYPE_CS_CALL_SERVICE 5
#define MME_PAGING_TYPE_SMS_SERVICE 6
#define MME_PAGING_TYPE_DETACH_TO_UE 7
        int type;
        void *data;
        bool failed;
    } paging;

    /* SGW UE context */
    sgw_ue_t        *sgw_ue;

    /* Save PDN Connectivity Request */
    ogs_nas_esm_message_container_t pdn_connectivity_request;

#define CLEAR_MME_UE_ALL_TIMERS(__mME) \
    do { \
        mme_sess_t *sess = NULL; \
        mme_bearer_t *bearer = NULL; \
        \
        CLEAR_MME_UE_TIMER((__mME)->t3413); \
        CLEAR_MME_UE_TIMER((__mME)->t3422); \
        CLEAR_MME_UE_TIMER((__mME)->t3450); \
        CLEAR_MME_UE_TIMER((__mME)->t3460); \
        CLEAR_MME_UE_TIMER((__mME)->t3470); \
        CLEAR_MME_UE_TIMER((__mME)->t_mobile_reachable); \
        CLEAR_MME_UE_TIMER((__mME)->t_implicit_detach); \
        \
        ogs_list_for_each(&mme_ue->sess_list, sess) { \
            ogs_list_for_each(&sess->bearer_list, bearer) { \
                CLEAR_BEARER_ALL_TIMERS(bearer); \
            } \
        } \
    } while(0);
#define CLEAR_MME_UE_TIMER(__mME_UE_TIMER) \
    do { \
        ogs_timer_stop((__mME_UE_TIMER).timer); \
        if ((__mME_UE_TIMER).pkbuf) { \
            ogs_pkbuf_free((__mME_UE_TIMER).pkbuf); \
            (__mME_UE_TIMER).pkbuf = NULL; \
        } \
        (__mME_UE_TIMER).retry_count = 0; \
    } while(0);
    struct {
        ogs_pkbuf_t     *pkbuf;
        ogs_timer_t     *timer;
        uint32_t        retry_count;;
    } t3413, t3422, t3450, t3460, t3470, t_mobile_reachable,
        t_implicit_detach;

#define CLEAR_SERVICE_INDICATOR(__mME) \
    do { \
        ogs_assert((__mME)); \
        (__mME)->service_indicator = 0; \
    } while(0);

#define CS_CALL_SERVICE_INDICATOR(__mME) \
    (MME_P_TMSI_IS_AVAILABLE(__mME) && \
     ((__mME)->service_indicator) == SGSAP_CS_CALL_SERVICE_INDICATOR)
#define SMS_SERVICE_INDICATOR(__mME) \
    (MME_P_TMSI_IS_AVAILABLE(__mME) && \
     ((__mME)->service_indicator) == SGSAP_SMS_SERVICE_INDICATOR)
    uint8_t         service_indicator;

    /* UE Radio Capability */
    OCTET_STRING_t  ueRadioCapability;

    /* S1AP Transparent Container */
    OCTET_STRING_t  container;

    /* GTP Request/Response Counter */
#define GTP_COUNTER_CLEAR(__mME, __tYPE) \
        do { \
            ogs_assert((__mME)); \
            ((__mME)->gtp_counter[__tYPE].request) = 0; \
            ((__mME)->gtp_counter[__tYPE].response) = 0; \
        } while(0);
#define GTP_COUNTER_INCREMENT(__mME, __tYPE) \
        do { \
            ogs_assert((__mME)); \
            ((__mME)->gtp_counter[__tYPE].request)++; \
        } while(0);

#define GTP_COUNTER_CHECK(__mME, __tYPE, __eXPR) \
        do { \
            ogs_assert((__mME)); \
            if ((__mME)->gtp_counter[__tYPE].request == 0) break; \
            ((__mME)->gtp_counter[__tYPE].response)++; \
            if (((__mME)->gtp_counter[__tYPE].request) == \
                ((__mME)->gtp_counter[__tYPE].response)) \
            { \
                GTP_COUNTER_CLEAR(__mME, __tYPE) \
                __eXPR \
            } \
        } while(0);

#define MAX_NUM_OF_GTP_COUNTER                                  16

#define GTP_COUNTER_CREATE_SESSION_BY_PATH_SWITCH               1
#define GTP_COUNTER_DELETE_SESSION_BY_PATH_SWITCH               2
    struct {
        uint8_t request;
        uint8_t response;
    } gtp_counter[MAX_NUM_OF_GTP_COUNTER];

    ogs_list_t      bearer_to_modify_list;

    mme_csmap_t     *csmap;
};

#define SESSION_CONTEXT_IS_AVAILABLE(__mME) \
     ((__mME) && ((__mME)->sgw_ue) && (((__mME)->sgw_ue)->sgw_s11_teid))

#define CLEAR_SESSION_CONTEXT(__mME) \
    do { \
        ogs_assert((__mME)); \
        ((__mME)->sgw_ue)->sgw_s11_teid = 0; \
    } while(0)

#define MME_SESS_CLEAR(__sESS) \
    do { \
        mme_ue_t *mme_ue = NULL; \
        ogs_assert(__sESS); \
        mme_ue = (__sESS)->mme_ue; \
        ogs_assert(mme_ue); \
        ogs_info("Removed Session: UE IMSI:[%s] APN:[%s]", \
                mme_ue->imsi_bcd, \
                (__sESS)->session ? (__sESS)->session->name : "Unknown"); \
        if (mme_sess_count(mme_ue) == 1) /* Last Session */ \
            CLEAR_SESSION_CONTEXT(mme_ue); \
        mme_sess_remove(__sESS); \
    } while(0)

#define ACTIVE_EPS_BEARERS_IS_AVAIABLE(__mME) \
    (mme_ue_have_active_eps_bearers(__mME))
#define MME_SESSION_RELEASE_PENDING(__mME) \
    (mme_ue_have_session_release_pending(__mME))
typedef struct mme_sess_s {
    ogs_lnode_t     lnode;

    uint8_t         pti;        /* Procedure Trasaction Identity */

    uint32_t        pgw_s5c_teid;
    ogs_ip_t        pgw_s5c_ip;

    /* PDN Connectivity Request */
    ogs_nas_request_type_t request_type;

    /* mme_bearer_first(sess) : Default Bearer Context */
    ogs_list_t      bearer_list;

    /* Related Context */
    mme_ue_t        *mme_ue;

    ogs_session_t   *session;

    /* Save Protocol Configuration Options from UE */
    struct {
        uint8_t length;
        uint8_t *buffer;
    } ue_pco;

    /* Save Extended Protocol Configuration Options from UE */
    struct {
        uint16_t length;
        uint8_t *buffer;
    } ue_epco;

    /* Save Protocol Configuration Options from PGW */
    ogs_tlv_octet_t pgw_pco;

    /* Save Extended Protocol Configuration Options from PGW */
    ogs_tlv_octet_t pgw_epco;
} mme_sess_t;

#define MME_HAVE_ENB_S1U_PATH(__bEARER) \
    ((__bEARER) && ((__bEARER)->enb_s1u_teid))
#define CLEAR_ENB_S1U_PATH(__bEARER) \
    do { \
        ogs_assert((__bEARER)); \
        (__bEARER)->enb_s1u_teid = 0; \
    } while(0)

#define MME_HAVE_SGW_S1U_PATH(__sESS) \
    ((__sESS) && (mme_bearer_first(__sESS)) && \
     ((mme_default_bearer_in_sess(__sESS)->sgw_s1u_teid)))
#define CLEAR_SGW_S1U_PATH(__sESS) \
    do { \
        mme_bearer_t *__bEARER = NULL; \
        ogs_assert((__sESS)); \
        __bEARER = mme_default_bearer_in_sess(__sESS); \
        __bEARER->sgw_s1u_teid = 0; \
    } while(0)

#define MME_HAVE_ENB_DL_INDIRECT_TUNNEL(__bEARER) \
    ((__bEARER) && ((__bEARER)->enb_dl_teid))
#define MME_HAVE_ENB_UL_INDIRECT_TUNNEL(__bEARER) \
    ((__bEARER) && ((__bEARER)->enb_ul_teid))
#define MME_HAVE_SGW_DL_INDIRECT_TUNNEL(__bEARER) \
    ((__bEARER) && ((__bEARER)->sgw_dl_teid))
#define MME_HAVE_SGW_UL_INDIRECT_TUNNEL(__bEARER) \
    ((__bEARER) && ((__bEARER)->sgw_ul_teid))
#define CLEAR_INDIRECT_TUNNEL(__bEARER) \
    do { \
        ogs_assert((__bEARER)); \
        (__bEARER)->enb_dl_teid = 0; \
        (__bEARER)->enb_ul_teid = 0; \
        (__bEARER)->sgw_dl_teid = 0; \
        (__bEARER)->sgw_ul_teid = 0; \
    } while(0)
typedef struct mme_bearer_s {
    ogs_lnode_t     lnode;
    ogs_lnode_t     to_modify_node;

    ogs_fsm_t       sm;             /* State Machine */

    uint8_t         *ebi_node;      /* Pool-Node for EPS Bearer ID */
    uint8_t         ebi;            /* EPS Bearer ID */

    uint32_t        enb_s1u_teid;
    ogs_ip_t        enb_s1u_ip;
    uint32_t        sgw_s1u_teid;
    ogs_ip_t        sgw_s1u_ip;
    uint32_t        pgw_s5u_teid;
    ogs_ip_t        pgw_s5u_ip;

    uint32_t        target_s1u_teid;    /* Target S1U TEID from HO-Req-Ack */
    ogs_ip_t        target_s1u_ip;      /* Target S1U ADDR from HO-Req-Ack */

    uint32_t        enb_dl_teid;
    ogs_ip_t        enb_dl_ip;
    uint32_t        enb_ul_teid;
    ogs_ip_t        enb_ul_ip;

    uint32_t        sgw_dl_teid;
    ogs_ip_t        sgw_dl_ip;
    uint32_t        sgw_ul_teid;
    ogs_ip_t        sgw_ul_ip;

    ogs_qos_t       qos;
    ogs_tlv_octet_t tft;   /* Saved TFT */

#define CLEAR_BEARER_ALL_TIMERS(__bEARER) \
    do { \
        CLEAR_BEARER_TIMER((__bEARER)->t3489); \
    } while(0);
#define CLEAR_BEARER_TIMER(__bEARER_TIMER) \
    do { \
        ogs_timer_stop((__bEARER_TIMER).timer); \
        if ((__bEARER_TIMER).pkbuf) \
        { \
            ogs_pkbuf_free((__bEARER_TIMER).pkbuf); \
            (__bEARER_TIMER).pkbuf = NULL; \
        } \
        (__bEARER_TIMER).retry_count = 0; \
    } while(0);
    struct {
        ogs_pkbuf_t     *pkbuf;
        ogs_timer_t     *timer;
        uint32_t        retry_count;;
    } t3489;

    /* Related Context */
    mme_ue_t        *mme_ue;
    mme_sess_t      *sess;

    struct {
        ogs_gtp_xact_t  *xact;
    } create, update, delete, notify;
} mme_bearer_t;

void mme_context_init(void);
void mme_context_final(void);
mme_context_t *mme_self(void);

int mme_context_parse_config(void);

mme_sgsn_t *mme_sgsn_add(ogs_sockaddr_t *addr);
void mme_sgsn_remove(mme_sgsn_t *sgsn);
void mme_sgsn_remove_all(void);
mme_sgsn_t *mme_sgsn_find_by_addr(const ogs_sockaddr_t *addr);
mme_sgsn_t *mme_sgsn_find_by_routing_address(const ogs_nas_rai_t *rai, uint16_t cell_id);
mme_sgsn_t *mme_sgsn_find_by_default_routing_address(void);

mme_sgw_t *mme_sgw_add(ogs_sockaddr_t *addr);
void mme_sgw_remove(mme_sgw_t *sgw);
void mme_sgw_remove_all(void);
mme_sgw_t *mme_sgw_find_by_addr(const ogs_sockaddr_t *addr);

mme_pgw_t *mme_pgw_add(ogs_sockaddr_t *addr);
void mme_pgw_remove(mme_pgw_t *pgw);
void mme_pgw_remove_all(void);
ogs_sockaddr_t *mme_pgw_addr_find_by_apn_enb(
        ogs_list_t *list, int family, const mme_sess_t *sess);

mme_vlr_t *mme_vlr_add(ogs_sockaddr_t *sa_list, ogs_sockopt_t *option);
void mme_vlr_remove(mme_vlr_t *vlr);
void mme_vlr_remove_all(void);
void mme_vlr_close(mme_vlr_t *vlr);
mme_vlr_t *mme_vlr_find_by_addr(const ogs_sockaddr_t *addr);

mme_csmap_t *mme_csmap_add(mme_vlr_t *vlr);
void mme_csmap_remove(mme_csmap_t *csmap);
void mme_csmap_remove_all(void);

mme_csmap_t *mme_csmap_find_by_tai(const ogs_eps_tai_t *tai);
mme_csmap_t *mme_csmap_find_by_nas_lai(const ogs_nas_lai_t *lai);

mme_enb_t *mme_enb_add(ogs_sock_t *sock, ogs_sockaddr_t *addr);
int mme_enb_remove(mme_enb_t *enb);
int mme_enb_remove_all(void);
mme_enb_t *mme_enb_find_by_addr(const ogs_sockaddr_t *addr);
mme_enb_t *mme_enb_find_by_enb_id(uint32_t enb_id);
int mme_enb_set_enb_id(mme_enb_t *enb, uint32_t enb_id);
int mme_enb_sock_type(ogs_sock_t *sock);
mme_enb_t *mme_enb_cycle(mme_enb_t *enb);

enb_ue_t *enb_ue_add(mme_enb_t *enb, uint32_t enb_ue_s1ap_id);
void enb_ue_remove(enb_ue_t *enb_ue);
void enb_ue_switch_to_enb(enb_ue_t *enb_ue, mme_enb_t *new_enb);
enb_ue_t *enb_ue_find_by_enb_ue_s1ap_id(
        const mme_enb_t *enb, uint32_t enb_ue_s1ap_id);
enb_ue_t *enb_ue_find(uint32_t index);
enb_ue_t *enb_ue_find_by_mme_ue_s1ap_id(uint32_t mme_ue_s1ap_id);
enb_ue_t *enb_ue_cycle(enb_ue_t *enb_ue);

sgw_ue_t *sgw_ue_add(mme_sgw_t *sgw);
void sgw_ue_remove(sgw_ue_t *sgw_ue);
void sgw_ue_switch_to_sgw(sgw_ue_t *sgw_ue, mme_sgw_t *new_sgw);
sgw_ue_t *sgw_ue_cycle(sgw_ue_t *sgw_ue);

typedef enum {
    SGW_WITHOUT_RELOCATION = 1,
    SGW_WITH_RELOCATION = 2,
    SGW_HAS_ALREADY_BEEN_RELOCATED = 3,
} sgw_relocation_e;
sgw_relocation_e sgw_ue_check_if_relocated(mme_ue_t *mme_ue);

void mme_ue_new_guti(mme_ue_t *mme_ue);
void mme_ue_confirm_guti(mme_ue_t *mme_ue);

mme_ue_t *mme_ue_add(enb_ue_t *enb_ue);
void mme_ue_remove(mme_ue_t *mme_ue);
void mme_ue_remove_all(void);
mme_ue_t *mme_ue_cycle(mme_ue_t *mme_ue);

void mme_ue_fsm_init(mme_ue_t *mme_ue);
void mme_ue_fsm_fini(mme_ue_t *mme_ue);

mme_ue_t *mme_ue_find_by_imsi(const uint8_t *imsi, int imsi_len);
mme_ue_t *mme_ue_find_by_imsi_bcd(const char *imsi_bcd);
mme_ue_t *mme_ue_find_by_guti(const ogs_nas_eps_guti_t *nas_guti);
mme_ue_t *mme_ue_find_by_s11_local_teid(uint32_t teid);
mme_ue_t *mme_ue_find_by_gn_local_teid(uint32_t teid);

mme_ue_t *mme_ue_find_by_message(const ogs_nas_eps_message_t *message);
int mme_ue_set_imsi(mme_ue_t *mme_ue, char *imsi_bcd);

bool mme_ue_have_indirect_tunnel(mme_ue_t *mme_ue);
void mme_ue_clear_indirect_tunnel(mme_ue_t *mme_ue);

bool mme_ue_have_active_eps_bearers(mme_ue_t *mme_ue);
bool mme_sess_have_active_eps_bearers(mme_sess_t *sess);
bool mme_ue_have_session_release_pending(mme_ue_t *mme_ue);
bool mme_sess_have_session_release_pending(mme_sess_t *sess);

int mme_ue_xact_count(mme_ue_t *mme_ue, uint8_t org);

/*
 * o RECV Initial UE-Message : S-TMSI
 * o RECV Attach Request : IMSI, GUTI
 * o RECV TAU Request : GUTI
 * ### MME_UE_ASSOCIATE_ENB_UE() ###
 * ### MME_UE_ECM_CONNECTED() ###
 *
 * o RECV Initial Context Setup Failure in EMM Registered State
 * ### ENB_UE_DEASSOCIATE_MME_UE() ###
 * ### ENB_UE_REMOVE() ###
 * ### ENB_UE_UNLINK() ###
 *
 * o SEND UE Context Release Command with S1_REMOVE_AND_UNLINK
 *   - RECV UE Context Release Complete
 * ### ENB_UE_REMOVE() ###
 * ### ENB_UE_UNLINK() ###
 *
 * o SEND UE Context Release Command with UE_CONTEXT_REMOVE
 *   - RECV UE Context Release Complete
 * ### ENB_UE_REMOVE() ###
 * ### MME_UE_REMOVE() ###
 *
 *
 * o RECV Handover Required
 * ### ENB_UE_SOURCE_ASSOCIATE_TARGET() ####
 *   - SEND Handover Request
 *
 * o RECV Handover Notify
 * ### MME_UE_ASSOCIATE_ENB_UE(TARGET) ###
 * ### MME_UE_ECM_CONNECTED(TARGET) ###
 *   - Modify Bearer Request/Response
 *   - UE Context Release Command/Complete
 * ### ENB_UE_SOURCE_DEASSOCIATE_TARGET() ####
 * ### ENB_UE_REMOVE() ####
 *   - Delete Indirect Data Forwarding Tunnel Request/Response
 *
 * o RECV Handover Cancel
 *   - UE Context Release Command/Complete
 * ### ENB_UE_SOURCE_DEASSOCIATE_TARGET() ####
 * ### ENB_UE_REMOVE() ####
 *   - Delete Indirect Data Forwarding Tunnel Request/Response
 *
 * o RECV Handover Failure
 *   - UE Context Release Command/Complete
 * ### ENB_UE_SOURCE_DEASSOCIATE_TARGET() ####
 * ### ENB_UE_REMOVE() ####
 *   - Delete Indirect Data Forwarding Tunnel Request/Response
 */
void enb_ue_associate_mme_ue(enb_ue_t *enb_ue, mme_ue_t *mme_ue);
void enb_ue_deassociate(enb_ue_t *enb_ue);
void enb_ue_unlink(mme_ue_t *mme_ue);
void enb_ue_source_associate_target(enb_ue_t *source_ue, enb_ue_t *target_ue);
void enb_ue_source_deassociate_target(enb_ue_t *enb_ue);

void sgw_ue_associate_mme_ue(sgw_ue_t *sgw_ue, mme_ue_t *mme_ue);
void sgw_ue_deassociate(sgw_ue_t *sgw_ue);
void sgw_ue_unlink(mme_ue_t *mme_ue);
void sgw_ue_source_associate_target(sgw_ue_t *source_ue, sgw_ue_t *target_ue);
void sgw_ue_source_deassociate_target(sgw_ue_t *sgw_ue);

mme_sess_t *mme_sess_add(mme_ue_t *mme_ue, uint8_t pti);
void mme_sess_remove(mme_sess_t *sess);
void mme_sess_remove_all(mme_ue_t *mme_ue);
mme_sess_t *mme_sess_find_by_pti(const mme_ue_t *mme_ue, uint8_t pti);
mme_sess_t *mme_sess_find_by_ebi(const mme_ue_t *mme_ue, uint8_t ebi);
mme_sess_t *mme_sess_find_by_apn(const mme_ue_t *mme_ue, const char *apn);

mme_sess_t *mme_sess_first(const mme_ue_t *mme_ue);
mme_sess_t *mme_sess_next(mme_sess_t *sess);
unsigned int mme_sess_count(const mme_ue_t *mme_ue);

mme_bearer_t *mme_bearer_add(mme_sess_t *sess);
void mme_bearer_remove(mme_bearer_t *bearer);
void mme_bearer_remove_all(mme_sess_t *sess);
mme_bearer_t *mme_bearer_find_by_sess_ebi(const mme_sess_t *sess, uint8_t ebi);
mme_bearer_t *mme_bearer_find_by_ue_ebi(const mme_ue_t *mme_ue, uint8_t ebi);
mme_bearer_t *mme_bearer_find_or_add_by_message(
        mme_ue_t *mme_ue, ogs_nas_eps_message_t *message, int create_action);
mme_bearer_t *mme_default_bearer_in_sess(mme_sess_t *sess);
mme_bearer_t *mme_linked_bearer(mme_bearer_t *bearer);
mme_bearer_t *mme_bearer_first(const mme_sess_t *sess);
mme_bearer_t *mme_bearer_next(mme_bearer_t *bearer);
mme_bearer_t *mme_bearer_cycle(mme_bearer_t *bearer);

void mme_session_remove_all(mme_ue_t *mme_ue);
ogs_session_t *mme_session_find_by_apn(mme_ue_t *mme_ue, const char *apn);
ogs_session_t *mme_default_session(mme_ue_t *mme_ue);

int mme_find_served_tai(ogs_eps_tai_t *tai);

mme_m_tmsi_t *mme_m_tmsi_alloc(void);
int mme_m_tmsi_free(mme_m_tmsi_t *tmsi);

void mme_ebi_pool_init(mme_ue_t *mme_ue);
void mme_ebi_pool_final(mme_ue_t *mme_ue);
void mme_ebi_pool_clear(mme_ue_t *mme_ue);

uint8_t mme_selected_int_algorithm(mme_ue_t *mme_ue);
uint8_t mme_selected_enc_algorithm(mme_ue_t *mme_ue);

#ifdef __cplusplus
}
#endif

#endif /* MME_CONTEXT_H */
