/* GMM PDUs, 3GPP TS 9.4 24.008 GPRS Mobility Management Messages */
/* (C) 2023 by Sysmocom s.f.m.c. GmbH
 *
 * All Rights Reserved
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 *
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
/* MS network capability 10.5.5.12*/
struct gprs_gmm_ms_net_cap {
#if OSMO_IS_LITTLE_ENDIAN
	uint8_t gea1:1,
		sm_cap_dedicated:1,
		sm_cap_gprs:1,
		ucs2_support:1,
		ss_screening_ind:2,
		solsa_cap:1,
		rev_level_ind:1;
	uint8_t pfc_feature_mode:1,
		gea2:1,
		gea3:1,
		gea4:1,
		gea5:1,
		gea6:1,
		gea7:1,
		lcs_va_cap:1;
	uint8_t ps_inter_rat_ho_geran2utran:1,
		ps_inter_rat_ho_geran2eutran:1,
		emm_combined_proc_cap:1,
		isr_support:1,
		srvcc_cap:1,
		epc_cap:1,
		nf_capability:1,
		geran_net_sharing_cap:1;
	uint8_t user_plane_integrity_protection_sup:1,
		gia4:1,
		gia5:1,
		gia6:1,
		gia7:1,
		epco_ie_ind:1,
		restrict_use_enhanced_cov_cap:1,
		dual_conn_eutra_nr_cap:1;
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
	uint8_t rev_level_ind:1, solsa_cap:1, ss_screening_ind:2, ucs2_support:1, sm_cap_gprs:1, sm_cap_dedicated:1, gea1:1;
	uint8_t lcs_va_cap:1, gea7:1, gea6:1, gea5:1, gea4:1, gea3:1, gea2:1, pfc_feature_mode:1;
	uint8_t geran_net_sharing_cap:1, nf_capability:1, epc_cap:1, srvcc_cap:1, isr_support:1, emm_combined_proc_cap:1, ps_inter_rat_ho_geran2eutran:1, ps_inter_rat_ho_geran2utran:1;
	uint8_t dual_conn_eutra_nr_cap:1, restrict_use_enhanced_cov_cap:1, epco_ie_ind:1, gia7:1, gia6:1, gia5:1, gia4:1, user_plane_integrity_protection_sup:1;
#endif
} __attribute__((packed));
static const struct gprs_gmm_ms_net_cap ms_net_cap_def = {
	.gea1 = 1,
	.sm_cap_dedicated = 1,
	.sm_cap_gprs = 1,
	.ucs2_support = 0,
	.ss_screening_ind = 1,
	.solsa_cap = 0,
	.rev_level_ind = 1,
	.pfc_feature_mode = 1,
	.gea2 = 1,
	.gea3 = 1,
	.gea4 = 0,
	.gea5 = 0,
	.gea6 = 0,
	.gea7 = 0,
	.lcs_va_cap = 0,
	.ps_inter_rat_ho_geran2utran = 0,
	.ps_inter_rat_ho_geran2eutran = 0,
	.emm_combined_proc_cap = 0,
	.isr_support = 0,
	.srvcc_cap = 0,
	.epc_cap = 0,
	.nf_capability = 0,
	.geran_net_sharing_cap = 0,
	.user_plane_integrity_protection_sup = 0,
	.gia4 = 0,
	.gia5 = 0,
	.gia6 = 0,
	.gia7 = 0,
	.epco_ie_ind = 0,
	.restrict_use_enhanced_cov_cap = 0,
	.dual_conn_eutra_nr_cap = 0,
};
/* 10.5.1.2 Ciphering Key Sequence Number */
#define CIPH_CKSN_UNAVAIL 0x03
/* 10.5.5.6 DRX parameter */
struct gprs_gmm_drx_param {
#if OSMO_IS_LITTLE_ENDIAN
	uint8_t split_pg_cycle_code;
	uint8_t coeff:4,
		split_ccch:1,
		non_drx_timer:3;
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
	uint8_t split_pg_cycle_code;
	uint8_t non_drx_timer:3, split_ccch:1, coeff:4;
#endif
} __attribute__((packed));
static const struct gprs_gmm_drx_param drx_param_def = {
	.split_pg_cycle_code = 10,
	.coeff = 0,
	.split_ccch = 0,
	.non_drx_timer = 0,
};
/* Remove after depending on libosmocore > 1.10.0 */
#ifndef GSM48_IE_GMM_UE_NET_CAP
#define GSM48_IE_GMM_UE_NET_CAP 0x58
#endif
#ifndef GSM48_IE_GMM_VD_PREF_UE_USAGE
#define GSM48_IE_GMM_VD_PREF_UE_USAGE 0x5d
#endif
#ifndef GSM48_IE_GMM_ADD_IDENTITY
#define GSM48_IE_GMM_ADD_IDENTITY 0x1a
#endif
#ifndef GSM48_IE_GMM_RAI2
#define GSM48_IE_GMM_RAI2 0x1b
#endif
const struct tlv_definition gprs_gmm_att_tlvdef = {
	.def = {
		[GSM48_IE_GMM_CIPH_CKSN]	= { TLV_TYPE_SINGLE_TV, 1 },
		[GSM48_IE_GMM_PTMSI_TYPE]	= { TLV_TYPE_SINGLE_TV, 1 },
		[GSM48_IE_GMM_TMSI_BASED_NRI_C]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_TIMER_READY]	= { TLV_TYPE_TV, 1 },
		[GSM48_IE_GMM_ALLOC_PTMSI]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_PTMSI_SIG]	= { TLV_TYPE_FIXED, 3 },
		[GSM48_IE_GMM_ADD_IDENTITY]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_RAI2]		= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_AUTH_RAND]	= { TLV_TYPE_FIXED, 16 },
		[GSM48_IE_GMM_AUTH_SRES]	= { TLV_TYPE_FIXED, 4 },
		[GSM48_IE_GMM_IMEISV]		= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_CAUSE]		= { TLV_TYPE_TV, 1 },
		[GSM48_IE_GMM_RX_NPDU_NUM_LIST]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_DRX_PARAM]	= { TLV_TYPE_FIXED, 2 },
		[GSM48_IE_GMM_AUTN]		= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_AUTH_RES_EXT]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_TIMER_T3302]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_AUTH_FAIL_PAR]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_MS_NET_CAPA]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_PDP_CTX_STATUS]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_PS_LCS_CAPA]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_GMM_MBMS_CTX_ST]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_TIMER_T3346]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_UE_NET_CAP]	= { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_VD_PREF_UE_USAGE] = { TLV_TYPE_TLV, 0 },
		[GSM48_IE_GMM_NET_FEAT_SUPPORT] = { TLV_TYPE_SINGLE_TV, 1 },
	},
};
static int encode_ms_net_cap(struct gprs_gmm_entity *gmme, struct msgb *msg)
{
	int rc;
	uint8_t *l; /* len */
	struct bitvec bv = {
		.data = msg->tail,
		.data_len = GSM_MACBLOCK_LEN,
	};
	msgb_put_u8(msg, GSM48_IE_GMM_MS_NET_CAPA);
	l = msgb_put(msg, 1); /* len */
	/* TODO: we hardcode a MS Net Cap for now. We may want to pass it from the app at some point: */
	rc = bitvec_unhex(&bv, "e5e0");
	*l = OSMO_BYTES_FOR_BITS(bv.cur_bit);
	msgb_put(msg, *l);
	return rc;
}
static int encode_ms_ra_acc_cap(struct gprs_gmm_entity *gmme, struct msgb *msg)
{
	int rc;
	uint8_t *l; /* len */
	struct bitvec bv = {
		.data = msg->tail,
		.data_len = GSM_MACBLOCK_LEN,
	};
	l = msgb_put(msg, 1); /* len */
	/* TODO: we hardcode a MS Ra Cap for now. We may want to pass it from the app at some point: */
	rc = bitvec_unhex(&bv, "171933432b37159ef98879cba28c6621e72688b198879c00");
	*l = OSMO_BYTES_FOR_BITS(bv.cur_bit);
	msgb_put(msg, *l);
	return rc;
}
/* Chapter 9.4.1: Attach request */
int gprs_gmm_build_attach_req(struct gprs_gmm_entity *gmme,
			      enum osmo_gprs_gmm_attach_type attach_type,
			      bool attach_with_imsi,
			      struct msgb *msg)
{
	struct gsm48_hdr *gh;
	uint8_t byte, cksn;
	struct osmo_mobile_identity mi;
	uint8_t *l;
	int rc;
	struct gsm48_ra_id *raid_enc;
	unsigned long t3314_sec;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_ATTACH_REQ;
	/* 10.5.5.12 MS network capability */
	msgb_lv_put(msg, sizeof(ms_net_cap_def), (const uint8_t *)&ms_net_cap_def);
	/* Attach type 10.5.5.2 */
	/* Ciphering key sequence number 10.5.1.2 */
	cksn = 0; /* Use 0 as Ciphering Key Sequence Number */
	byte = (cksn << 4) | (attach_type & 0x0f);
	msgb_put_u8(msg, byte);
	/* DRX parameter 10.5.5.6 */
	memcpy(msgb_put(msg, sizeof(drx_param_def)),
	       &drx_param_def,
	       sizeof(drx_param_def));
	/* Mobile identity 10.5.1.4 */
	/* 4.7.3.1.1: If "AttachWithIMSI" is configured, use IMSI instead: */
	if (attach_with_imsi) {
		mi = (struct osmo_mobile_identity){
			.type = GSM_MI_TYPE_IMSI,
		};
		OSMO_STRLCPY_ARRAY(mi.imsi, gmme->imsi);
	} else {
		mi = (struct osmo_mobile_identity){
			.type = GSM_MI_TYPE_TMSI,
			.tmsi = gmme->ptmsi,
		};
	}
	l = msgb_put(msg, 1); /* len */
	rc = osmo_mobile_identity_encode_msgb(msg, &mi, false);
	if (rc < 0)
		return -EINVAL;
	*l = rc;
	/* Old routing area identification 0.5.5.15 */
	raid_enc = (struct gsm48_ra_id *)msgb_put(msg, sizeof(struct gsm48_ra_id));
	gsm48_encode_ra(raid_enc, &gmme->ra);
	/* MS Radio Access capability 10.5.5.12a */
	rc = encode_ms_ra_acc_cap(gmme, msg);
	if (rc < 0)
		return -EINVAL;
	/* TODO: optional fields */
	/* 10.5.5.8 Old P-TMSI signature: */
	if (!attach_with_imsi && gmme->ptmsi != GSM_RESERVED_TMSI) {
		uint8_t ptmsi_sig[3] = { gmme->ptmsi_sig >> 16, gmme->ptmsi_sig >> 8, gmme->ptmsi_sig };
		msgb_tv_fixed_put(msg, GSM48_IE_GMM_PTMSI_SIG, sizeof(ptmsi_sig), ptmsi_sig);
	}
	/* 10.5.7.3 Requested READY timer value */
	t3314_sec = osmo_tdef_get(g_gmm_ctx->T_defs, 3314, OSMO_TDEF_S, -1);
	msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY, gprs_gmm_secs_to_gprs_tmr_floor(t3314_sec));
	/* 9.4.1.13 P-TMSI type: The MS shall include this IE if the
	 * type of identity in the Mobile identity IE is set to
	 * "TMSI/P-TMSI/M-TMSI". */
	if (!attach_with_imsi) {
		uint8_t ptmsi_type_native = 1; /* Table 10.5.5.29.1 */
		msgb_v_put(msg, (GSM48_IE_GMM_PTMSI_TYPE << 4) | (ptmsi_type_native & 0x01));
	}
	return 0;
}
/* 9.4.3 Attach complete */
int gprs_gmm_build_attach_compl(struct gprs_gmm_entity *gmme, struct msgb *msg)
{
	struct gsm48_hdr *gh;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_ATTACH_COMPL;
	/* TODO: Add optional IEs */
	return 0;
}
/* 9.4.8 P-TMSI reallocation complete */
int gprs_gmm_build_ptmsi_realloc_compl(struct gprs_gmm_entity *gmme, struct msgb *msg)
{
	struct gsm48_hdr *gh;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_PTMSI_REALL_COMPL;
	return 0;
}
/* 9.4.10a Authentication and Ciphering Failure */
int gprs_gmm_build_auth_ciph_fail(struct gprs_gmm_entity *gmme, struct msgb *msg,
				  enum gsm48_gmm_cause cause)
{
	struct gsm48_hdr *gh;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_PTMSI_REALL_COMPL;
	/* 10.5.5.14 Cause */
	msgb_put_u8(msg, (uint8_t)cause);
	/* TODO: 10.5.3.2.2 Authentication Failure parameter */
	return 0;
}
/* Chapter 9.4.14: Routing area update request */
int gprs_gmm_build_rau_req(struct gprs_gmm_entity *gmme,
			   enum gprs_gmm_upd_type rau_type,
			   struct msgb *msg)
{
	struct gsm48_hdr *gh;
	uint8_t byte, cksn;
	int rc;
	struct gsm48_ra_id *raid_enc;
	unsigned long t3314_sec;
	uint8_t ptmsi_type_native = 1; /* Table 10.5.5.29.1 */
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_RA_UPD_REQ;
	/* 10.5.5.18 Update type */
	cksn = gmme->auth_ciph.req.key_seq;
	byte = (cksn << 4) | (((uint8_t)rau_type) & 0x07);
	msgb_put_u8(msg, byte);
	/* Old routing area identification 0.5.5.15 */
	raid_enc = (struct gsm48_ra_id *)msgb_put(msg, sizeof(struct gsm48_ra_id));
	gsm48_encode_ra(raid_enc, &gmme->ra);
	/* MS Radio Access capability 10.5.5.12a */
	rc = encode_ms_ra_acc_cap(gmme, msg);
	if (rc < 0)
		return -EINVAL;
	/* 10.5.5.8 Old P-TMSI signature: */
	if (gmme->ptmsi_sig != GSM_RESERVED_TMSI) {
		uint8_t ptmsi_sig[3] = { gmme->ptmsi_sig >> 16, gmme->ptmsi_sig >> 8, gmme->ptmsi_sig };
		msgb_tv_fixed_put(msg, GSM48_IE_GMM_PTMSI_SIG, sizeof(ptmsi_sig), ptmsi_sig);
	}
	/* 10.5.7.3 Requested READY timer value */
	t3314_sec = osmo_tdef_get(g_gmm_ctx->T_defs, 3314, OSMO_TDEF_S, -1);
	msgb_tv_put(msg, GSM48_IE_GMM_TIMER_READY, gprs_gmm_secs_to_gprs_tmr_floor(t3314_sec));
	/* DRX parameter 10.5.5.6 */
	memcpy(msgb_put(msg, sizeof(drx_param_def)),
	       &drx_param_def,
	       sizeof(drx_param_def));
	/* 9.4.14.6 MS network capability */
	rc = encode_ms_net_cap(gmme, msg);
	if (rc < 0)
		return -EINVAL;
	/* 9.4.14.7 PDP context status */
	/* TODO: implement. Table 9.4.14/3GPP TS 24.00 states it is optional (O) but 9.4.14.7 states:
	 * "This IE shall be included by the MS." */
	/* 9.4.14.17 P-TMSI type */
	/* Table 9.4.14/3GPP TS 24.00 states it is optional (O) but 9.4.14.17 states:
	 * "This IE shall be included by the MS." */
	msgb_v_put(msg, (GSM48_IE_GMM_PTMSI_TYPE << 4) | (ptmsi_type_native & 0x01));
	return 0;
}
/* 9.2.11 Identity response */
int gprs_gmm_build_identity_resp(struct gprs_gmm_entity *gmme,
				 uint8_t mi_type,
				 struct msgb *msg)
{
	struct gsm48_hdr *gh;
	struct osmo_mobile_identity mi;
	uint8_t *l;
	int rc;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_ID_RESP;
	/* Mobile identity 10.5.1.4 */
	switch (mi_type) {
	case GSM_MI_TYPE_IMSI:
		mi = (struct osmo_mobile_identity){
			.type = GSM_MI_TYPE_IMSI,
		};
		OSMO_STRLCPY_ARRAY(mi.imsi, gmme->imsi);
		break;
	case GSM_MI_TYPE_TMSI:
		mi = (struct osmo_mobile_identity){
			.type = GSM_MI_TYPE_TMSI,
			.tmsi = gmme->ptmsi,
		};
		break;
	case GSM_MI_TYPE_IMEI:
		mi = (struct osmo_mobile_identity){
			.type = GSM_MI_TYPE_IMEI,
		};
		OSMO_STRLCPY_ARRAY(mi.imei, gmme->imei);
		break;
	case GSM_MI_TYPE_IMEISV:
		mi = (struct osmo_mobile_identity){
			.type = GSM_MI_TYPE_IMEISV,
		};
		OSMO_STRLCPY_ARRAY(mi.imeisv, gmme->imeisv);
		break;
	default:
		LOGGMME(gmme, LOGL_ERROR, "Tx GMM IDENTITY RESPONSE: mi_type=%s not supported!\n",
			gsm48_mi_type_name(mi_type));
		return -EINVAL;
	}
	l = msgb_put(msg, 1); /* len */
	rc = osmo_mobile_identity_encode_msgb(msg, &mi, false);
	if (rc < 0)
		return -EINVAL;
	*l = rc;
	/* TODO: Optional IEs */
	return 0;
}
/* Tx GMM Authentication and ciphering response, 9.4.10 */
int gprs_gmm_build_auth_ciph_resp(const struct gprs_gmm_entity *gmme, const uint8_t *sres, struct msgb *msg)
{
	struct gsm48_hdr *gh;
	struct gsm48_auth_ciph_resp *acr;
	int rc = 0;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_RESP;
	acr = (struct gsm48_auth_ciph_resp *) msgb_put(msg, sizeof(*acr));
	acr->ac_ref_nr = gmme->auth_ciph.req.ac_ref_nr;
	/* Authentication parameter Response, 10.5.3.2 */
	if (sres)
		msgb_tv_fixed_put(msg, GSM48_IE_GMM_AUTH_SRES, 4, sres);
	/* IMEISV, 10.5.1.4 */
	if (gmme->auth_ciph.req.imeisv_requested) {
		uint8_t *l;
		struct osmo_mobile_identity mi = (struct osmo_mobile_identity){
			.type = GSM_MI_TYPE_IMEISV,
		};
		OSMO_STRLCPY_ARRAY(mi.imeisv, gmme->imeisv);
		l = msgb_tl_put(msg, GSM48_IE_GMM_IMEISV);
		rc = osmo_mobile_identity_encode_msgb(msg, &mi, false);
		if (rc < 0)
			return -EINVAL;
		*l = rc;
	}
	/* TODO: Authentication Response parameter (extension) */
	/* TODO: Message authentication code */
	return rc;
}
int gprs_gmm_build_detach_req(struct gprs_gmm_entity *gmme,
			      enum osmo_gprs_gmm_detach_ms_type detach_type,
			      enum osmo_gprs_gmm_detach_poweroff_type poweroff_type,
			      struct msgb *msg)
{
	struct gsm48_hdr *gh;
	uint8_t byte;
	struct osmo_mobile_identity mi;
	uint8_t *l;
	int rc;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_DETACH_REQ;
	/* Detach type 10.5.5.5 + Spare half octet 10.5.1.8 */
	byte = ((detach_type & 0x07) << 5) | (poweroff_type & 0x01) << 4;
	msgb_put_u8(msg, byte);
	/* DRX parameter 10.5.5.6 */
	memcpy(msgb_put(msg, sizeof(drx_param_def)),
	       &drx_param_def,
	       sizeof(drx_param_def));
	/* P-TMSI, Mobile identity 10.5.1.4 */
	mi = (struct osmo_mobile_identity){
		.type = GSM_MI_TYPE_TMSI,
		.tmsi = gmme->ptmsi,
	};
	l = msgb_put(msg, 1); /* len */
	rc = osmo_mobile_identity_encode_msgb(msg, &mi, false);
	if (rc < 0)
		return -EINVAL;
	*l = rc;
	/* TODO: optional fields: P-TMSI signature 10.5.5.8a */
	return 0;
}
int gprs_gmm_build_rau_compl(struct gprs_gmm_entity *gmme, struct msgb *msg)
{
	struct gsm48_hdr *gh;
	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
	gh->proto_discr = GSM48_PDISC_MM_GPRS;
	gh->msg_type = GSM48_MT_GMM_RA_UPD_COMPL;
	/* TODO: 3GPP TS 24.008 4.7.5.1.3 "If Receive N-PDU Numbers were
	 * included, the Receive N-PDU Numbers values valid in the MS, shall be included in
	 * the ROUTING AREA UPDATE COMPLETE message."
	 */
	/* TODO: Add optional IEs */
	return 0;
}