/*
 * Copyright (C) 2019 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 SMF_CONTEXT_H
#define SMF_CONTEXT_H

#include "smf-config.h"

#include "ogs-gtp.h"
#include "ogs-diameter-gx.h"
#include "ogs-diameter-gy.h"
#include "ogs-diameter-rx.h"
#include "ogs-diameter-s6b.h"
#include "ogs-pfcp.h"
#include "ogs-sbi.h"
#include "ogs-app.h"
#include "ogs-ngap.h"
#include "ogs-nas-5gs.h"
#include "ipfw/ogs-ipfw.h"

#include "timer.h"
#include "smf-sm.h"
#include "metrics.h"

#if HAVE_NET_IF_H
#include <net/if.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

extern int __smf_log_domain;
extern int __gsm_log_domain;

#undef OGS_LOG_DOMAIN
#define OGS_LOG_DOMAIN __smf_log_domain

typedef enum {
    SMF_CTF_ENABLED_AUTO = 0,
    SMF_CTF_ENABLED_YES,
    SMF_CTF_ENABLED_NO,
} smf_ctf_enabled_mode;

typedef struct smf_ctf_config_s {
    smf_ctf_enabled_mode enabled;
} smf_ctf_config_t;

int smf_ctf_config_init(smf_ctf_config_t *ctf_config);

typedef struct smf_context_s {
    smf_ctf_config_t    ctf_config;
    const char*         diam_conf_path;   /* SMF Diameter conf path */
    ogs_diam_config_t   *diam_config;     /* SMF Diameter config */

    OpenAPI_nf_type_e   nf_type;

#define MAX_NUM_OF_DNS              2
    const char      *dns[MAX_NUM_OF_DNS];
    const char      *dns6[MAX_NUM_OF_DNS];

#define MAX_NUM_OF_P_CSCF           16
    const char      *p_cscf[MAX_NUM_OF_P_CSCF];
    int             num_of_p_cscf;
    int             p_cscf_index;
    const char      *p_cscf6[MAX_NUM_OF_P_CSCF];
    int             num_of_p_cscf6;
    int             p_cscf6_index;

    ogs_list_t      sgw_s5c_list;   /* SGW GTPC Node List */
    ogs_list_t      ip_pool_list;

    ogs_hash_t      *supi_hash;     /* hash table (SUPI) */
    ogs_hash_t      *imsi_hash;     /* hash table (IMSI) */
    ogs_hash_t      *ipv4_hash;     /* hash table (IPv4 Address) */
    ogs_hash_t      *ipv6_hash;     /* hash table (IPv6 Address) */
    ogs_hash_t      *n1n2message_hash; /* hash table (N1N2Message Location) */

    uint16_t        mtu;            /* MTU to advertise in PCO */

#define SMF_UE_IS_LAST_SESSION(__sMF) \
     ((__sMF) && (ogs_list_count(&(__sMF)->sess_list)) == 1)
    ogs_list_t      smf_ue_list;
} smf_context_t;

typedef struct smf_gtp_node_s {
    ogs_gtp_node_t *gnode;
    ogs_metrics_inst_t *metrics[_SMF_METR_GTP_NODE_MAX];
} smf_gtp_node_t;

typedef struct smf_ue_s {
    ogs_lnode_t lnode;

    /* SUPI */
    char *supi;

    /* IMSI */
    uint8_t imsi[OGS_MAX_IMSI_LEN];
    int imsi_len;
    char imsi_bcd[OGS_MAX_IMSI_BCD_LEN+1];

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

    /* IMEI */
    uint8_t imeisv[OGS_MAX_IMEISV_LEN];
    int imeisv_len;
    char  imeisv_bcd[OGS_MAX_IMEISV_BCD_LEN+1];

    ogs_list_t sess_list;
} smf_ue_t;

#define SMF_NF_INSTANCE_CLEAR(_cAUSE, _nFInstance) \
    do { \
        ogs_assert(_nFInstance); \
        if ((_nFInstance)->reference_count == 1) { \
            ogs_info("[%s] (%s) NF removed", (_nFInstance)->id, (_cAUSE)); \
            smf_nf_fsm_fini((_nFInstance)); \
        } else { \
            /* There is an assocation with other context */ \
            ogs_info("[%s:%d] (%s) NF suspended", \
                    _nFInstance->id, _nFInstance->reference_count, (_cAUSE)); \
            OGS_FSM_TRAN(&_nFInstance->sm, smf_nf_state_de_registered); \
            ogs_fsm_dispatch(&_nFInstance->sm, NULL); \
        } \
        ogs_sbi_nf_instance_remove(_nFInstance); \
    } while(0)
#define SMF_SESS_CLEAR(__sESS) \
    do { \
        smf_ue_t *smf_ue = NULL; \
        ogs_assert(__sESS); \
        smf_ue = __sESS->smf_ue; \
        ogs_assert(smf_ue); \
        if (SMF_UE_IS_LAST_SESSION(smf_ue)) \
            smf_ue_remove(smf_ue); \
        else \
            smf_sess_remove(__sESS); \
    } while(0)

typedef struct smf_bearer_s smf_bearer_t;
typedef struct smf_sess_s smf_sess_t;

typedef struct smf_pf_s {
    ogs_lnode_t     lnode;
    ogs_lnode_t     to_add_node;

ED3(uint8_t spare:2;,
    uint8_t direction:2;,
    uint8_t identifier:4;)

    uint8_t precedence;             /* Only used in EPC */

    uint8_t *identifier_node;       /* Pool-Node for Identifier */
    uint8_t *precedence_node;       /* Pool-Node for Precedence */

    ogs_ipfw_rule_t ipfw_rule;
    char *flow_description;

    smf_bearer_t    *bearer;
} smf_pf_t;

typedef struct smf_bearer_s {
    ogs_lnode_t     lnode;          /**< A node of list_t */

    ogs_lnode_t     to_modify_node;
    ogs_lnode_t     to_delete_node;

    uint32_t        index;

    ogs_pfcp_pdr_t  *dl_pdr;
    ogs_pfcp_pdr_t  *ul_pdr;
    ogs_pfcp_far_t  *dl_far;
    ogs_pfcp_far_t  *ul_far;
    ogs_pfcp_urr_t  *urr;
    ogs_pfcp_qer_t  *qer;

    uint8_t         *qfi_node;      /* Pool-Node for 5GC-QFI */
    uint8_t         qfi;            /* 5G Core QFI */
    uint8_t         ebi;            /* EPC EBI */

    uint32_t        pgw_s5u_teid;   /* PGW-S5U TEID */
    ogs_sockaddr_t  *pgw_s5u_addr;  /* PGW-S5U IPv4 */
    ogs_sockaddr_t  *pgw_s5u_addr6; /* PGW-S5U IPv6 */

    uint32_t        sgw_s5u_teid;   /* SGW-S5U TEID */
    ogs_ip_t        sgw_s5u_ip;     /* SGW-S5U IPv4/IPv6 */

    struct {
        char        *name;          /* EPC: PCC Rule Name */
        char        *id;            /* 5GC: PCC Rule Id */
    } pcc_rule;
    ogs_qos_t       qos;            /* QoS Information */

    OGS_POOL(pf_identifier_pool, uint8_t);

    /* Packet Filter List */
    ogs_list_t      pf_list;
    ogs_list_t      pf_to_add_list;

    uint8_t num_of_pf_to_delete;
    uint8_t pf_to_delete[OGS_MAX_NUM_OF_FLOW_IN_NAS];

    smf_sess_t      *sess;
} smf_bearer_t;

#define SMF_SESS(pfcp_sess) ogs_container_of(pfcp_sess, smf_sess_t, pfcp)
typedef struct smf_sess_s {
    ogs_sbi_object_t sbi;
    uint32_t        index;          /**< An index of this node */
    ogs_fsm_t       sm;             /* A state machine */
    struct {
        bool gx_ccr_init_in_flight; /* Waiting for Gx CCA */
        uint32_t gx_cca_init_err; /* Gx CCA RXed error code */
        bool gy_ccr_init_in_flight; /* Waiting for Gy CCA */
        uint32_t gy_cca_init_err; /* Gy CCA RXed error code */
        bool gx_ccr_term_in_flight; /* Waiting for Gx CCA */
        uint32_t gx_cca_term_err; /* Gx CCA RXed error code */
        bool gy_ccr_term_in_flight; /* Waiting for Gy CCA */
        uint32_t gy_cca_term_err; /* Gy CCA RXed error code */
        bool s6b_str_in_flight; /* Waiting for S6B CCA */
        uint32_t s6b_sta_err; /* S6B CCA RXed error code */
    } sm_data;

    bool            epc;            /**< EPC or 5GC */

    ogs_pfcp_sess_t pfcp;           /* PFCP session context */

    uint64_t        smpolicycontrol_features; /* SBI features */

    uint32_t        smf_n4_teid;    /* SMF-N4-TEID is derived from INDEX */

    uint32_t        sgw_s5c_teid;   /* SGW-S5C-TEID is received from SGW */
    ogs_ip_t        sgw_s5c_ip;     /* SGW-S5C IPv4/IPv6 */

    uint64_t        smf_n4_seid;    /* SMF SEID is dervied from INDEX */
    uint64_t        upf_n4_seid;    /* UPF SEID is received from Peer */

    uint32_t        upf_n3_teid;    /* UPF-N3 TEID */
    ogs_sockaddr_t  *upf_n3_addr;   /* UPF-N3 IPv4 */
    ogs_sockaddr_t  *upf_n3_addr6;  /* UPF-N3 IPv6 */

    uint32_t        gnb_n3_teid;    /* gNB-N3 TEID */
    ogs_ip_t        gnb_n3_ip;      /* gNB-N3 IPv4/IPv6 */

    char            *gx_sid;        /* Gx Session ID */
    char            *gy_sid;        /* Gx Session ID */
    char            *s6b_sid;       /* S6b Session ID */

    OGS_POOL(pf_precedence_pool, uint8_t);

#define CLEAR_QOS_FLOW_ID(__sESS) \
    do { \
        ogs_assert((__sESS)); \
        smf_qfi_pool_final(__sESS); \
        smf_qfi_pool_init(__sESS); \
    } while(0)
    OGS_POOL(qfi_pool, uint8_t);

    char            *sm_context_ref; /* smContextRef */
    uint8_t         psi; /* PDU session identity */
    uint8_t         pti; /* 5GS-NAS : Procedure transaction identity */

    char            *sm_context_status_uri; /* SmContextStatusNotification */
    struct {
        ogs_sbi_client_t *client;
    } namf;

    /* PCF sends the RESPONSE
     * of [POST] /npcf-smpolocycontrol/v1/policies */
    char *policy_association_id;

    /* PLMN ID & NID */
    ogs_plmn_id_t   plmn_id;

    /* LTE Location */
    ogs_eps_tai_t   e_tai;
    ogs_e_cgi_t     e_cgi;

    /* NR Location */
    ogs_5gs_tai_t   nr_tai;
    ogs_nr_cgi_t    nr_cgi;
    ogs_time_t      ue_location_timestamp;

    /* PCF ID */
    char            *pcf_id;

    /* Integrity protection maximum data rate */
    struct {
        uint8_t mbr_dl;
        uint8_t mbr_ul;
    } integrity_protection;

    /* S_NSSAI */
    ogs_s_nssai_t s_nssai;
    ogs_s_nssai_t mapped_hplmn;

    /* PDN Configuration */
    ogs_session_t session;
    uint8_t ue_session_type;
    uint8_t ue_ssc_mode;

    ogs_pfcp_ue_ip_t *ipv4;
    ogs_pfcp_ue_ip_t *ipv6;

    /* RAT Type */
    uint8_t gtp_rat_type;
    OpenAPI_rat_type_e sbi_rat_type;

    struct {
        uint8_t version; /* GTPC version */
        ogs_tlv_octet_t ue_pco;
        ogs_tlv_octet_t user_location_information;
        ogs_tlv_octet_t ue_timezone;
        ogs_tlv_octet_t charging_characteristics;
        bool create_session_response_apn_ambr;
        bool create_session_response_bearer_qos;
        uint8_t selection_mode; /* OGS_GTP{1,2}_SELECTION_MODE_*, same in GTPv1C and 2C. */
        struct {
            uint8_t nsapi;
            bool peer_supports_apn_ambr;
        } v1;  /* GTPv1C specific fields */
    } gtp; /* Saved from S5-C/Gn */

    struct {
        uint64_t ul_octets;
        uint64_t dl_octets;
        ogs_time_t duration;
        uint32_t reporting_reason; /* OGS_DIAM_GY_REPORTING_REASON_* */
        /* Snapshot of measurement when last report was sent: */
        struct {
            uint64_t ul_octets;
            uint64_t dl_octets;
            ogs_time_t duration;
        } last_report;
    } gy;

    struct {
        ogs_nas_extended_protocol_configuration_options_t ue_pco;
    } nas; /* Saved from NAS-5GS */

    struct {
        ogs_pcc_rule_t  pcc_rule[OGS_MAX_NUM_OF_PCC_RULE];
        int             num_of_pcc_rule;
    } policy; /* Saved from N7 or Gx */

    /* Paging */
    struct {
        bool ue_requested_pdu_session_establishment_done;
        char *n1n2message_location;
    } paging;

    /* State */
#define SMF_NGAP_STATE_NONE                                     0
#define SMF_NGAP_STATE_DELETE_TRIGGER_UE_REQUESTED              1
#define SMF_NGAP_STATE_DELETE_TRIGGER_PCF_INITIATED             2
#define SMF_NGAP_STATE_ERROR_INDICATION_RECEIVED_FROM_5G_AN     3
    struct {
        int pdu_session_resource_release;
    } ngap_state;

    /* Handover */
    struct {
        bool prepared;
        bool data_forwarding_not_possible;
        bool indirect_data_forwarding;

        /* NG-U UP Transport Information Saved Temporally */
        uint32_t gnb_n3_teid;
        ogs_ip_t gnb_n3_ip;

        /* Indirect DL Forwarding */
        uint32_t upf_dl_teid;
        ogs_sockaddr_t *upf_dl_addr;
        ogs_sockaddr_t *upf_dl_addr6;
        uint32_t gnb_dl_teid;
        ogs_ip_t gnb_dl_ip;
    } handover;

    /* Charging */
    struct {
        uint32_t id;
    } charging;

    /* Data Forwarding between the CP and UP functions */
    ogs_pfcp_pdr_t  *cp2up_pdr;
    ogs_pfcp_pdr_t  *up2cp_pdr;
    ogs_pfcp_far_t  *cp2up_far;
    ogs_pfcp_far_t  *up2cp_far;

    ogs_list_t      bearer_list;

    ogs_list_t      pdr_to_modify_list;
    ogs_list_t      qos_flow_to_modify_list;

    ogs_gtp_node_t  *gnode;
    ogs_pfcp_node_t *pfcp_node;

    smf_ue_t *smf_ue;
} smf_sess_t;

void smf_context_init(void);
void smf_context_final(void);
smf_context_t *smf_self(void);

int smf_context_parse_config(void);

int smf_use_gy_iface(void);

smf_gtp_node_t *smf_gtp_node_new(ogs_gtp_node_t *gnode);
void smf_gtp_node_free(smf_gtp_node_t *smf_gnode);

smf_ue_t *smf_ue_add_by_supi(char *supi);
smf_ue_t *smf_ue_add_by_imsi(uint8_t *imsi, int imsi_len);
void smf_ue_remove(smf_ue_t *smf_ue);
void smf_ue_remove_all(void);
smf_ue_t *smf_ue_find_by_supi(char *supi);
smf_ue_t *smf_ue_find_by_imsi(uint8_t *imsi, int imsi_len);

smf_sess_t *smf_sess_add_by_gtp1_message(ogs_gtp1_message_t *message);
smf_sess_t *smf_sess_add_by_gtp2_message(ogs_gtp2_message_t *message);
smf_sess_t *smf_sess_add_by_apn(smf_ue_t *smf_ue, char *apn, uint8_t rat_type);

smf_sess_t *smf_sess_add_by_sbi_message(ogs_sbi_message_t *message);
smf_sess_t *smf_sess_add_by_psi(smf_ue_t *smf_ue, uint8_t psi);

void smf_sess_select_upf(smf_sess_t *sess);
uint8_t smf_sess_set_ue_ip(smf_sess_t *sess);
void smf_sess_set_paging_n1n2message_location(
        smf_sess_t *sess, char *n1n2message_location);

void smf_sess_remove(smf_sess_t *sess);
void smf_sess_remove_all(smf_ue_t *smf_ue);

smf_sess_t *smf_sess_find(uint32_t index);
smf_sess_t *smf_sess_find_by_teid(uint32_t teid);
smf_sess_t *smf_sess_find_by_seid(uint64_t seid);
smf_sess_t *smf_sess_find_by_apn(smf_ue_t *smf_ue, char *apn, uint8_t rat_type);
smf_sess_t *smf_sess_find_by_psi(smf_ue_t *smf_ue, uint8_t psi);
smf_sess_t *smf_sess_find_by_charging_id(uint32_t charging_id);
smf_sess_t *smf_sess_find_by_sm_context_ref(char *sm_context_ref);
smf_sess_t *smf_sess_find_by_ipv4(uint32_t addr);
smf_sess_t *smf_sess_find_by_ipv6(uint32_t *addr6);
smf_sess_t *smf_sess_find_by_paging_n1n2message_location(
        char *n1n2message_location);
smf_sess_t *smf_sess_find_by_error_indication_report(
        smf_ue_t *smf_ue,
        ogs_pfcp_tlv_error_indication_report_t *error_indication_report);

void smf_sess_create_indirect_data_forwarding(smf_sess_t *sess);
bool smf_sess_have_indirect_data_forwarding(smf_sess_t *sess);
void smf_sess_delete_indirect_data_forwarding(smf_sess_t *sess);

void smf_sess_create_cp_up_data_forwarding(smf_sess_t *sess);
void smf_sess_delete_cp_up_data_forwarding(smf_sess_t *sess);

ogs_pcc_rule_t *smf_pcc_rule_find_by_id(smf_sess_t *sess, char *pcc_rule_id);

smf_bearer_t *smf_qos_flow_add(smf_sess_t *sess);
smf_bearer_t *smf_qos_flow_find_by_qfi(smf_sess_t *sess, uint8_t qfi);
smf_bearer_t *smf_qos_flow_find_by_pcc_rule_id(
        smf_sess_t *sess, char *pcc_rule_id);

smf_bearer_t *smf_bearer_add(smf_sess_t *sess);
int smf_bearer_remove(smf_bearer_t *bearer);
void smf_bearer_remove_all(smf_sess_t *sess);
smf_bearer_t *smf_bearer_find_by_pgw_s5u_teid(
        smf_sess_t *sess, uint32_t pgw_s5u_teid);
smf_bearer_t *smf_bearer_find_by_ebi(smf_sess_t *sess, uint8_t ebi);
smf_bearer_t *smf_bearer_find_by_pcc_rule_name(
        smf_sess_t *sess, char *pcc_rule_name);
smf_bearer_t *smf_bearer_find_by_pdr_id(
        smf_sess_t *sess, ogs_pfcp_pdr_id_t pdr_id);
smf_bearer_t *smf_default_bearer_in_sess(smf_sess_t *sess);

void smf_bearer_tft_update(smf_bearer_t *bearer);
void smf_bearer_qos_update(smf_bearer_t *bearer);

smf_ue_t *smf_ue_cycle(smf_ue_t *smf_ue);
smf_sess_t *smf_sess_cycle(smf_sess_t *sess);
smf_bearer_t *smf_qos_flow_cycle(smf_bearer_t *qos_flow);
smf_bearer_t *smf_bearer_cycle(smf_bearer_t *bearer);

void smf_sess_select_nf(smf_sess_t *sess, OpenAPI_nf_type_e nf_type);

smf_pf_t *smf_pf_add(smf_bearer_t *bearer);
int smf_pf_remove(smf_pf_t *pf);
void smf_pf_remove_all(smf_bearer_t *bearer);
smf_pf_t *smf_pf_find_by_id(smf_bearer_t *smf_bearer, uint8_t id);
smf_pf_t *smf_pf_find_by_flow(
    smf_bearer_t *bearer, uint8_t direction, char *flow_description);
smf_pf_t *smf_pf_first(smf_bearer_t *bearer);
smf_pf_t *smf_pf_next(smf_pf_t *pf);

int smf_pco_build(uint8_t *pco_buf, uint8_t *buffer, int length);

void smf_qfi_pool_init(smf_sess_t *sess);
void smf_qfi_pool_final(smf_sess_t *sess);

void smf_pf_identifier_pool_init(smf_bearer_t *bearer);
void smf_pf_identifier_pool_final(smf_bearer_t *bearer);

void smf_pf_precedence_pool_init(smf_sess_t *sess);
void smf_pf_precedence_pool_final(smf_sess_t *sess);

#ifdef __cplusplus
}
#endif

#endif /* SMF_CONTEXT_H */
