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)