/* Encoding/Decoding routines for GSM System Information messages
 * according to 3GPP TS 44.018 Version 12.3.0 Release 12
 *
 * (C) 2018 Harald Welte <laforge@gnumonks.org>
 * All rights reserved.
 *
 * Released under the terms of GNU General Public License, Version 2 or
 * (at your option) any later version.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

module GSM_SystemInformation {

	import from General_Types all;
	import from GSM_Types all;
	import from GSM_RR_Types all;
	import from GSM_RestOctets all;
	import from Osmocom_Types all;

	type union ArfcnOrMaio {
		uint12_t	arfcn,
		MaioHsn		maio_hsn
	} with { variant "" };

	/* 24.008 10.5.1.1 */
	type uint16_t SysinfoCellIdentity;

	/* 44.018 10.5.2.1b */
	type octetstring CellChannelDescription with { variant "FIELDLENGTH(16)" };

	/* 44.018 10.5.2.3 */
	type enumerated CellOptions_DTX {
		MS_MAY_USE_UL_DTX	('00'B),
		MS_SHALL_USE_UL_DTX	('01'B),
		MS_SHALL_NOT_USE_UL_DTX	('10'B)
	} with { variant "FIELDLENGTH(2)" };
	type record CellOptions {
		boolean		dn_ind,
		boolean		pwrc,
		CellOptions_DTX	dtx,
		uint4_t		radio_link_tout_div4
	} with { variant "" };

	/* 44.018 10.5.2.3a */
	type record CellOptionsSacch {
		BIT1		dtx_ext,
		boolean		pwrc,
		BIT2		dtx,
		BIT4		radio_link_timeout
	} with { variant "" };

	/* 44.018 10.5.2.4 */
	type record CellSelectionParameters {
		uint3_t		cell_resel_hyst_2dB,
		uint5_t		ms_txpwr_max_cch,
		BIT1		acs,
		boolean		neci,
		uint6_t		rxlev_access_min
	} with { variant "" };

	/* 44.018 10.5.2.11 */
	type enumerated CtrlChanDesc_CC {
		CCHAN_DESC_1CCCH_NOT_COMBINED	('000'B),
		CCHAN_DESC_1CCCH_COMBINED	('001'B),
		CCHAN_DESC_2CCCH_NOT_COMBINED	('010'B),
		CCHAN_DESC_3CCCH_NOT_COMBINED	('100'B),
		CCHAN_DESC_4CCCH_NOT_COMBINED	('110'B)
	} with { variant "FIELDLENGTH(3)" };
	type enumerated CBQ3 {
		CBQ3_IU_MODE_NOT_SUPPORTED	('00'B),
		CBQ3_IU_MODE_MS_BARRED		('01'B),
		CBQ3_IU_MODE_NOT_BARRED		('10'B)
	} with { variant "FIELDLENGTH(2)" };
	type record ControlChannelDescription {
		boolean		msc_r99,
		boolean		att,
		uint3_t		bs_ag_blks_res,
		CtrlChanDesc_CC	ccch_conf,
		boolean		si22ind,
		CBQ3		cbq3,
		BIT2		spare,
		uint3_t		bs_pa_mfrms, /* off by 2 */
		uint8_t		t3212
	} with { variant "" };

	template ControlChannelDescription t_ControlChannelDescription := { ?, ?, ?, ?, ?, ?, '00'B, ?, ? };

	/* 44.018 10.5.2.22 */
	type octetstring NeighbourCellDescription with { variant "FIELDLENGTH(16)" };

	/* 44.018 10.5.2.22a */
	type octetstring NeighbourCellDescription2 with { variant "FIELDLENGTH(16)" };

	type bitstring AccessControlClass with { variant "FIELDLENGTH(16), BYTEORDER(last)" };

	/* 44.018 10.5.2.29 */
	type enumerated RachCtrlPar_MR {
		RACH_MAX_RETRANS_1	('00'B),
		RACH_MAX_RETRANS_2	('01'B),
		RACH_MAX_RETRANS_4	('10'B),
		RACH_MAX_RETRANS_7	('11'B)
	} with { variant "FIELDLENGTH(2)" };
	type record RachControlParameters {
		RachCtrlPar_MR	max_retrans,
		BIT4		tx_integer,
		boolean		cell_barr_access,
		boolean		re_not_allowed,
		AccessControlClass acc
	} with { variant (acc) "FIELDLENGTH(16)" };

	/* 44.018 9.1.31 */
	type record SystemInformationType1 {
		CellChannelDescription	cell_chan_desc,
		RachControlParameters	rach_control,
		RestOctets		rest_octets length(0..1)
	} with { variant "" };

	/* 44.018 9.1.32 */
	type record SystemInformationType2 {
		NeighbourCellDescription bcch_freq_list,
		BIT8			ncc_permitted,
		RachControlParameters	rach_control
	} with { variant "" };

	/* 44.018 9.1.33 */
	type record SystemInformationType2bis {
		NeighbourCellDescription	extd_bcch_freq_list,
		RachControlParameters		rach_control,
		RestOctets			rest_octets length(0..1)
	} with { variant "" };

	/* 44.018 9.1.34 */
	type record SystemInformationType2ter {
		NeighbourCellDescription2	extd_bcch_freq_list,
		RestOctets			rest_octets length(0..4)
	} with { variant "" };

	type record SystemInformationType2quater {
		SI2quaterRestOctets	rest_octets
	} with { variant "" };

	/* 44.018 9.1.35 */
	type record SystemInformationType3 {
		SysinfoCellIdentity		cell_id,
		LocationAreaIdentification	lai,
		ControlChannelDescription	ctrl_chan_desc,
		CellOptions			cell_options,
		CellSelectionParameters		cell_sel_par,
		RachControlParameters		rach_control,
		SI3RestOctets			rest_octets
	} with { variant "" };

	template SystemInformationType3 t_SI3 := {
		cell_id := ?,
		lai := ?,
		ctrl_chan_desc := t_ControlChannelDescription,
		cell_options := ?,
		cell_sel_par := ?,
		rach_control := ?,
		rest_octets := ?
	};


	/* 44.018 9.1.36 */
	type record SystemInformationType4 {
		LocationAreaIdentification	lai,
		CellSelectionParameters		cell_sel_par,
		RachControlParameters		rach_control,
		ChannelDescriptionTV		cbch_chan_desc optional,
		MobileAllocationTLV		cbch_mobile_alloc optional,
		SI4RestOctets			rest_octets /* see 10.5.2.35 */
	} with { variant "TAG(cbch_chan_desc, iei = '64'O; cbch_mobile_alloc, iei = '72'O)" };

	/* 44.018 9.1.37 */
	type record SystemInformationType5 {
		NeighbourCellDescription	bcch_freq_list
	} with { variant "" };

	/* 44.018 9.1.38 */
	type record SystemInformationType5bis {
		NeighbourCellDescription	extd_bcch_freq_list
	} with { variant "" };

	/* 44.018 9.1.39 */
	type record SystemInformationType5ter {
		NeighbourCellDescription2	extd_bcch_freq_list
	} with { variant "" };

	/* 44.018 9.1.40 */
	type record SystemInformationType6 {
		SysinfoCellIdentity		cell_id,
		LocationAreaIdentification	lai,
		CellOptionsSacch		cell_options,
		BIT8				ncc_permitted,
		SI6RestOctets			rest_octets
	} with { variant "" };

	/* 44.018 9.1.43a */
	type record SystemInformationType13 {
		SI13RestOctets			rest_octets
	} with { variant "" };

	type union SystemInformationUnion {
		SystemInformationType1		si1,
		SystemInformationType2		si2,
		SystemInformationType2bis	si2bis,
		SystemInformationType2ter	si2ter,
		SystemInformationType2quater	si2quater,
		SystemInformationType3		si3,
		SystemInformationType4		si4,
		SystemInformationType5		si5,
		SystemInformationType5bis	si5bis,
		SystemInformationType5ter	si5ter,
		SystemInformationType6		si6,
		SystemInformationType13		si13,
		octetstring			other
	} with { variant "" };

	type record SystemInformation {
		RrHeader			header,
		SystemInformationUnion		payload
	} with { variant (payload) "CROSSTAG(si1, header.message_type = SYSTEM_INFORMATION_TYPE_1;
			      si2, header.message_type = SYSTEM_INFORMATION_TYPE_2;
			      si2bis, header.message_type = SYSTEM_INFORMATION_TYPE_2bis;
			      si2ter, header.message_type = SYSTEM_INFORMATION_TYPE_2ter;
			      si2quater, header.message_type = SYSTEM_INFORMATION_TYPE_2quater;
			      si3, header.message_type = SYSTEM_INFORMATION_TYPE_3;
			      si4, header.message_type = SYSTEM_INFORMATION_TYPE_4;
			      si5, header.message_type = SYSTEM_INFORMATION_TYPE_5;
			      si5bis, header.message_type = SYSTEM_INFORMATION_TYPE_5bis;
			      si5ter, header.message_type = SYSTEM_INFORMATION_TYPE_5ter;
			      si6, header.message_type = SYSTEM_INFORMATION_TYPE_6;
			      si13, header.message_type = SYSTEM_INFORMATION_TYPE_13;
			      other, OTHERWISE;
			)" };

	external function enc_SystemInformationNoPad(in SystemInformation si) return octetstring
		with { extension "prototype(convert) encode(RAW)" };
	external function dec_SystemInformation(in octetstring stream) return SystemInformation
		with { extension "prototype(convert) decode(RAW)" };

	/* Due to a buggy nature of TITAN's padding attributes, we have to apply padding manually. */
	function enc_SystemInformation(in SystemInformation si) return octetstring
	{
		var octetstring si_enc := enc_SystemInformationNoPad(si);

		/* Resulting message length depends on SI Type */
		select (si.header.message_type) {
		case (SYSTEM_INFORMATION_TYPE_5,
		      SYSTEM_INFORMATION_TYPE_5bis,
		      SYSTEM_INFORMATION_TYPE_5ter) {
			/* SACCH: no Rest Octets, return 'as-is' */
			return si_enc;
			}
		case (SYSTEM_INFORMATION_TYPE_6) {
			/* SACCH: pad to 19 octets, leave room for L1/LAPDm headers */
			return f_pad_oct(si_enc, 19, '2B'O);
			}
		case else {
			/* BCCH: pad to 23 octets */
			return f_pad_oct(si_enc, 23, '2B'O);
			}
		}
	}

	external function dec_SystemInformationSafeBT(in octetstring stream, out SystemInformation si)
		return integer /* Decoding result: successful (0) or unsuccessful (1) */
		with { extension "prototype(backtrack) decode(RAW)" };

	/* Some types of System Information (mostly the Rest Octets) are not fully implemented,
	 * so calling the generic dec_SystemInformation() may result in a DTE.  This function
	 * additionally checks RR Protocol Discriminator, and should be used in the most cases. */
	function dec_SystemInformationSafe(in octetstring stream, out SystemInformation si)
	return integer {
		/* Try to decode a given octetstring as System Information */
		if (dec_SystemInformationSafeBT(stream, si) != 0) {
			log("Failed to decode (RR) System Information: ", stream);
			return 1;
		}

		/* Check the protocol discriminator (we expect RR messages) */
		if (si.header.rr_protocol_discriminator != bit2int('0110'B)) {
			log("Protocol discriminator is not RR (!= '0110'B): ",
			    si.header.rr_protocol_discriminator);
			return 1;
		}

		return 0;
	}

} with { encode "RAW"; variant "FIELDORDER(msb)" }
