bts: Add testscase & infra to validate Osmux support BTS<->BSC

Related: SYS#5987
Change-Id: I1af23c7a60b05edc3b544f1fea0023f48e89f7a7
diff --git a/bts/BTS_Tests.ttcn b/bts/BTS_Tests.ttcn
index 68e5cf1..e71aed9 100644
--- a/bts/BTS_Tests.ttcn
+++ b/bts/BTS_Tests.ttcn
@@ -49,6 +49,11 @@
 
 import from AMR_Types all;
 
+import from OSMUX_Types all;
+import from OSMUX_CodecPort all;
+import from OSMUX_CodecPort_CtrlFunct all;
+import from OSMUX_Emulation all;
+
 import from IPL4asp_Types all;
 import from TRXC_Types all;
 import from TRXC_CodecPort all;
@@ -99,6 +104,8 @@
 	integer mp_bsc_ctrl_port := 4249;
 	charstring mp_rtpem_bind_ip := "127.0.0.1";
 	integer mp_rtpem_bind_port := 6766;
+	charstring mp_osmuxem_bind_ip := "127.0.0.1";
+	integer mp_osmuxem_bind_port := 1984;
 	integer mp_tolerance_rxqual := 1;
 	integer mp_tolerance_rxlev := 3;
 	integer mp_tolerance_timing_offset_256syms := 0;
@@ -219,6 +226,9 @@
 	var RTP_Emulation_CT vc_RTPEM;
 	port RTPEM_CTRL_PT RTPEM_CTRL;
 	port RTPEM_DATA_PT RTPEM_DATA;
+	var OSMUX_Emulation_CT vc_OsmuxEM;
+	port OsmuxEM_CTRL_PT OsmuxEM_CTRL;
+	port OsmuxEM_DATA_PT OsmuxEM_DATA;
 }
 
 private function f_init_rsl(charstring id) runs on test_CT {
@@ -319,7 +329,9 @@
 	/* Training Sequence Code */
 	GsmTsc tsc,
 	/* Frequency hopping parameters */
-	FreqHopPars fhp
+	FreqHopPars fhp,
+	OsmuxCID loc_osmux_cid,
+	OsmuxCID rem_osmux_cid optional
 };
 
 /* Test-specific parameters */
@@ -907,7 +919,9 @@
 		maio_hsn := ts_HsnMaio(0, 0),
 		ma_map := c_MA_null,
 		ma := { }
-	}
+	},
+	loc_osmux_cid := trx_nr,
+	rem_osmux_cid := omit
 }
 
 /* This altstep triggers on receipt of a L1CTL DATA.ind matching the given
@@ -2622,7 +2636,7 @@
 		/* FIXME (OS#5242): do not include Remote IP/Port IEs because
 		 * osmo-bts would respond with nonsense listen addr='0.0.0.0'. */
 		ts_RSL_IPA_CRCX(g_chan_nr, omit, omit),
-		tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?),
+		tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?, omit),
 		"IPA CRCX ACK");
 	var uint16_t conn_id := crcx_ack.ies[1].body.ipa_conn_id;
 
@@ -2644,6 +2658,71 @@
 	f_rtpem_mode(RTPEM_CTRL, mode);
 }
 
+/* Initialize and start the RTP emulation component for a ConnHdlr */
+friend function f_osmuxem_activate(inout octetstring payload,
+				 OsmuxemConfig cfg := c_OsmuxemDefaultCfg,
+				 OsmuxemMode mode := OSMUXEM_MODE_BIDIR)
+runs on ConnHdlr {
+	var RSL_IE_Body ie;
+	var OsmuxTxHandle tx_hdl;
+	var OsmuxRxHandle rx_hdl;
+	/* Step 0: initialize, connect and start the emulation component */
+	vc_OsmuxEM := OSMUX_Emulation_CT.create(testcasename() & "-OsmuxEM");
+	map(vc_OsmuxEM:OSMUX, system:OSMUX);
+	connect(vc_OsmuxEM:CTRL, self:OsmuxEM_CTRL);
+	connect(vc_OsmuxEM:DATA, self:OsmuxEM_DATA);
+	vc_OsmuxEM.start(OSMUX_Emulation.f_main());
+
+	/* Step 1: configure the RTP parameters */
+	var integer payload_len := 31;
+	var octetstring hdr := ''O;
+
+	/* Pad the payload to conform the expected length */
+	payload := f_pad_oct(hdr & payload, payload_len, '00'O);
+	cfg.tx_fixed_payload := payload;
+	f_osmuxem_configure(OsmuxEM_CTRL, cfg);
+
+	/* Step 2: bind the RTP emulation to the configured address */
+	var PortNumber osmuxem_bind_port := mp_osmuxem_bind_port;
+	f_osmuxem_bind(OsmuxEM_CTRL, mp_osmuxem_bind_ip, osmuxem_bind_port);
+	rx_hdl := c_OsmuxemDefaultRxHandle;
+	rx_hdl.cid := g_pars.loc_osmux_cid;
+	f_osmuxem_register_rxhandle(OsmuxEM_CTRL, rx_hdl);
+
+	/* Step 3a: send CRCX to create an RTP connection at the IUT */
+	var RSL_Message crcx_ack := f_rsl_transceive_ret(
+		/* FIXME (OS#5242): do not include Remote IP/Port IEs because
+		 * osmo-bts would respond with nonsense listen addr='0.0.0.0'. */
+		ts_RSL_IPA_CRCX(g_chan_nr, omit, omit, g_pars.loc_osmux_cid),
+		tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?, ?),
+		"IPA CRCX ACK");
+	var uint16_t conn_id := crcx_ack.ies[1].body.ipa_conn_id;
+	f_rsl_find_ie(crcx_ack, RSL_IE_OSMO_OSMUX_CID, ie);
+	g_pars.rem_osmux_cid := ie.osmux_cid.cid;
+
+
+	/* Step 3b: send MDCX with the configured address/port to the IUT */
+	var RSL_Message mdcx_ack := f_rsl_transceive_ret(
+		ts_RSL_IPA_MDCX(g_chan_nr, conn_id,
+				remote_ip := f_inet_addr(mp_osmuxem_bind_ip),
+				remote_port := osmuxem_bind_port,
+				rtp_pt2 := 0,
+				osmux_cid := g_pars.loc_osmux_cid),
+		tr_RSL_IPA_MDCX_ACK(g_chan_nr, conn_id, ?, ?, ?, g_pars.rem_osmux_cid),
+		"IPA MDCX ACK");
+
+	tx_hdl := valueof(t_TxHandleAMR590(g_pars.rem_osmux_cid));
+	f_osmuxem_register_txhandle(OsmuxEM_CTRL, tx_hdl);
+
+	/* Step 4: connect to the IUT's address/port parsed from MDCX ACK */
+	var HostName bts_bind_ip := f_inet_ntoa(mdcx_ack.ies[2].body.ipa_local_ip);
+	var PortNumber bts_bind_port := mdcx_ack.ies[3].body.ipa_local_port;
+	f_osmuxem_connect(OsmuxEM_CTRL, bts_bind_ip, bts_bind_port);
+
+	/* Step 5: set the given RTP emulation mode */
+	f_osmuxem_mode(OsmuxEM_CTRL, mode);
+}
+
 /* establish DChan, verify existance + contents of measurement reports */
 private function f_TC_meas_res_periodic(charstring id) runs on ConnHdlr {
 	f_l1_tune(L1CTL);
@@ -8211,6 +8290,120 @@
 	Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
 }
 
+
+/* Verify handling of Downlink and Uplink Osmux speech frames */
+private function f_TC_speech_osmux(charstring id) runs on ConnHdlr {
+	var L1ctlDlMessage l1_dl;
+	var OSMUX_PDU osmux_pdu;
+	var octetstring pl;
+	var octetstring exp_rtp_pl;
+	timer Td, Tu;
+
+	f_l1_tune(L1CTL);
+	f_est_dchan();
+
+	/* Activate the RTP emulation */
+	pl := f_rnd_octstring(6);
+	f_osmuxem_activate(pl);
+
+	/* Give the scheduler some time to fill up the buffers */
+	f_sleep(2.0);
+	L1CTL.clear;
+	RSL.clear;
+
+	/* we transmit using AMR_FT_2 (5.90), see t_TxHandleAMR590 in f_osmuxem_activate() */
+	var integer amr_ft := get_start_amr_ft();
+	var integer amr_pl_len := f_amrft_payload_len(amr_ft);
+	var octetstring hdr := enc_RTP_AMR_Hdr(valueof(ts_RTP_AMR_Hdr(amr_ft, amr_ft, '1'B)));
+	pl := f_osmux_gen_expected_rx_rtp_payload(amr_ft, pl);
+	exp_rtp_pl := hdr & pl;
+
+	/* Make sure that Downlink frames are received at the UE */
+	Td.start(2.0);
+	alt {
+	[] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr, frame := exp_rtp_pl)) -> value l1_dl {
+		log("TCH received: ", l1_dl.payload.traffic_ind.data);
+		L1CTL.send(ts_L1CTL_TRAFFIC_REQ(g_chan_nr, l1_dl.dl_info.link_id,
+			   l1_dl.payload.traffic_ind.data));
+		setverdict(pass);
+		}
+	[] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr, frame := ?)) -> value l1_dl {
+		setverdict(fail, "Rx unexpected Downlink speech frame ",
+			   "(", l1_dl.payload.traffic_ind.data, ") ",
+			   "expected (", exp_rtp_pl, ")");
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+		}
+	[] as_l1_sacch();
+	[] L1CTL.receive { repeat; }
+	[] Td.timeout {
+		setverdict(fail, "Timeout waiting for Downlink speech frames");
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+		}
+	}
+
+	/* Make sure that Uplink frames are received at the BTS */
+	OsmuxEM_DATA.clear;
+	var template (present) OSMUX_PDU osmux_pdu_exp := tr_PDU_Osmux_AMR(cid := g_pars.loc_osmux_cid,
+									   amr_ft := amr_ft,
+									   amr_cmr := amr_ft);
+	Tu.start(2.0);
+	alt {
+	[] OsmuxEM_DATA.receive(osmux_pdu_exp) -> value osmux_pdu {
+		var boolean matched := false;
+		for (var integer i := 0; i < osmux_pdu.osmux_amr.header.ctr + 1; i := i + 1) {
+			var octetstring rx_pl;
+			rx_pl := f_osmux_amr_get_nth_amr_payload(osmux_pdu.osmux_amr, i);
+			log("got ", rx_pl, " vs exp ", pl);
+			if (rx_pl == pl) {
+				matched := true;
+				break;
+			}
+		}
+		if (not matched) {
+			repeat;
+		}
+		}
+	[] OsmuxEM_DATA.receive { repeat; }
+	[] Tu.timeout {
+		setverdict(fail, "Timeout waiting for Uplink speech frames");
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+		}
+	}
+
+	f_osmuxem_mode(OsmuxEM_CTRL, OSMUXEM_MODE_NONE);
+	f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr);
+	f_rsl_chan_deact();
+	f_rslem_unregister(0, g_chan_nr);
+}
+testcase TC_speech_osmux_tchf() runs on test_CT {
+	var ConnHdlr vc_conn;
+	var ConnHdlrPars pars;
+
+	f_init();
+
+	/* TS5, TCH/H0, V3 (AMR codec) */
+	pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM3)));
+	pars.mr_conf := valueof(ts_RSL_MultirateCfg(false, 0, '00000100'B /* 5,90k */));
+	vc_conn := f_start_handler(refers(f_TC_speech_osmux), pars);
+	vc_conn.done;
+
+	Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+}
+testcase TC_speech_osmux_tchh() runs on test_CT {
+	var ConnHdlr vc_conn;
+	var ConnHdlrPars pars;
+
+	f_init();
+
+	/* TS5, TCH/H0, V3 (AMR codec) */
+	pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM3)));
+	pars.mr_conf := valueof(ts_RSL_MultirateCfg(false, 0, '00000100'B /* 5,90k */));
+	vc_conn := f_start_handler(refers(f_TC_speech_osmux), pars);
+	vc_conn.done;
+
+	Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+}
+
 private function f_TC_early_immediate_assignment(charstring id) runs on ConnHdlr {
 	var GsmFrameNumber fn;
 	var ChannelDescription ch_desc;
@@ -8808,6 +9001,8 @@
 	execute( TC_speech_no_rtp_tchh() );
 	execute( TC_speech_rtp_tchf() );
 	execute( TC_speech_rtp_tchh() );
+	execute( TC_speech_osmux_tchf() );
+	execute( TC_speech_osmux_tchh() );
 
 	execute( TC_early_immediate_assignment() );
 
diff --git a/bts/gen_links.sh b/bts/gen_links.sh
index b7ba2cc..c9eb786 100755
--- a/bts/gen_links.sh
+++ b/bts/gen_links.sh
@@ -47,6 +47,7 @@
 FILES+="AMR_Types.ttcn "
 FILES+="RTP_CodecPort.ttcn RTP_Emulation.ttcn IuUP_Types.ttcn IuUP_Emulation.ttcn IuUP_EncDec.cc "
 FILES+="RTP_CodecPort_CtrlFunct.ttcn RTP_CodecPort_CtrlFunctDef.cc "
+FILES+="OSMUX_CodecPort.ttcn OSMUX_Emulation.ttcn OSMUX_Types.ttcn OSMUX_CodecPort_CtrlFunct.ttcn OSMUX_CodecPort_CtrlFunctDef.cc "
 FILES+="PCUIF_Types.ttcn PCUIF_CodecPort.ttcn "
 FILES+="IPA_Testing.ttcn"
 gen_links $DIR $FILES
diff --git a/bts/osmo-bts.cfg b/bts/osmo-bts.cfg
index f581be0..2eb84ff 100644
--- a/bts/osmo-bts.cfg
+++ b/bts/osmo-bts.cfg
@@ -24,7 +24,9 @@
  logging level dsp info
  logging level pcu debug
  logging level trx info
+ logging level osmux info
  logging level lmib debug
+ logging level lmux info
 !
 line vty
  no login
@@ -60,6 +62,10 @@
  min-qual-norm -5
  !settsc
  pcu-socket /tmp/pcu_sock
+ osmux
+  use on
+  local-ip 127.0.0.1
+  local-port 1984
  trx 0
   power-ramp max-initial 0 mdBm
   power-ramp step-size 8000 mdB
diff --git a/bts/regen_makefile.sh b/bts/regen_makefile.sh
index 147d64f..054b750 100755
--- a/bts/regen_makefile.sh
+++ b/bts/regen_makefile.sh
@@ -11,6 +11,7 @@
 	IuUP_EncDec.cc
 	L1CTL_PortType_CtrlFunctDef.cc
 	Native_FunctionDefs.cc
+	OSMUX_CodecPort_CtrlFunctDef.cc
 	RLCMAC_EncDec.cc
 	RTP_CodecPort_CtrlFunctDef.cc
 	RTP_EncDec.cc
diff --git a/library/OSMUX_Types.ttcn b/library/OSMUX_Types.ttcn
index 8ff7451..bc592b7 100644
--- a/library/OSMUX_Types.ttcn
+++ b/library/OSMUX_Types.ttcn
@@ -11,6 +11,9 @@
 module OSMUX_Types {
 
 import from General_Types all;
+import from Misc_Helpers all;
+
+import from AMR_Types all;
 
 external function enc_OSMUX_PDU ( in OSMUX_PDU msg ) return octetstring
         with { extension "prototype(convert) encode(RAW)" };
@@ -83,4 +86,44 @@
   )"
 };
 
+template (present) OSMUX_PDU tr_PDU_Osmux_AMR(template (present) BIT1 marker := ?,
+						  template (present) INT3b ctr := ?,
+						  template (present) BIT1 amr_f := ?,
+						  template (present) BIT1 amr_q := ?,
+						  template (present) INT1 seq := ?,
+						  template (present) OsmuxCID cid := ?,
+						  template (present) INT4b amr_ft := ?,
+						  template (present) INT4b amr_cmr := ?,
+						  template (present) octetstring payload := ?) := {
+	osmux_amr := {
+		header := {
+			marker := marker,
+			ft := 1,
+			ctr := ctr,
+			amr_f := amr_f,
+			amr_q := amr_q,
+			seq := seq,
+			cid := cid,
+			amr_ft := amr_ft,
+			amr_cmr := amr_cmr
+		},
+		data := payload
+	}
+}
+
+/* Get Nth AMR payload of osmux AMR frame (starting from 0) */
+function f_osmux_amr_get_nth_amr_payload(PDU_Osmux_AMR osmux_amr, integer nth) return octetstring
+{
+	var integer amr_pl_len := f_amrft_payload_len(osmux_amr.header.amr_ft);
+	if (nth > osmux_amr.header.ctr) {
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "nth > ctr");
+		return ''O;
+	}
+	if (amr_pl_len * (nth+1) > lengthof(osmux_amr.data)) {
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "osmux payload too short");
+	}
+	var octetstring pl := substr(osmux_amr.data, amr_pl_len * nth, amr_pl_len);
+	return pl;
+}
+
 } with { encode "RAW"}
diff --git a/library/RSL_Types.ttcn b/library/RSL_Types.ttcn
index 3acd619..a86be86 100644
--- a/library/RSL_Types.ttcn
+++ b/library/RSL_Types.ttcn
@@ -973,6 +973,14 @@
 		len := ?, /* overwritten */
 		cid := osmux_cid
 	};
+	function f_tr_RSL_IE_OSMO_Osmux_CID(template uint8_t osmux_cid := *)
+	return template RSL_IE_OSMO_Osmux_CID {
+		var template RSL_IE_OSMO_Osmux_CID ie := omit;
+		if (not istemplatekind(osmux_cid, "omit")) {
+			ie := tr_RSL_IE_OSMO_Osmux_CID(osmux_cid);
+		}
+		return ie;
+	}
 	template (value) RSL_IE_OSMO_Osmux_CID
 	ts_RSL_IE_OSMO_Osmux_CID(template (value) uint8_t osmux_cid) := {
 		len := 0, /* overwritten */
@@ -2157,7 +2165,8 @@
 
 	private function f_ts_RSL_IPA_CRCX_IEs(template (value) RslChannelNr chan_nr,
 					       template (omit) OCT4 remote_ip,
-					       template (omit) uint16_t remote_port)
+					       template (omit) uint16_t remote_port,
+					       template (omit) uint8_t osmux_cid)
 	return RSL_IE_List {
 		var RSL_IE_List ies;
 
@@ -2185,6 +2194,15 @@
 				})
 			};
 		}
+		/* Osmux CID extension IE is optional: */
+		if (not istemplatekind(osmux_cid, "omit")) {
+			ies := ies & {
+				valueof(RSL_IE:{
+					iei := RSL_IE_OSMO_OSMUX_CID,
+					body := { osmux_cid := ts_RSL_IE_OSMO_Osmux_CID(osmux_cid) }
+				})
+			};
+		}
 
 		return ies;
 	}
@@ -2199,10 +2217,11 @@
 	template (value) RSL_Message
 	ts_RSL_IPA_CRCX(template (value) RslChannelNr chan_nr,
 			template (omit) OCT4 remote_ip := omit,
-			template (omit) uint16_t remote_port := omit) := {
+			template (omit) uint16_t remote_port := omit,
+			template (omit) uint8_t osmux_cid := omit) := {
 		msg_disc := ts_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
 		msg_type := RSL_MT_IPAC_CRCX,
-		ies := f_ts_RSL_IPA_CRCX_IEs(chan_nr, remote_ip, remote_port)
+		ies := f_ts_RSL_IPA_CRCX_IEs(chan_nr, remote_ip, remote_port, osmux_cid)
 	}
 
 	function ts_RSL_IPA_CRCX_ACK(template (value) RslChannelNr chan_nr,
@@ -2227,19 +2246,27 @@
 		return msg;
 	}
 
-	template RSL_Message tr_RSL_IPA_CRCX_ACK(template RslChannelNr chan_nr,
-						 template uint16_t ipa_conn_id,
-						 template OCT4 local_ip,
-						 template uint16_t local_port) := {
-		msg_disc := tr_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
-		msg_type := RSL_MT_IPAC_CRCX_ACK,
-		ies := {
-			tr_RSL_IE(RSL_IE_Body:{chan_nr := chan_nr}),
-			tr_RSL_IE(RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
-			tr_RSL_IE(RSL_IE_Body:{ipa_local_ip := local_ip}),
-			tr_RSL_IE(RSL_IE_Body:{ipa_local_port := local_port})
-			/* Optional: RTP Payload Type 2 IE */
+	function tr_RSL_IPA_CRCX_ACK(template RslChannelNr chan_nr,
+				     template uint16_t ipa_conn_id,
+				     template OCT4 local_ip,
+				     template uint16_t local_port,
+				     template uint8_t osmux_cid := omit)
+	return template RSL_Message {
+		var template RSL_Message msg := {
+			msg_disc := ts_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
+			msg_type := RSL_MT_IPAC_CRCX_ACK,
+			ies := {
+				tr_RSL_IE(RSL_IE_Body:{chan_nr := chan_nr}),
+				tr_RSL_IE(RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
+				tr_RSL_IE(RSL_IE_Body:{ipa_local_ip := local_ip}),
+				tr_RSL_IE(RSL_IE_Body:{ipa_local_port := local_port})
+				/* Optional: RTP Payload Type 2 IE */
+			}
 		}
+		if (not istemplatekind(osmux_cid, "omit")) {
+			msg.ies[lengthof(msg.ies)] := tr_RSL_IE(RSL_IE_Body:{osmux_cid := f_tr_RSL_IE_OSMO_Osmux_CID(osmux_cid)});
+		}
+		return msg;
 	}
 
 	template (value) RSL_Message ts_RSL_IPA_CRCX_NACK(template (value) RslChannelNr chan_nr,
@@ -2261,20 +2288,28 @@
 		}
 	}
 
-	template (value) RSL_Message ts_RSL_IPA_MDCX(template (value) RslChannelNr chan_nr,
-						     uint16_t ipa_conn_id,
-						     OCT4 remote_ip, uint16_t remote_port,
-						     uint7_t rtp_pt2) := {
-		msg_disc := ts_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
-		msg_type := RSL_MT_IPAC_MDCX,
-		ies := {
-			t_RSL_IE(RSL_IE_CHAN_NR, RSL_IE_Body:{chan_nr := chan_nr}),
-			t_RSL_IE(RSL_IE_IPAC_CONN_ID, RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
-			t_RSL_IE(RSL_IE_IPAC_REMOTE_IP, RSL_IE_Body:{ipa_remote_ip := remote_ip}),
-			t_RSL_IE(RSL_IE_IPAC_REMOTE_PORT, RSL_IE_Body:{ipa_remote_port := remote_port}),
-			/* optional: RTP Payload Type */
-			t_RSL_IE(RSL_IE_IPAC_RTP_PAYLOAD2, RSL_IE_Body:{ipa_rtp_pt2 := rtp_pt2})
+	function ts_RSL_IPA_MDCX(template (value) RslChannelNr chan_nr,
+				 uint16_t ipa_conn_id,
+				 OCT4 remote_ip, uint16_t remote_port,
+				 uint7_t rtp_pt2,
+				 template (omit) uint8_t osmux_cid := omit)
+	return template (value) RSL_Message {
+		var template (value) RSL_Message msg := {
+			msg_disc := ts_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
+			msg_type := RSL_MT_IPAC_MDCX,
+			ies := {
+				t_RSL_IE(RSL_IE_CHAN_NR, RSL_IE_Body:{chan_nr := chan_nr}),
+				t_RSL_IE(RSL_IE_IPAC_CONN_ID, RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
+				t_RSL_IE(RSL_IE_IPAC_REMOTE_IP, RSL_IE_Body:{ipa_remote_ip := remote_ip}),
+				t_RSL_IE(RSL_IE_IPAC_REMOTE_PORT, RSL_IE_Body:{ipa_remote_port := remote_port}),
+				/* optional: RTP Payload Type */
+				t_RSL_IE(RSL_IE_IPAC_RTP_PAYLOAD2, RSL_IE_Body:{ipa_rtp_pt2 := rtp_pt2})
+			}
 		}
+		if (not istemplatekind(osmux_cid, "omit")) {
+			msg.ies[lengthof(msg.ies)] := t_RSL_IE(RSL_IE_OSMO_OSMUX_CID, RSL_IE_Body:{osmux_cid := ts_RSL_IE_OSMO_Osmux_CID(osmux_cid)});
+		}
+		return msg;
 	}
 	template RSL_Message tr_RSL_IPA_MDCX(template RslChannelNr chan_nr,
 					     template uint16_t ipa_conn_id) := {
@@ -2292,41 +2327,49 @@
 				     OCT4 local_ip, uint16_t local_port,
 				     uint7_t rtp_pt2,
 				     template (omit) uint8_t osmux_cid := omit)
-		return template (value) RSL_Message {
-			var template (value) RSL_Message msg := {
-				msg_disc := ts_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
-				msg_type := RSL_MT_IPAC_MDCX_ACK,
-				ies := {
-					t_RSL_IE(RSL_IE_CHAN_NR, RSL_IE_Body:{chan_nr := chan_nr}),
-					/* optional */
-					t_RSL_IE(RSL_IE_IPAC_CONN_ID, RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
-					t_RSL_IE(RSL_IE_IPAC_LOCAL_IP, RSL_IE_Body:{ipa_local_ip := local_ip}),
-					t_RSL_IE(RSL_IE_IPAC_LOCAL_PORT, RSL_IE_Body:{ipa_local_port := local_port}),
-					/* optional: RTP Payload Type */
-					t_RSL_IE(RSL_IE_IPAC_RTP_PAYLOAD2, RSL_IE_Body:{ipa_rtp_pt2 := rtp_pt2})
-				}
+	return template (value) RSL_Message {
+		var template (value) RSL_Message msg := {
+			msg_disc := ts_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
+			msg_type := RSL_MT_IPAC_MDCX_ACK,
+			ies := {
+				t_RSL_IE(RSL_IE_CHAN_NR, RSL_IE_Body:{chan_nr := chan_nr}),
+				/* optional */
+				t_RSL_IE(RSL_IE_IPAC_CONN_ID, RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
+				t_RSL_IE(RSL_IE_IPAC_LOCAL_IP, RSL_IE_Body:{ipa_local_ip := local_ip}),
+				t_RSL_IE(RSL_IE_IPAC_LOCAL_PORT, RSL_IE_Body:{ipa_local_port := local_port}),
+				/* optional: RTP Payload Type */
+				t_RSL_IE(RSL_IE_IPAC_RTP_PAYLOAD2, RSL_IE_Body:{ipa_rtp_pt2 := rtp_pt2})
 			}
-			if (not istemplatekind(osmux_cid, "omit")) {
-				msg.ies[lengthof(msg.ies)] := t_RSL_IE(RSL_IE_OSMO_OSMUX_CID, RSL_IE_Body:{osmux_cid := ts_RSL_IE_OSMO_Osmux_CID(osmux_cid)});
+		}
+		if (not istemplatekind(osmux_cid, "omit")) {
+			msg.ies[lengthof(msg.ies)] := t_RSL_IE(RSL_IE_OSMO_OSMUX_CID, RSL_IE_Body:{osmux_cid := ts_RSL_IE_OSMO_Osmux_CID(osmux_cid)});
+		}
+		return msg;
+	}
+	function tr_RSL_IPA_MDCX_ACK(template RslChannelNr chan_nr,
+				     template uint16_t ipa_conn_id,
+				     template OCT4 local_ip,
+				     template uint16_t local_port,
+				     template uint7_t rtp_pt2,
+				     template uint8_t osmux_cid := omit)
+	return template RSL_Message {
+		var template RSL_Message msg := {
+			msg_disc := ts_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
+			msg_type := RSL_MT_IPAC_MDCX_ACK,
+			ies := {
+				tr_RSL_IE(RSL_IE_Body:{chan_nr := chan_nr}),
+				/* optional */
+				tr_RSL_IE(RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
+				tr_RSL_IE(RSL_IE_Body:{ipa_local_ip := local_ip}),
+				tr_RSL_IE(RSL_IE_Body:{ipa_local_port := local_port}),
+				/* optional: RTP Payload Type */
+				tr_RSL_IE(RSL_IE_Body:{ipa_rtp_pt2 := rtp_pt2})
 			}
-			return msg;
 		}
-	template RSL_Message tr_RSL_IPA_MDCX_ACK(template RslChannelNr chan_nr,
-						 template uint16_t ipa_conn_id,
-						 template OCT4 local_ip,
-						 template uint16_t local_port,
-						 template uint7_t rtp_pt2) := {
-		msg_disc := tr_RSL_MsgDisc(RSL_MDISC_IPACCESS, false),
-		msg_type := RSL_MT_IPAC_MDCX_ACK,
-		ies := {
-			tr_RSL_IE(RSL_IE_Body:{chan_nr := chan_nr}),
-			/* optional */
-			tr_RSL_IE(RSL_IE_Body:{ipa_conn_id := ipa_conn_id}),
-			tr_RSL_IE(RSL_IE_Body:{ipa_local_ip := local_ip}),
-			tr_RSL_IE(RSL_IE_Body:{ipa_local_port := local_port}),
-			/* optional: RTP Payload Type */
-			tr_RSL_IE(RSL_IE_Body:{ipa_rtp_pt2 := rtp_pt2})
+		if (not istemplatekind(osmux_cid, "omit")) {
+			msg.ies[lengthof(msg.ies)] := tr_RSL_IE(RSL_IE_Body:{osmux_cid := f_tr_RSL_IE_OSMO_Osmux_CID(osmux_cid)});
 		}
+		return msg;
 	}
 
 	template (value) RSL_Message ts_RSL_IPA_MDCX_NACK(template (value) RslChannelNr chan_nr,