/* Data Types / Encoding / Decoding for OsmocomBB L1CTL interface */
/* (C) 2017 by Harald Welte <laforge@gnumonks.org>, derived from l1ctl_proto.h
 * which is (C) 2010 by Harald Welte + Holger Hans Peter Freyther
 * 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 L1CTL_Types {

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

	type uint32_t uint32_le with { variant "BYTEORDER(first)" };

	type enumerated L1ctlMsgType {
		L1CTL_NONE,
		L1CTL_FBSB_REQ,
		L1CTL_FBSB_CONF,
		L1CTL_DATA_IND,
		L1CTL_RACH_REQ,
		L1CTL_DM_EST_REQ,
		L1CTL_DATA_REQ,
		L1CTL_RESET_IND,
		L1CTL_PM_REQ,		/* power measurement */
		L1CTL_PM_CONF,		/* power measurement */
		L1CTL_ECHO_REQ,
		L1CTL_ECHO_CONF,
		L1CTL_RACH_CONF,
		L1CTL_RESET_REQ,
		L1CTL_RESET_CONF,
		L1CTL_DATA_CONF,
		L1CTL_CCCH_MODE_REQ,
		L1CTL_CCCH_MODE_CONF,
		L1CTL_DM_REL_REQ,
		L1CTL_PARAM_REQ,
		L1CTL_DM_FREQ_REQ,
		L1CTL_CRYPTO_REQ,
		L1CTL_SIM_REQ,
		L1CTL_SIM_CONF,
		L1CTL_TCH_MODE_REQ,
		L1CTL_TCH_MODE_CONF,
		L1CTL_NEIGH_PM_REQ,
		L1CTL_NEIGH_PM_IND,
		L1CTL_TRAFFIC_REQ,
		L1CTL_TRAFFIC_CONF,
		L1CTL_TRAFFIC_IND,
		L1CTL_BURST_IND,
		L1CTL_TBF_CFG_REQ,
		L1CTL_TBF_CFG_CONF,
		L1CTL_DATA_TBF_REQ,
		L1CTL_DATA_TBF_CONF,
		L1CTL_EXT_RACH_REQ,
		L1CTL_DATA_ABS_REQ /*!< FIXME: no such message in OsmocomBB */
	} with { variant "FIELDLENGTH(8)" };

	type enumerated L1ctlCcchMode {
		CCCH_MODE_NONE (0),
		CCCH_MODE_NON_COMBINED,
		CCCH_MODE_COMBINED,
		CCCH_MODE_COMBINED_CBCH
	} with { variant "FIELDLENGTH(8)" };

	type enumerated L1ctlNeighMode {
		NEIGH_MODE_NONE (0),
		NEIGH_MODE_PM,
		NEIGH_MODE_SB
	} with { variant "FIELDLENGTH(8)" };

	type enumerated L1ctlGprsCs {
		L1CTL_CS1 	(1),
		L1CTL_CS2,
		L1CTL_CS3,
		L1CTL_CS4,
		L1CTL_MCS1,
		L1CTL_MCS2,
		L1CTL_MCS3,
		L1CTL_MCS4,
		L1CTL_MCS5,
		L1CTL_MCS6,
		L1CTL_MCS7,
		L1CTL_MCS8,
		L1CTL_MCS9
	} with { variant "FIELDLENGTH(8)" };

	type enumerated L1ctlResetType {
		L1CTL_RES_T_BOOT (0),
		L1CTL_RES_T_FULL,
		L1CTL_RES_T_SCHED
	} with { variant "FIELDLENGTH(8)" };

	type record L1ctlHdrFlags {
		BIT7	padding,
		boolean f_done
	} with { variant "" };

	type record L1ctlHeader {
		L1ctlMsgType	msg_type,
		L1ctlHdrFlags	flags,
		OCT2		padding
	} with { variant "" };

	template L1ctlHeader
	tr_L1ctlHeader(template (present) L1ctlMsgType msg_type) := {
		msg_type := msg_type,
		flags := ?,
		padding := ?
	};

	template (value) L1ctlHeader
	ts_L1ctlHeader(template (value) L1ctlMsgType msg_type) := {
		msg_type := msg_type,
		flags := { padding := '0000000'B, f_done := false },
		padding := '0000'O
	};

	type record L1ctlDlInfo {
		RslChannelNr	chan_nr,
		RslLinkId	link_id,
		GsmBandArfcn	arfcn,
		uint32_t	frame_nr,
		GsmRxLev	rx_level,
		uint8_t		snr,
		uint8_t		num_biterr,
		uint8_t		fire_crc
	} with { variant "" };

	type record L1ctlFbsbConf {
		int16_t		initial_freq_err,
		uint8_t		result,
		uint8_t		bsic
	} with { variant "" };

	type record L1ctlCcchModeConf {
		L1ctlCcchMode	ccch_mode,
		OCT3		padding
	} with { variant "" };

	/* gsm48_chan_mode */
	type enumerated L1ctlTchMode {
		L1CTL_CHAN_MODE_SIGN		('00000000'B),	/* Signalling */
		L1CTL_CHAN_MODE_SPEECH_V1	('00000001'B),	/* FR or HR codec */
		L1CTL_CHAN_MODE_SPEECH_V2	('00100001'B),	/* EFR codec */
		L1CTL_CHAN_MODE_SPEECH_V3	('01000001'B)	/* AMR codec */
		/* Other modes are not supported for now */
	} with { variant "FIELDLENGTH(8)" };

	type enumerated L1ctlLoopMode {
		L1CTL_LOOP_MODE_OPEN		('00000000'B),
		L1CTL_LOOP_MODE_A		('00000001'B),
		L1CTL_LOOP_MODE_B		('00000010'B),
		L1CTL_LOOP_MODE_C		('00000011'B),
		L1CTL_LOOP_MODE_D		('00000100'B),
		L1CTL_LOOP_MODE_E		('00000101'B),
		L1CTL_LOOP_MODE_F		('00000110'B),
		L1CTL_LOOP_MODE_I		('00000111'B)
	} with { variant "FIELDLENGTH(8)" };

	type record L1ctlAudioMode {
		BIT4		padding,
		boolean		tx_microphone,
		boolean		tx_traffic_req,
		boolean		rx_speaker,
		boolean		rx_traffic_ind
	} with { variant "" };

	template (value) L1ctlAudioMode t_L1CTL_AudioModeNone := { '0000'B, false, false, false, false };

	/* Traffic forwarding mode (see TRAFFIC.{req,cnf,ind} messages) */
	template (value) L1ctlAudioMode t_L1CTL_AudioModeFwd
	modifies t_L1CTL_AudioModeNone := {
		tx_traffic_req := true,
		rx_traffic_ind := true
	};

	type record L1ctlTchModeConf {
		L1ctlTchMode	tch_mode,
		L1ctlAudioMode	audio_mode,
		record {
			uint8_t	start_codec,
			BIT8	codecs_bitmask
		} amr
	} with { variant "" };

	type record L1ctlDataInd {
		octetstring	payload
	} with {
		variant (payload) "BYTEORDER(first)"
	};

	type union L1ctlDlPayload {
		L1ctlFbsbConf		fbsb_conf,
		L1ctlCcchModeConf	ccch_mode_conf,
		L1ctlTchModeConf	tch_mode_conf,
		L1ctlDataInd		data_ind,
		L1ctlTrafficReq		traffic_ind,
		L1ctlTbfCfgReq		tbf_cfg_conf,
		octetstring		other
	} with {
		variant (other) "BYTEORDER(first)"
	};

	type record L1ctlDlMessage {
		L1ctlHeader	header,
		L1ctlDlInfo	dl_info optional,
		L1ctlDlPayload	payload optional
	} with { variant (dl_info) "PRESENCE(header.msg_type = L1CTL_FBSB_CONF,
					     header.msg_type = L1CTL_RACH_CONF,
					     header.msg_type = L1CTL_DATA_IND,
					     header.msg_type = L1CTL_DATA_CONF,
					     header.msg_type = L1CTL_TRAFFIC_IND,
					     header.msg_type = L1CTL_TRAFFIC_CONF)"
		 variant (payload) "CROSSTAG(fbsb_conf, header.msg_type = L1CTL_FBSB_CONF;
					     ccch_mode_conf, header.msg_type = L1CTL_CCCH_MODE_CONF;
					     tch_mode_conf, header.msg_type = L1CTL_TCH_MODE_CONF;
					     data_ind, header.msg_type = L1CTL_DATA_IND;
					     traffic_ind, header.msg_type = L1CTL_TRAFFIC_IND;
					     tbf_cfg_conf, header.msg_type = L1CTL_TBF_CFG_CONF;
					     other, OTHERWISE;
				)" };

	external function enc_L1ctlDlMessage(in L1ctlDlMessage msg) return octetstring
		with { extension "prototype(convert) encode(RAW)" };
	external function dec_L1ctlDlMessage(in octetstring stream) return L1ctlDlMessage
		with { extension "prototype(convert) decode(RAW)" };


	type record L1ctlUlInfo {
		RslChannelNr	chan_nr,
		RslLinkId	link_id,
		OCT2		padding
	} with { variant "" };

	type record L1ctlUlTbfInfo {
		uint8_t		tbf_nr,
		L1ctlGprsCs	cs,
		OCT2		padding
	} with { variant "" };

	type record L1ctlUlAbsInfo {
		uint8_t		tbf_nr,
		L1ctlGprsCs	cs,
		uint8_t		ts_nr,
		OCT1		padding,
		uint32_le	fn,
		GsmBandArfcn	arfcn,
		OCT2		padding2
	} with { variant "" };

	type record L1ctlFbsbFlags {
		BIT5		padding,
		boolean		sb,
		boolean		fb1,
		boolean		fb0
	} with { variant "FIELDORDER(msb)" };

	template (value) L1ctlFbsbFlags t_L1CTL_FBSB_F_ALL := {
		padding := '00000'B,
		sb := true,
		fb1 := true,
		fb0 := true
	};

	type record L1ctlFbsbReq {
		GsmBandArfcn	arfcn,
		uint16_t	timeout_tdma_frames,
		uint16_t	freq_err_thresh1,
		uint16_t	freq_err_thresh2,
		uint8_t		num_freqerr_avg,
		L1ctlFbsbFlags	flags,
		uint8_t		sync_info_idx,
		L1ctlCcchMode	ccch_mode,
		GsmRxLev	rxlev_exp
	} with { variant "" };

	type record L1ctlCcchModeReq {
		L1ctlCcchMode	ccch_mode,
		OCT3		padding
	} with { variant "" };

	type record L1ctlTchModeReq {
		L1ctlTchMode	tch_mode,
		L1ctlAudioMode	audio_mode,
		L1ctlLoopMode	loop_mode,
		record {
			uint8_t	start_codec,
			BIT8	codecs_bitmask
		} amr
	} with { variant "" };

	type record L1ctlRachReq {
		uint8_t		ra,
		uint8_t		combined,
		uint16_t	offset
	} with { variant "" };

	type enumerated L1ctlRachSynchSeq {
		RACH_SYNCH_SEQ_TS0 (0),
		RACH_SYNCH_SEQ_TS1,
		RACH_SYNCH_SEQ_TS2
	} with { variant "FIELDLENGTH(8)" };

	type record L1ctlExtRachReq {
		uint16_t		ra11,
		L1ctlRachSynchSeq	synch_seq,
		uint8_t			combined,
		uint16_t		offset
	} with { variant "" };

	type record L1ctlParReq {
		int8_t		ta,
		uint8_t		tx_power,
		OCT2		padding
	} with { variant "" };

	type record L1ctlDataReq {
		SacchL1Header	l1header optional,
		octetstring	l2_payload
	} with { variant "" };

	type record L1ctlH0 {
		uint8_t		h,
		GsmBandArfcn	arfcn,
		octetstring	padding length(130)
	} with { variant "" };

	type record length(0..64) of GsmBandArfcn L1ctlMA;
	type record L1ctlH1 {
		uint8_t		h,
		uint8_t		hsn,
		uint8_t		maio,
		uint8_t		n,
		OCT1		spare,
		L1ctlMA		ma,
		octetstring	padding
	} with {
		variant (n) "LENGTHTO(ma)"
		variant (n) "UNIT(elements)"
		/* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=562849.
		 * TL;DR The reference point of the PADDING attribute is the beginning
		 * of the message, not the beginning of the type/field it's applied on.
		 * Therefore we cannot use it here, and have to add padding manually.
		 * variant (ma) "PADDING(128)" */
	};

	type union L1ctlH0H1 {
		L1ctlH0		h0,
		L1ctlH1		h1
	} with { variant "TAG(h0, h = 0; h1, h = 1)" };

	type record L1ctlDmEstReq {
		GsmTsc		tsc,
		L1ctlH0H1	h0h1,
		L1ctlTchMode	tch_mode,
		L1ctlAudioMode	audio_mode
	} with { variant "" };

	type record L1ctlReset {
		L1ctlResetType	reset_type,
		OCT3		padding
	} with { variant "" };

	type record L1CtlCryptoReq {
		uint8_t		algo,
		uint8_t		key_len,
		octetstring	key
	} with { variant (key_len) "LENGTHTO(key)" };


	type record L1ctlTrafficReq {
		octetstring	data
	} with {
		variant (data) "BYTEORDER(first)"
	}

	type record length(8) of uint8_t TfiUsfArr;

	type record L1ctlTbfCfgReq {
		uint8_t		tbf_nr,
		boolean		is_uplink,
		OCT2		padding,
		TfiUsfArr	tfi_usf
	} with { variant (is_uplink) "FIELDLENGTH(8)" };

	type union L1ctlUlPayload {
		L1ctlFbsbReq		fbsb_req,
		L1ctlCcchModeReq	ccch_mode_req,
		L1ctlTchModeReq		tch_mode_req,
		L1ctlRachReq		rach_req,
		L1ctlExtRachReq		ext_rach_req,
		L1ctlParReq		par_req,
		L1ctlDmEstReq		dm_est_req,
		L1ctlReset		reset_req,
		//L1ctlNeighPmReq		neigh_pm_req,
		L1CtlCryptoReq		crypto_req,
		L1ctlTrafficReq		traffic_req,
		L1ctlTbfCfgReq		tbf_cfg_req,
		L1ctlDataReq		data_req,
		octetstring		other
	} with {
		variant (other) "BYTEORDER(first)"
	};

	type record L1ctlUlMessage {
		L1ctlHeader	header,
		L1ctlUlInfo	ul_info optional,
		L1ctlUlTbfInfo	ul_info_tbf optional,
		L1ctlUlAbsInfo	ul_info_abs optional,
		L1ctlUlPayload	payload
	} with { variant (ul_info) "PRESENCE(header.msg_type = L1CTL_RACH_REQ,
					     header.msg_type = L1CTL_EXT_RACH_REQ,
					     header.msg_type = L1CTL_PARAM_REQ,
					     header.msg_type = L1CTL_CRYPTO_REQ,
					     header.msg_type = L1CTL_DATA_REQ,
					     header.msg_type = L1CTL_DM_EST_REQ,
					     header.msg_type = L1CTL_DM_FREQ_REQ,
					     header.msg_type = L1CTL_DM_REL_REQ,
					     header.msg_type = L1CTL_TRAFFIC_REQ)"
		 variant (ul_info_tbf) "PRESENCE(header.msg_type = L1CTL_DATA_TBF_REQ)"
		 variant (ul_info_abs) "PRESENCE(header.msg_type = L1CTL_DATA_ABS_REQ)"
		 variant (payload) "CROSSTAG(fbsb_req, header.msg_type = L1CTL_FBSB_REQ;
					     ccch_mode_req, header.msg_type = L1CTL_CCCH_MODE_REQ;
					     tch_mode_req, header.msg_type = L1CTL_TCH_MODE_REQ;
					     rach_req, header.msg_type = L1CTL_RACH_REQ;
					     ext_rach_req, header.msg_type = L1CTL_EXT_RACH_REQ;
					     par_req, header.msg_type = L1CTL_PARAM_REQ;
					     dm_est_req, header.msg_type = L1CTL_DM_EST_REQ;
					     reset_req, header.msg_type = L1CTL_RESET_REQ;
					     crypto_req, header.msg_type = L1CTL_CRYPTO_REQ;
					     traffic_req, header.msg_type = L1CTL_TRAFFIC_REQ;
					     tbf_cfg_req, header.msg_type = L1CTL_TBF_CFG_REQ;
					     data_req, header.msg_type = L1CTL_DATA_REQ;
					     other, OTHERWISE;
				)" };

	external function enc_L1ctlUlMessage(in L1ctlUlMessage msg) return octetstring
		with { extension "prototype(convert) encode(RAW)" };
	external function dec_L1ctlUlMessage(in octetstring stream) return L1ctlUlMessage
		with { extension "prototype(convert) decode(RAW)" };

	type record L1ctlUlMessageLV {
		uint16_t	len,
		L1ctlUlMessage	msg
	} with { variant (len) "LENGTHTO(msg)" };

	external function enc_L1ctlUlMessageLV(in L1ctlUlMessageLV msg) return octetstring
		with { extension "prototype(convert) encode(RAW)" };
	external function dec_L1ctlUlMessageLV(in octetstring stream) return L1ctlUlMessageLV
		with { extension "prototype(convert) decode(RAW)" };

	type record L1ctlDlMessageLV {
		uint16_t	len,
		L1ctlDlMessage	msg
	} with { variant (len) "LENGTHTO(msg)" };

	external function enc_L1ctlDlMessageLV(in L1ctlDlMessageLV msg) return octetstring
		with { extension "prototype(convert) encode(RAW)" };
	external function dec_L1ctlDlMessageLV(in octetstring stream) return L1ctlDlMessageLV
		with { extension "prototype(convert) decode(RAW)" };




	/* for generating RESET_REQ */
	template (value) L1ctlUlMessage
	t_L1ctlResetReq(template (value) L1ctlResetType rst_type) := {
		header := ts_L1ctlHeader(L1CTL_RESET_REQ),
		ul_info := omit,
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			reset_req := {
				reset_type := rst_type,
				padding := '000000'O
			}
		}
	};

	/* for generating FBSB_REQ */
	template (value) L1ctlUlMessage
	ts_L1CTL_FBSB_REQ(template (value) GsmBandArfcn arfcn,
			  template (value) L1ctlFbsbFlags flags,
			  template (value) uint8_t sync_info_idx,
			  template (value) L1ctlCcchMode ccch_mode,
			  template (value) GsmRxLev rxlev_exp) := {
		header := ts_L1ctlHeader(L1CTL_FBSB_REQ),
		ul_info := omit,
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			fbsb_req := {
				arfcn := arfcn,
				timeout_tdma_frames := 250, /* about 1s */
				freq_err_thresh1 := 10000,
				freq_err_thresh2 := 800,
				num_freqerr_avg := 3,
				flags := flags,
				sync_info_idx := sync_info_idx,
				ccch_mode := ccch_mode,
				rxlev_exp := rxlev_exp
			}
		}
	};

	/* for matching against incoming FBSB_CONF */
	template L1ctlDlMessage
	tr_L1CTL_FBSB_CONF(template (present) uint8_t result) := {
		header := tr_L1ctlHeader(L1CTL_FBSB_CONF),
		dl_info := ?,
		payload := {
			fbsb_conf := {
				initial_freq_err := ?,
				result := result,
				bsic := ?
			}
		}
	};

	template (value) L1ctlUlMessage
	ts_L1CTL_CCCH_MODE_REQ(template (value) L1ctlCcchMode ccch_mode) := {
		header := ts_L1ctlHeader(L1CTL_CCCH_MODE_REQ),
		ul_info := omit,
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			ccch_mode_req := {
				ccch_mode := ccch_mode,
				padding := '000000'O
			}
		}
	};

	template (value) L1ctlUlMessage
	ts_L1CTL_TCH_MODE_REQ(template (value) L1ctlTchMode tch_mode := L1CTL_CHAN_MODE_SIGN,
			      template (value) L1ctlAudioMode audio_mode := t_L1CTL_AudioModeFwd,
			      template (value) L1ctlLoopMode loop_mode := L1CTL_LOOP_MODE_OPEN,
			      template (value) uint8_t amr_start_codec := 0,
			      template (value) BIT8 amr_codecs_bitmask := '00000000'B) := {
		header := ts_L1ctlHeader(L1CTL_TCH_MODE_REQ),
		ul_info := omit,
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			tch_mode_req := {
				tch_mode := tch_mode,
				audio_mode := audio_mode,
				loop_mode := loop_mode,
				amr := {
					start_codec := amr_start_codec,
					codecs_bitmask := amr_codecs_bitmask
				}
			}
		}
	};


	template L1ctlDlMessage
	tr_L1CTL_MsgType(template (present) L1ctlMsgType msg_type) := {
		header := tr_L1ctlHeader(msg_type),
		dl_info := *,
		payload := *
	}

	template L1ctlDlMessage tr_L1CTL_CCCH_MODE_CONF := tr_L1CTL_MsgType(L1CTL_CCCH_MODE_CONF);

	template (value) L1ctlUlMessage
	ts_L1CTL_RACH_REQ(template (value) uint8_t ra,
			  template (value) uint8_t combined,
			  template (value) uint16_t offset,
			  template (value) RslChannelNr chan_nr := ts_RslChanNr_RACH(0),
			  template (value) RslLinkId link_id := ts_RslLinkID_DCCH(0)) := {
		header := ts_L1ctlHeader(L1CTL_RACH_REQ),
		ul_info := {
			chan_nr := chan_nr,
			link_id := link_id,
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			rach_req := {
				ra := ra,
				combined := combined,
				offset := offset
			}
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_EXT_RACH_REQ(template (value) uint16_t ra11,
			      template (value) L1ctlRachSynchSeq seq,
			      template (value) uint8_t combined,
			      template (value) uint16_t offset) := {
		header := ts_L1ctlHeader(L1CTL_EXT_RACH_REQ),
		ul_info := {
			/* FIXME: both RSL chan_nr and link_id should be configurable */
			chan_nr := ts_RslChanNr_RACH(0),
			link_id := ts_RslLinkID_DCCH(0),
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			ext_rach_req := {
				ra11 := ra11,
				synch_seq := seq,
				combined := combined,
				offset := offset
			}
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_PAR_REQ(template (value) uint8_t ta,
			 template (value) uint8_t tx_power) := {
		header := ts_L1ctlHeader(L1CTL_PARAM_REQ),
		ul_info := {
			chan_nr := ts_RslChanNr_RACH(0),
			link_id := ts_RslLinkID_DCCH(0),
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			par_req := {
				ta := ta,
				tx_power := tx_power,
				padding := '0000'O
			}
		}
	}

	/* Base template to be inherited by ts_L1CTL_DM_EST_REQ_H0 and ts_L1CTL_DM_EST_REQ_H1 */
	private template (value) L1ctlUlMessage
	ts_L1CTL_DM_EST_REQ(template (value) RslChannelNr chan_nr,
			    template (value) GsmTsc tsc) := {
		header := ts_L1ctlHeader(L1CTL_DM_EST_REQ),
		ul_info := {
			chan_nr := chan_nr,
			link_id := ts_RslLinkID_DCCH(0),
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			dm_est_req := {
				tsc := tsc,
				tch_mode := L1CTL_CHAN_MODE_SIGN,
				audio_mode := t_L1CTL_AudioModeFwd
			}
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_DM_EST_REQ_H0(template (value) RslChannelNr chan_nr,
			       template (value) GsmTsc tsc,
			       template (value) GsmArfcn arfcn)
	modifies ts_L1CTL_DM_EST_REQ := {
		payload := {
			dm_est_req := {
				h0h1 := {
					h0 := {
						h := 0,
						arfcn := ts_GsmBandArfcn(arfcn),
						padding := f_pad_oct(''O, 130, '00'O)
					}
				}
			}
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_DM_EST_REQ_H1(template (value) RslChannelNr chan_nr,
			       template (value) GsmTsc tsc,
			       template (value) uint6_t hsn,
			       template (value) uint6_t maio,
			       template (value) L1ctlMA ma)
	modifies ts_L1CTL_DM_EST_REQ := {
		payload := {
			dm_est_req := {
				h0h1 := {
					h1 := {
						h := 1,
						hsn := hsn,
						maio := maio,
						n := sizeof(ma),
						spare := '00'O,
						ma := ma,
						/* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=562849 */
						padding := f_pad_oct(''O, (64 - sizeof(ma)) * 2, '00'O)
					}
				}
			}
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_DM_REL_REQ(template (value) RslChannelNr chan_nr) := {
		header := ts_L1ctlHeader(L1CTL_DM_REL_REQ),
		ul_info := {
			chan_nr := chan_nr,
			link_id := ts_RslLinkID_DCCH(0),
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			other := ''O
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_DATA_REQ(template (value) RslChannelNr chan_nr,
			  template (value) RslLinkId link_id,
			  octetstring l2_data) := {
		header := ts_L1ctlHeader(L1CTL_DATA_REQ),
		ul_info := {
			chan_nr := chan_nr,
			link_id := link_id,
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			data_req := {
				l1header := omit,
				l2_payload := l2_data
			}
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_DATA_REQ_SACCH(template (value) RslChannelNr chan_nr,
				template (value) RslLinkId link_id,
				template (value) SacchL1Header l1h,
				octetstring l2_data) := {
		header := ts_L1ctlHeader(L1CTL_DATA_REQ),
		ul_info := {
			chan_nr := chan_nr,
			link_id := link_id,
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			data_req := {
				l1header := l1h,
				l2_payload := l2_data
			}
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_TRAFFIC_REQ(template (value) RslChannelNr chan_nr,
			     template (value) RslLinkId link_id,
			     octetstring frame) := {
		header := ts_L1ctlHeader(L1CTL_TRAFFIC_REQ),
		ul_info := {
			chan_nr := chan_nr,
			link_id := link_id,
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			traffic_req := {
				data := frame
			}
		}
	};

	template (value) L1ctlUlMessage
	ts_L1CTL_TBF_CFG_REQ(template (value) boolean is_uplink,
			     template (value) TfiUsfArr tfi_usf) := {
		header := ts_L1ctlHeader(L1CTL_TBF_CFG_REQ),
		ul_info := omit,
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			tbf_cfg_req := {
				tbf_nr := 0,
				is_uplink := is_uplink,
				padding := '0000'O,
				tfi_usf := tfi_usf
			}
		}
	};

	template L1ctlDlMessage
	tr_L1CTL_TBF_CFG_CONF(template (present) boolean is_uplink) := {
		header := tr_L1ctlHeader(L1CTL_TBF_CFG_CONF),
		dl_info := omit,
		payload := {
			tbf_cfg_conf := {
				tbf_nr := 0,
				is_uplink := is_uplink,
				padding := ?,
				tfi_usf := ?
			}
		}
	};

	template (value) L1ctlUlMessage
	ts_L1CTL_DATA_TBF_REQ(octetstring l2_data,
			      template (value) L1ctlGprsCs cs := L1CTL_CS1,
			      template (value) uint8_t tbf_nr := 0) := {
		header := ts_L1ctlHeader(L1CTL_DATA_TBF_REQ),
		ul_info := omit,
		ul_info_tbf := {
			tbf_nr := tbf_nr,
			cs := cs,
			padding := '0000'O
		},
		ul_info_abs := omit,
		payload := {
			other := l2_data
		}
	}

	template (value) L1ctlUlMessage
	ts_L1CTL_DATA_ABS_REQ(octetstring l2_data,
			      template (value) GsmBandArfcn arfcn,
			      template (value) uint8_t ts,
			      template (value) GsmFrameNumber fn,
			      template (value) L1ctlGprsCs cs := L1CTL_CS1,
			      template (value) uint8_t tbf_nr := 0) := {
		header := ts_L1ctlHeader(L1CTL_DATA_ABS_REQ),
		ul_info := omit,
		ul_info_tbf := omit,
		ul_info_abs := {
			tbf_nr := tbf_nr,
			cs := cs,
			ts_nr := ts,
			padding := '00'O,
			fn := fn,
			arfcn := arfcn,
			padding2 := '0000'O
		},
		payload := {
			other := l2_data
		}
	}


	/* for matching against incoming RACH_CONF */
	template L1ctlDlMessage tr_L1CTL_RACH_CONF := {
		header := tr_L1ctlHeader(L1CTL_RACH_CONF),
		dl_info := ?,
		payload := *
	};

	/* for matching against incoming DATA_IND */
	template L1ctlDlMessage
	tr_L1CTL_DATA_IND(template (present) RslChannelNr chan_nr,
			  template (present) RslLinkId link_id := ?,
			  template (present) octetstring l2_data := ?,
			  template (present) uint8_t num_biterr := 0,
			  template (present) uint8_t fire_crc := 0) := {
		header := tr_L1ctlHeader(L1CTL_DATA_IND),
		dl_info := {
			chan_nr := chan_nr,
			link_id := link_id,
			arfcn := ?,
			frame_nr := ?,
			rx_level := ?,
			snr := ?,
			num_biterr := num_biterr,
			fire_crc := fire_crc
		},
		payload := {
			data_ind := {
				payload := l2_data
			}
		}
	};

	/* for matching against incoming TRAFFIC_IND */
	template L1ctlDlMessage
	tr_L1CTL_TRAFFIC_IND(template (present) RslChannelNr chan_nr,
			     template (present) RslLinkId link_id := ?,
			     template (present) octetstring frame := ?,
			     template (present) uint8_t num_biterr := ?,
			     template (present) uint8_t fire_crc := ?) := {
		header := tr_L1ctlHeader(L1CTL_TRAFFIC_IND),
		dl_info := {
			chan_nr := chan_nr,
			link_id := link_id,
			arfcn := ?,
			frame_nr := ?,
			rx_level := ?,
			snr := ?,
			num_biterr := num_biterr,
			fire_crc := fire_crc
		},
		payload := {
			traffic_ind := {
				data := frame
			}
		}
	};

	template (value) L1ctlUlMessage
	ts_L1CTL_CRYPTO_REQ(template (value) RslChannelNr chan_nr,
			    template (value) uint8_t algo,
			    template (value) octetstring key) := {
		header := ts_L1ctlHeader(L1CTL_CRYPTO_REQ),
		ul_info := {
			chan_nr := chan_nr,
			link_id := ts_RslLinkID_DCCH(0),
			padding := '0000'O
		},
		ul_info_tbf := omit,
		ul_info_abs := omit,
		payload := {
			crypto_req := {
				algo := algo,
				key_len := 0, /* overwritten */
				key := key
			}
		}
	};

	const octetstring c_DummyUI := '0303012B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B'O;

/* We use "BYTEORDER(last)" so we get little-endian integers.  Unfortuantely, this also
   switches the byte ordering in octet strings, so we need to explicitly annotate them :/ */
} with { encode "RAW" };
