S1AP_Emulation: improve accessibility of unit-data

When S1AP unit-data is passed around, there is always the problem that
it is routed to the MTC_CT. The reason for this is that S1AP_Emulation
cannot determine a specific receiver component because unit-data does
not contain any addressing fields that would identifiy a specific eNB or
UE.

Unfortunately it can be a huge problem when developing test fixtures
that use unit-data from inside a ConnHdlr component. It is no problem to
send unit-data using S1AP.send(), but it is impossible to receive
unit-data through the same path.

The solution that is proposed in this patch uses a mechanism that allows
to create an expectation for a specific procedureCode. When the
S1AP_Emulation sees a messages with the expected procedureCode, it will
forward it to the ConnHdlr component.

Related: OS#5760
Change-Id: I041b45b247e365b0d4ee8645c07dc5f26007af82
diff --git a/library/S1AP_Emulation.ttcn b/library/S1AP_Emulation.ttcn
index 38c738b..bb3a2a4 100644
--- a/library/S1AP_Emulation.ttcn
+++ b/library/S1AP_Emulation.ttcn
@@ -15,7 +15,9 @@
  *
  * If a pre-existing component wants to register to handle a future inbound UE
  * association, it can do so by registering an "expect" with the expected
- * MME_UE_S1AP_ID/ENB_UE_S1AP_ID identifiers.
+ * MME_UE_S1AP_ID/ENB_UE_S1AP_ID identifiers. It is also possible to register
+ * an expect for a specific procedureCode, in case the expected message is non
+ * UE related (unit-data).
  *
  * Inbound non-UE related S1AP messages (such as RESET, SETUP, OVERLOAD) are
  * dispatched to the S1apOps.unitdata_cb() callback, which is registered with
@@ -107,6 +109,8 @@
 	var AssociationData S1apAssociationTable[16];
 	/* pending expected S1AP Association (UE oriented) */
 	var ExpectData S1apExpectTable[8];
+	/* pending expected S1AP PDU */
+	var ExpectDataProc S1apExpectTableProc[8];
 	/* procedure based port to register for incoming connections */
 	port S1APEM_PROC_PT S1AP_PROC;
 	/* test port for unit data messages */
@@ -367,6 +371,32 @@
 	}
 }
 
+private function SendToS1apExpectTableProc(S1AP_PDU msg) runs on S1AP_Emulation_CT {
+	var integer procedureCode;
+	var S1AP_ConnHdlr vc_conn;
+
+	if (ispresent(msg.initiatingMessage.procedureCode)) {
+		procedureCode := msg.initiatingMessage.procedureCode;
+	} else if (ispresent(msg.unsuccessfulOutcome.procedureCode)) {
+		procedureCode := msg.unsuccessfulOutcome.procedureCode;
+	} else if (ispresent(msg.successfulOutcome.procedureCode)) {
+		procedureCode := msg.successfulOutcome.procedureCode;
+	} else {
+		return;
+	}
+
+	for (var integer i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
+		if (S1apExpectTableProc[i].procedureCode == procedureCode) {
+			vc_conn := S1apExpectTableProc[i].vc_conn;
+			if (vc_conn != null) {
+				S1AP_CLIENT.send(msg) to vc_conn;
+			}
+		}
+	}
+
+	return;
+}
+
 function main(S1APOps ops, S1AP_conn_parameters p, charstring id) runs on S1AP_Emulation_CT {
 	var Result res;
 	g_pars := p;
@@ -397,6 +427,7 @@
 		var PDU_NAS_EPS nas;
 		var MME_UE_S1AP_ID mme_id;
 		var ENB_UE_S1AP_ID enb_id;
+		var integer procedureCode;
 		var S1AP_RecvFrom mrf;
 		var S1AP_PDU msg;
 		var S1APEM_Config s1cfg;
@@ -448,6 +479,7 @@
 		[] S1AP.receive(tr_S1AP_RecvFrom_R(?)) -> value mrf {
 			if (match(mrf.msg, tr_S1AP_nonUErelated)) {
 				/* non-UE-related S1AP message */
+				SendToS1apExpectTableProc(mrf.msg);
 				var template S1AP_PDU resp := ops.unitdata_cb.apply(mrf.msg);
 				if (isvalue(resp)) {
 					S1AP.send(t_S1AP_Send(g_s1ap_conn_id, valueof(resp)));
@@ -491,6 +523,10 @@
 			f_create_expect(mme_id, enb_id, vc_conn);
 			S1AP_PROC.reply(S1APEM_register:{mme_id, enb_id, vc_conn}) to vc_conn;
 			}
+		[] S1AP_PROC.getcall(S1APEM_register_proc:{?,?}) -> param(procedureCode, vc_conn) {
+			f_create_expect_proc(procedureCode, vc_conn);
+			S1AP_PROC.reply(S1APEM_register_proc:{procedureCode, vc_conn}) to vc_conn;
+			}
 		}
 
 	}
@@ -504,10 +540,19 @@
 	S1AP_ConnHdlr vc_conn
 }
 
+/* represents a single S1AP PDU that we expect. When a matching PDU is seen, it is forwarded to the registered
+ * component */
+type record ExpectDataProc {
+	integer procedureCode optional,
+	S1AP_ConnHdlr vc_conn				/* component handling this UE connection */
+};
+
 signature S1APEM_register(in MME_UE_S1AP_ID mme_id, in ENB_UE_S1AP_ID enb_id, in S1AP_ConnHdlr hdlr);
+signature S1APEM_register_proc(in integer procedureCode, in S1AP_ConnHdlr hdlr);
 
 type port S1APEM_PROC_PT procedure {
 	inout S1APEM_register;
+	inout S1APEM_register_proc;
 } with { extension "internal" };
 
 /* Function that can be used as create_cb and will use the expect table */
@@ -573,6 +618,39 @@
 	}
 }
 
+private function f_create_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_Emulation_CT {
+	var integer i;
+
+	/* Check an entry like this is not already presnt */
+	for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
+		if (S1apExpectTableProc[i].vc_conn == null) {
+			continue;
+		}
+		if (S1apExpectTableProc[i].procedureCode == procedureCode) {
+			setverdict(fail, "procedureCode ", procedureCode, " already present");
+			mtc.stop;
+		}
+	}
+	for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
+		if (S1apExpectTableProc[i].vc_conn == null) {
+			S1apExpectTableProc[i].procedureCode := procedureCode;
+			S1apExpectTableProc[i].vc_conn := hdlr;
+			log("Created Expect[", i, "] for PDU:", procedureCode, " to be handled at ", hdlr);
+			return;
+		}
+	}
+	testcase.stop("No space left in S1apExpectTableProc")
+}
+
+/* client/conn_hdlr side function to use procedure port to create expect (PDU) in emulation */
+function f_create_s1ap_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_ConnHdlr
+{
+	S1AP_PROC.call(S1APEM_register_proc:{procedureCode, self}) {
+		[] S1AP_PROC.getreply(S1APEM_register_proc:{?,?}) {};
+	}
+
+	log(procedureCode);
+}
 
 private function f_expect_table_init()
 runs on S1AP_Emulation_CT {
@@ -582,6 +660,11 @@
 		S1apExpectTable[i].enb_id := omit;
 		S1apExpectTable[i].vc_conn := null;
 	}
+
+	for (i := 0; i < sizeof(S1apExpectTableProc); i := i + 1) {
+		S1apExpectTableProc[i].procedureCode := omit;
+		S1apExpectTableProc[i].vc_conn := null;
+	}
 }
 
 function DummyUnitdataCallback(S1AP_PDU msg)