#pragma once

/* SCCP User SAP description */

/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
 * All Rights Reserved
 *
 * This program 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 2 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 <http://www.gnu.org/licenses/>.
 *
 */

#include <stdint.h>
#include <osmocom/core/prim.h>
#include <osmocom/sigtran/sigtran_sap.h>

#include <netinet/in.h>

/* detailed coding of primitives at the SAP_SCCP_USER */

/*! \brief SCCP-User primitives as per Q.711 */
enum osmo_scu_prim_type {
	/* connection oriented, 6.1.1 */
	OSMO_SCU_PRIM_N_CONNECT,
	OSMO_SCU_PRIM_N_DATA,
	OSMO_SCU_PRIM_N_EXPEDITED_DATA,
	OSMO_SCU_PRIM_N_DISCONNECT,
	OSMO_SCU_PRIM_N_RESET,
	OSMO_SCU_PRIM_N_INFORM,
	/* connectionless, 6.2.2 */
	OSMO_SCU_PRIM_N_UNITDATA,
	OSMO_SCU_PRIM_N_NOTICE,
	/* management */
	OSMO_SCU_PRIM_N_COORD,
	OSMO_SCU_PRIM_N_STATE,
	OSMO_SCU_PRIM_N_PCSTATE,
};

#define OSMO_SCCP_ADDR_T_GT	0x0001 /* global title */
#define OSMO_SCCP_ADDR_T_PC	0x0002 /* signalling point code */
#define OSMO_SCCP_ADDR_T_SSN	0x0004 /* subsystem number */
#define OSMO_SCCP_ADDR_T_IPv4	0x0008
#define OSMO_SCCP_ADDR_T_IPv6	0x0010
#define OSMO_SCCP_ADDR_T_MASK	0x001f

/* Q.713 3.4.1 + RFC 3868 3.10.2.3 */
enum osmo_sccp_routing_ind {
	OSMO_SCCP_RI_NONE,
	OSMO_SCCP_RI_GT,
	OSMO_SCCP_RI_SSN_PC,
	OSMO_SCCP_RI_SSN_IP,
};

extern const struct value_string osmo_sccp_routing_ind_names[];
static inline const char *osmo_sccp_routing_ind_name(enum osmo_sccp_routing_ind val)
{ return get_value_string(osmo_sccp_routing_ind_names, val); }


/* Q.713 3.4.1 + RFC 3868 3.10.2.3 */
enum osmo_sccp_gti {
	OSMO_SCCP_GTI_NO_GT,
	OSMO_SCCP_GTI_NAI_ONLY,
	OSMO_SCCP_GTI_TT_ONLY,
	OSMO_SCCP_GTI_TT_NPL_ENC,
	OSMO_SCCP_GTI_TT_NPL_ENC_NAI,
};

extern const struct value_string osmo_sccp_gti_names[];
static inline const char *osmo_sccp_gti_name(enum osmo_sccp_gti val)
{ return get_value_string(osmo_sccp_gti_names, val); }

/* RFC 3868 3.10.2.3 */
enum osmo_sccp_npi {
	OSMO_SCCP_NPI_UNKNOWN = 0,
	OSMO_SCCP_NPI_E164_ISDN		= 1,
	OSMO_SCCP_NPI_GEERIC		= 2,
	OSMO_SCCP_NPI_X121_DATA		= 3,
	OSMO_SCCP_NPI_F69_TELEX		= 4,
	OSMO_SCCP_NPI_E210_MARITIME	= 5,
	OSMO_SCCP_NPI_E212_LAND		= 6,
	OSMO_SCCP_NPI_E214_ISDN_MOBILE	= 7,
	OSMO_SCCP_NPI_PRIVATE		= 14,
};

/* Q.713 3.4.2.3.1 + RFC 3868 3.10.2.3 */
enum osmo_sccp_nai {
	OSMO_SCCP_NAI_UNKNOWN		= 0,
	OSMO_SCCP_NAI_SUBSCR		= 1,
	OSMO_SCCP_NAI_RES_NAT_USE	= 2,
	OSMO_SCCP_NAI_NATL		= 3,
	OSMO_SCCP_NAI_INTL		= 4,
	/* 5.. 255: Spare */
};

/* Q.713 3.4.2.2 */
enum osmo_sccp_ssn {
	/* globally standardised for GSM/UMTS: 1-31 */
	OSMO_SCCP_SSN_MGMT		= 1,
	OSMO_SCCP_SSN_ISUP		= 3,
	OSMO_SCCP_SSN_OMAP		= 4,
	OSMO_SCCP_SSN_MAP		= 5,
	OSMO_SCCP_SSN_HLR		= 6,
	OSMO_SCCP_SSN_VLR		= 7,
	OSMO_SCCP_SSN_MSC		= 8,
	OSMO_SCCP_SSN_EIR		= 9,
	OSMO_SCCP_SSN_AUC		= 0x0a,
	/* Q.713 */
	OSMO_SCCP_SSN_ISDN_SS		= 0x0b,
	OSMO_SCCP_SSN_RES_INTL		= 0x0c,
	OSMO_SCCP_SSN_BISDN		= 0x0d,
	OSMO_SCCP_SSN_TC_TEST		= 0x0e,
	/* national network SSN for wihin and between GSM/UMTS: 129-150 */
	OSMO_SCCP_SSN_RANAP		= 142,
	OSMO_SCCP_SSN_RNSAP		= 143,
	OSMO_SCCP_SSN_GMLC_MAP		= 145,
	OSMO_SCCP_SSN_CAP		= 146,
	OSMO_SCCP_SSN_gsmSCF_MAP	= 147,
	OSMO_SCCP_SSN_SIWF_MAP		= 148,
	OSMO_SCCP_SSN_SGSN_MAP		= 149,
	OSMO_SCCP_SSN_GGSN_MAP		= 150,
	/* national network SSN within GSM/UMTS: 32-128 + 151-254 */
	OSMO_SCCP_SSN_PCAP		= 249,
	OSMO_SCCP_SSN_BSC_BSSAP_LE	= 250,
	OSMO_SCCP_SSN_MSC_BSSAP_LE	= 251,
	OSMO_SCCP_SSN_SMLC_BSSAP_LE	= 252,
	OSMO_SCCP_SSN_BSS_OAM		= 253,
	OSMO_SCCP_SSN_BSSAP		= 254,
};

/* Q.711 6.3.2.2.5 Signalling point status */
enum osmo_sccp_sp_status {
	OSMO_SCCP_SP_S_INACCESSIBLE	= 1,
	OSMO_SCCP_SP_S_CONGESTED	= 2,
	OSMO_SCCP_SP_S_ACCESSIBLE	= 3,
};

extern const struct value_string osmo_sccp_sp_status_names[];
static inline const char *osmo_sccp_sp_status_name(enum osmo_sccp_sp_status val)
{
	return get_value_string(osmo_sccp_sp_status_names, val);
}

/* Q.711 6.3.2.2.6 Remote SCCP status */
enum osmo_sccp_rem_sccp_status {
	OSMO_SCCP_REM_SCCP_S_AVAILABLE 			= 1,
	OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN	= 2,
	OSMO_SCCP_REM_SCCP_S_UNEQUIPPED			= 3,
	OSMO_SCCP_REM_SCCP_S_INACCESSIBLE		= 4,
	OSMO_SCCP_REM_SCCP_S_CONGESTED			= 5,
};

extern const struct value_string osmo_sccp_rem_sccp_status_names[];
static inline const char *osmo_sccp_rem_sccp_status_name(enum osmo_sccp_rem_sccp_status val)
{
	return get_value_string(osmo_sccp_rem_sccp_status_names, val);
}

/* legacy shim for name change */
#define OSMO_SCCP_SSN_SMLC_BSSAP OSMO_SCCP_SSN_SMLC_BSSAP_LE

extern const struct value_string osmo_sccp_ssn_names[];
static inline const char *osmo_sccp_ssn_name(enum osmo_sccp_ssn val)
{ return get_value_string(osmo_sccp_ssn_names, val); }

struct osmo_sccp_gt {
	uint8_t gti;
	uint8_t tt;
	uint32_t npi;
	uint32_t nai;
	char digits[32];
};

struct osmo_sccp_addr {
	uint32_t presence;
	enum osmo_sccp_routing_ind ri;
	struct osmo_sccp_gt gt;
	uint32_t pc;
	uint32_t ssn;
	union {
		struct in_addr v4;
		struct in6_addr v6;
	} ip;
	/* we don't do hostnames */
};

/* OSMO_SCU_PRIM_N_CONNECT */
struct osmo_scu_connect_param {
	struct osmo_sccp_addr called_addr;
	struct osmo_sccp_addr calling_addr;
	struct osmo_sccp_addr responding_addr;
	//struct osmo_sccp_qos_pars qos_pars;
	uint32_t sccp_class;
	uint32_t importance;
	uint32_t conn_id;
	/* user data */
};

/* OSMO_SCU_PRIM_N_DATA / OSMO_SCU_PRIM_N_EXPEDITED_DATA */
struct osmo_scu_data_param {
	uint32_t conn_id;
	uint32_t importance;
	/* user data */
};

enum osmo_sccp_originator {
	OSMO_SCCP_ORIG_NS_PROVIDER,
	OSMO_SCCP_ORIG_NS_USER,
	OSMO_SCCP_ORIG_UNDEFINED,
};

/* OSMO_SCU_PRIM_N_DISCONNECT */
struct osmo_scu_disconn_param {
	enum osmo_sccp_originator originator;
	struct osmo_sccp_addr responding_addr;
	uint32_t cause;
	uint32_t conn_id;
	uint32_t importance;
	/* user data */
};

/* OSMO_SCU_PRIM_N_RESET */
struct osmo_scu_reset_param {
	enum osmo_sccp_originator originator;
	uint32_t cause;
	uint32_t conn_id;
};

/* OSMO_SCU_PRIM_N_UNITDATA */
/* NOTE: Ideally there should have been a "bool in_sequence_control_present",
 * but that was found too late, so instead set:
 * "in_sequence_control = OSMO_SCU_UNITDATA_PARAM_SEQUENCE_CONTROL_NOT_PRESENT"
 * to mark the field not present (aka select Protocol Class 0).
 * See ITU-T Q.711 6.2.1 for more information.
 */
#define OSMO_SCU_UNITDATA_REQ_P_SEQUENCE_CONTROL_NOT_PRESENT 0xffffffff
struct osmo_scu_unitdata_param {
	struct osmo_sccp_addr called_addr;
	struct osmo_sccp_addr calling_addr;
	uint32_t in_sequence_control;
	uint32_t return_option;
	uint32_t importance;
	/* user data */
};

/* OSMO_SCU_PRIM_N_NOTICE */
struct osmo_scu_notice_param {
	struct osmo_sccp_addr called_addr;
	struct osmo_sccp_addr calling_addr;
	/* ITU Q.711 6.2.2.2.4 "Reason for return":
	 * enum sccp_return_cause (ITU Q.713 3.12), as per ITU Q.713 A.5. */
	uint32_t cause;
	uint32_t importance; /* ITU Q.711 6.2.2.2.6, Q.713 3.19 */
	/* user data */
};

/* OSMO_SCU_PRIM_N_STATE */
struct osmo_scu_state_param {
	uint32_t affected_pc;
	uint32_t affected_ssn;
	bool user_in_service; /* true: UIS; false: UOS */
	uint32_t ssn_multiplicity_ind;
};

/* OSMO_SCU_PRIM_N_PCSTATE */
struct osmo_scu_pcstate_param {
	uint32_t affected_pc;
	uint32_t restricted_importance_level;
	enum osmo_sccp_sp_status sp_status;
	enum osmo_sccp_rem_sccp_status remote_sccp_status;
};

struct osmo_scu_prim {
	struct osmo_prim_hdr oph;
	union {
		struct osmo_scu_connect_param connect;
		struct osmo_scu_data_param data;
		struct osmo_scu_disconn_param disconnect;
		struct osmo_scu_reset_param reset;
		struct osmo_scu_unitdata_param unitdata;
		struct osmo_scu_notice_param notice;
		struct osmo_scu_state_param state;
		struct osmo_scu_pcstate_param pcstate;
	} u;
};

#define msgb_scu_prim(msg) ((struct osmo_scu_prim *)(msg)->l1h)

extern const struct value_string osmo_scu_prim_type_names[];
static inline const char *osmo_scu_prim_type_name(enum osmo_scu_prim_type val)
{
	return get_value_string(osmo_scu_prim_type_names, val);
}

int osmo_scu_prim_hdr_name_buf(char *buf, size_t buflen, const struct osmo_prim_hdr *oph);
char *osmo_scu_prim_hdr_name_c(void *ctx, const struct osmo_prim_hdr *oph);
char *osmo_scu_prim_name(const struct osmo_prim_hdr *oph);

struct osmo_ss7_instance;
struct osmo_sccp_instance;
struct osmo_sccp_user;

void osmo_sccp_vty_init(void);

struct osmo_sccp_instance *osmo_sccp_instance_create(struct osmo_ss7_instance *ss7, void *priv);
void osmo_sccp_instance_destroy(struct osmo_sccp_instance *inst);
struct osmo_ss7_instance *osmo_sccp_get_ss7(const struct osmo_sccp_instance *sccp);
struct osmo_sccp_instance *osmo_sccp_get_sccp(const struct osmo_sccp_user *scu);
void osmo_sccp_set_priv(struct osmo_sccp_instance *sccp, void *priv);
void *osmo_sccp_get_priv(struct osmo_sccp_instance *sccp);

void osmo_sccp_user_unbind(struct osmo_sccp_user *scu);
void osmo_sccp_user_set_priv(struct osmo_sccp_user *scu, void *priv);
void *osmo_sccp_user_get_priv(struct osmo_sccp_user *scu);

struct osmo_sccp_user *osmo_sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name,
					      osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc);

struct osmo_sccp_user *osmo_sccp_user_bind(struct osmo_sccp_instance *inst, const char *name,
					   osmo_prim_cb prim_cb, uint16_t ssn);
struct osmo_sccp_user *osmo_sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc);

int osmo_sccp_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph);
int osmo_sccp_user_sap_down_nofree(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph);

int osmo_sccp_addr_create(struct osmo_ss7_instance *inst, const char *name, const struct osmo_sccp_addr *dest_addr);
int osmo_sccp_addr_update(struct osmo_ss7_instance *inst, const char *name, const struct osmo_sccp_addr *dest_addr);
struct osmo_ss7_instance *osmo_sccp_addr_by_name(struct osmo_sccp_addr *dest_addr, const char *name);
int osmo_sccp_addr_by_name_local(struct osmo_sccp_addr *dest_addr, const char *name,
				 const struct osmo_ss7_instance *inst);
const char *osmo_sccp_name_by_addr(const struct osmo_sccp_addr *addr);
void osmo_sccp_local_addr_by_instance(struct osmo_sccp_addr *dest_addr,
				      const struct osmo_sccp_instance *inst,
				      uint32_t ssn);

bool osmo_sccp_check_addr(struct osmo_sccp_addr *addr, uint32_t presence);
int osmo_sccp_addr_cmp(const struct osmo_sccp_addr *a, const struct osmo_sccp_addr *b, uint32_t presence_criteria);
int osmo_sccp_addr_ri_cmp(const struct osmo_sccp_addr *a, const struct osmo_sccp_addr *b);
int osmo_sccp_gt_cmp(const struct osmo_sccp_gt *a, const struct osmo_sccp_gt *b);

const char *osmo_sccp_user_name(struct osmo_sccp_user *scu);

int osmo_sccp_instance_next_conn_id(struct osmo_sccp_instance *sccp);
