hnbgw: test SCCP CR without payload

Various tweaks are required to allow RAN_Emulation to handle, and also
to expect, an SCCP CR that has no payload data.

Related: SYS#5968
Related: If0c5c0a76e5230bf22871f527dcb2dbdf34d7328 (osmo-hnbgw)
Change-Id: I827e081eaacfb8e76684ed1560603e6c8f896c38
diff --git a/hnbgw/HNBGW_Tests.ttcn b/hnbgw/HNBGW_Tests.ttcn
index b2c491e..1b1d709 100644
--- a/hnbgw/HNBGW_Tests.ttcn
+++ b/hnbgw/HNBGW_Tests.ttcn
@@ -167,7 +167,8 @@
 	hexstring imsi,
 	boolean ps_domain,
 	MgcpParameters mgcp_pars optional,
-	HnbConfig hnb optional
+	HnbConfig hnb optional,
+	boolean separate_sccp_cr
 }
 
 /* We extend:
@@ -498,12 +499,21 @@
 	}
 
 	/* create an expect on the Iu side for the random NAS portion */
-	var template (omit) octetstring nas := f_ranap_extract_l3(valueof(tx));
-	f_ran_register_exp(valueof(nas));
+	if (g_pars.separate_sccp_cr) {
+		f_ran_register_sccp_cr_without_payload();
+	} else {
+		var template (omit) octetstring nas := f_ranap_extract_l3(valueof(tx));
+		f_ran_register_exp(valueof(nas));
+	}
 
 	/* send it via Iuh (creating a RUA connection) */
 	RUA.send(RUA_Conn_Req:{g_pars.ps_domain, tx});
 
+	if (g_pars.separate_sccp_cr) {
+		/* Acknowledge the empty SCCP CR. RAN_Emulation does the confirmation, no need to respond. */
+		BSSAP.receive(tr_RANAP_Conn_Req());
+	}
+
 	/* expect to receive it on the Iu side */
 	T.start;
 	alt {
@@ -634,11 +644,13 @@
  ***********************************************************************/
 
 private template (value) TestHdlrParams
-t_pars(integer imsi_suffix, boolean ps_domain := false,integer hnb_idx := 0) := {
+t_pars(integer imsi_suffix, boolean ps_domain := false, integer hnb_idx := 0,
+       boolean separate_sccp_cr := false) := {
 	hnb_idx := hnb_idx,
 	imsi := f_gen_imsi(imsi_suffix),
 	ps_domain := ps_domain,
-	hnb := omit	/* filled in later */
+	hnb := omit,	/* filled in later */
+	separate_sccp_cr := separate_sccp_cr
 }
 
 /* Create an Iuh connection; send InitialUE; expect it to appear on new SCCP conenction */
@@ -666,6 +678,43 @@
 	vc_conn.done;
 }
 
+private function f_vty_set_sccp_cr_max_payload_len(TELNETasp_PT pt, integer val := 999999)
+{
+	f_vty_enter_config(pt);
+	f_vty_transceive(pt, "hnbgw");
+	f_vty_transceive(pt, "sccp cr max-payload-len " & int2str(val));
+	f_vty_transceive(pt, "end");
+}
+
+testcase TC_ranap_cs_initial_ue_empty_cr() runs on test_CT {
+	var ConnHdlr vc_conn;
+
+	f_init();
+	f_start_hnbs();
+
+	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY, 0);
+
+	vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(1, separate_sccp_cr := true));
+	vc_conn.done;
+
+	/* reset */
+	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY);
+}
+testcase TC_ranap_ps_initial_ue_empty_cr() runs on test_CT {
+	var ConnHdlr vc_conn;
+
+	f_init();
+	f_start_hnbs();
+
+	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY, 0);
+
+	vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(2, true, separate_sccp_cr := true));
+	vc_conn.done;
+
+	/* reset */
+	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY);
+}
+
 /* Reply to a received CRCX with an OK (or the reply configured in cpars), using the given parameters.
  * Return true when an OK reply was sent, false otherwise.
  * Count occurrence of Osmux, include Osmux parameters in the reply if necessary. */
@@ -1104,6 +1153,8 @@
 	execute(TC_hnb_register());
 	execute(TC_ranap_cs_initial_ue());
 	execute(TC_ranap_ps_initial_ue());
+	execute(TC_ranap_cs_initial_ue_empty_cr());
+	execute(TC_ranap_ps_initial_ue_empty_cr());
 	execute(TC_ranap_cs_bidir());
 	execute(TC_ranap_ps_bidir());
 	execute(TC_rab_assignment());
diff --git a/library/RAN_Emulation.ttcnpp b/library/RAN_Emulation.ttcnpp
index 3198ee1..543df2b 100644
--- a/library/RAN_Emulation.ttcnpp
+++ b/library/RAN_Emulation.ttcnpp
@@ -555,9 +555,18 @@
 type record RANAP_Conn_Req {
 	SCCP_PAR_Address	addr_peer,
 	SCCP_PAR_Address	addr_own,
-	RANAP_PDU		ranap
+	RANAP_PDU		ranap optional
 }
-template (value) RANAP_Conn_Req ts_RANAP_Conn_Req(SCCP_PAR_Address peer, SCCP_PAR_Address own, RANAP_PDU ranap) := {
+template (value) RANAP_Conn_Req ts_RANAP_Conn_Req(SCCP_PAR_Address peer, SCCP_PAR_Address own,
+						  template (omit) RANAP_PDU ranap) := {
+	addr_peer := peer,
+	addr_own := own,
+	ranap := ranap
+};
+
+template RANAP_Conn_Req tr_RANAP_Conn_Req(template SCCP_PAR_Address peer := ?,
+						  template SCCP_PAR_Address own := ?,
+						  template (omit) RANAP_PDU ranap := omit) := {
 	addr_peer := peer,
 	addr_own := own,
 	ranap := ranap
@@ -973,7 +982,12 @@
 			/* store mapping between client components and SCCP connectionId */
 			f_conn_table_add(vc_conn, rconn_ind.connectionId);
 			/* handle user payload */
-			f_handle_userData_RANAP(vc_conn, rconn_ind.userData);
+			if (ispresent(rconn_ind.userData)) {
+				f_handle_userData_RANAP(vc_conn, rconn_ind.userData);
+			} else {
+				/* Notify client that we received an SCCP CR without user data */
+				CLIENT.send(ts_RANAP_Conn_Req(rconn_ind.callingAddress, rconn_ind.calledAddress, omit));
+			}
 			/* confirm connection establishment */
 			RANAP.send(ts_RANAP_CONNECT_res(rconn_ind.connectionId, omit));
 			}
@@ -1250,6 +1264,11 @@
 			PROC.reply(RAN_register_handoverRequest:{targetPointCode, vc_hdlr}) to vc_hdlr;
 			}
 
+		[] PROC.getcall(RAN_register_sccp_cr_without_payload:{?}) -> param(vc_hdlr) {
+			f_create_expect(omit, vc_hdlr);
+			PROC.reply(RAN_register_sccp_cr_without_payload:{vc_hdlr}) to vc_hdlr;
+			}
+
 		[] PROC.getcall(RAN_register_imsi:{?,?,?}) -> param(imsi, tmsi, vc_hdlr) {
 			f_create_imsi(imsi, tmsi, vc_hdlr);
 			PROC.reply(RAN_register_imsi:{imsi, tmsi, vc_hdlr}) to vc_hdlr;
@@ -1280,6 +1299,7 @@
 	/* L3 payload based on which we can match it */
 	octetstring l3_payload optional,
 	integer handoverRequestPointCode optional,
+	boolean sccp_cr_without_payload,
 	/* component reference for this connection */
 	RAN_ConnHdlr vc_conn
 }
@@ -1287,6 +1307,7 @@
 /* procedure based port to register for incoming connections */
 signature RAN_register(in octetstring l3, in RAN_ConnHdlr hdlr);
 signature RAN_register_handoverRequest(in integer targetPointCode, in RAN_ConnHdlr hdlr);
+signature RAN_register_sccp_cr_without_payload(in RAN_ConnHdlr hdlr);
 
 /* procedure based port to register for incoming IMSI/TMSI */
 signature RAN_register_imsi(in hexstring imsi, in OCT4 tmsi, in RAN_ConnHdlr hdlr);
@@ -1299,7 +1320,8 @@
 signature RAN_continue_after_n_sd(N_Sd_Array last_n_sd, in RAN_ConnHdlr hdlr);
 
 type port RAN_PROC_PT procedure {
-	inout RAN_register, RAN_register_imsi, RAN_unregister_imsi, RAN_register_handoverRequest, RAN_last_n_sd, RAN_continue_after_n_sd;
+	inout RAN_register, RAN_register_imsi, RAN_unregister_imsi, RAN_register_handoverRequest,
+	      RAN_register_sccp_cr_without_payload, RAN_last_n_sd, RAN_continue_after_n_sd;
 } with { extension "internal" };
 
 #ifdef RAN_EMULATION_BSSAP
@@ -1360,25 +1382,31 @@
 runs on RAN_Emulation_CT return RAN_ConnHdlr {
 	var RAN_ConnHdlr ret := null;
 	var template (omit) octetstring l3_info;
+	var boolean rx_sccp_cr_without_payload;
 	var integer i;
 
-	l3_info := f_ranap_extract_l3(conn_ind.userData);
-	if (istemplatekind(l3_info, "omit")) {
-		setverdict(fail, "N-CONNECT.ind without NAS payload");
-		mtc.stop;
-		return ret;
+	if (ispresent(conn_ind.userData)) {
+		l3_info := f_ranap_extract_l3(conn_ind.userData);
+		rx_sccp_cr_without_payload := false;
+		if (istemplatekind(l3_info, "omit") and not rx_sccp_cr_without_payload) {
+			setverdict(fail, "N-CONNECT.ind without NAS payload");
+			mtc.stop;
+			return ret;
+		}
+	} else {
+		l3_info := omit;
+		rx_sccp_cr_without_payload := true;
 	}
 
 	for (i := 0; i < sizeof(ExpectTable); i:= i+1) {
-		if (not ispresent(ExpectTable[i].l3_payload)) {
-			continue;
-		}
-		if (valueof(l3_info) == ExpectTable[i].l3_payload) {
+		if ((rx_sccp_cr_without_payload and ExpectTable[i].sccp_cr_without_payload)
+		    or (ispresent(ExpectTable[i].l3_payload) and valueof(l3_info) == ExpectTable[i].l3_payload)) {
 			ret := ExpectTable[i].vc_conn;
 			/* release this entry to be used again */
+			ExpectTable[i].sccp_cr_without_payload := false;
 			ExpectTable[i].l3_payload := omit;
 			ExpectTable[i].vc_conn := null;
-			log("Found Expect[", i, "] for ", l3_info, " handled at ", ret);
+			log("Found Expect[", i, "] for l3=", l3_info, " handled at ", ret);
 			/* return the component reference */
 			return ret;
 		}
@@ -1396,9 +1424,12 @@
 	log("f_create_expect(l3 := ", l3, ", handoverRequest := ", handoverRequestPointCode);
 	for (i := 0; i < sizeof(ExpectTable); i := i+1) {
 		if (not ispresent(ExpectTable[i].l3_payload)
+		    and not ExpectTable[i].sccp_cr_without_payload
 		    and not ispresent(ExpectTable[i].handoverRequestPointCode)) {
 			if (ispresent(l3)) {
 				ExpectTable[i].l3_payload := valueof(l3);
+			} else {
+				ExpectTable[i].sccp_cr_without_payload := true;
 			}
 			if (ispresent(handoverRequestPointCode)) {
 				ExpectTable[i].handoverRequestPointCode := valueof(handoverRequestPointCode);
@@ -1448,6 +1479,7 @@
 	for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
 		ExpectTable[i].l3_payload := omit;
 		ExpectTable[i].handoverRequestPointCode := omit;
+		ExpectTable[i].sccp_cr_without_payload := false;
 	}
 }
 
@@ -1484,6 +1516,12 @@
 	}
 }
 
+function f_ran_register_sccp_cr_without_payload() runs on RAN_ConnHdlr {
+	BSSAP_PROC.call(RAN_register_sccp_cr_without_payload:{self}) {
+		[] BSSAP_PROC.getreply(RAN_register_sccp_cr_without_payload:{?}) {};
+	}
+}
+
 #ifdef RAN_EMULATION_RANAP
 /* expect a IuReleaseCommand; Confirm that; expect SCCP-level N-DISCONNET.ind */
 altstep as_iu_release_compl_disc(float t := 5.0) runs on RAN_ConnHdlr {