DIAMETER_Emulation: Support forwarding messages identified by ete_id to a client component

This is useful in the scenarios where the client component submits a
IMSI-based transaction such as AIR, but its answer (AIA) contains no
IMSI (as per what's specified in TS 29.272 5.2.3.1). As a result, the
received AIA message would be enqueued in the DIAMETER_UNIT.

With this new feature, the test can create an expect using the
End-to-End Identifier of the message it is going to transmit, and
receive the answer in the same DIAMETER_CLIENT port the request was
transmitted, even if it contains no IMSI.

Related: OS#5757
Change-Id: I25e44146d2c49e308c1fb490b499e70ac6045f2f
diff --git a/library/DIAMETER_Emulation.ttcn b/library/DIAMETER_Emulation.ttcn
index 6eb72ad..e7481ca 100644
--- a/library/DIAMETER_Emulation.ttcn
+++ b/library/DIAMETER_Emulation.ttcn
@@ -70,6 +70,12 @@
 	hexstring	imsi optional
 };
 
+/* represents a single DIAMETER message identified by ete_id field */
+type record ETEIDData {
+	DIAMETER_ConnHdlr	comp_ref,
+	UINT32			ete_id optional
+};
+
 type component DIAMETER_Emulation_CT {
 	/* Port facing to the UDP SUT */
 	port DIAMETER_CODEC_PT DIAMETER;
@@ -79,6 +85,8 @@
 	port DIAMETER_Conn_PT DIAMETER_CLIENT;
 	/* currently tracked connections */
 	var AssociationData DiameterAssocTable[256];
+	/* Forward reply messages not containing IMSI to correct client port */
+	var ETEIDData DiameterETEIDTable[256];
 	/* pending expected CRCX */
 	var ExpectData DiameterExpectTable[256];
 	/* procedure based port to register for incoming connections */
@@ -202,7 +210,6 @@
 	mtc.stop;
 }
 
-
 private function f_imsi_table_init()
 runs on DIAMETER_Emulation_CT {
 	for (var integer i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
@@ -211,6 +218,67 @@
 	}
 }
 
+/* End-to-End ID table matching. */
+private function f_ete_id_known(UINT32 ete_id)
+runs on DIAMETER_Emulation_CT return boolean {
+	var integer i;
+	for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) {
+		if (DiameterETEIDTable[i].ete_id == ete_id) {
+			return true;
+		}
+	}
+	return false;
+}
+
+private function f_comp_by_ete_id(UINT32 ete_id)
+runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr {
+	var integer i;
+	for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) {
+		if (DiameterETEIDTable[i].ete_id == ete_id) {
+			return DiameterETEIDTable[i].comp_ref;
+		}
+	}
+	setverdict(fail, "DIAMETER ETEID Table not found by ete_id", ete_id);
+	mtc.stop;
+}
+
+private function f_eteid_table_add(DIAMETER_ConnHdlr comp_ref, UINT32 ete_id)
+runs on DIAMETER_Emulation_CT {
+	var integer i;
+	for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) {
+		if (not isvalue(DiameterETEIDTable[i].ete_id)) {
+			DiameterETEIDTable[i].ete_id := ete_id;
+			DiameterETEIDTable[i].comp_ref := comp_ref;
+			return;
+		}
+	}
+	testcase.stop("DIAMETER ETEID Table full!");
+}
+
+private function f_eteid_table_del(DIAMETER_ConnHdlr comp_ref, UINT32 ete_id)
+runs on DIAMETER_Emulation_CT {
+	var integer i;
+	for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) {
+		if (DiameterETEIDTable[i].comp_ref == comp_ref and
+		    DiameterETEIDTable[i].ete_id == ete_id) {
+			DiameterETEIDTable[i].ete_id := omit;
+			DiameterETEIDTable[i].comp_ref := null;
+			return;
+		}
+	}
+	setverdict(fail, "DIAMETER ETEID Table: Couldn't find to-be-deleted entry!");
+	mtc.stop;
+}
+
+
+private function f_eteid_table_init()
+runs on DIAMETER_Emulation_CT {
+	for (var integer i := 0; i < sizeof(DiameterETEIDTable); i := i+1) {
+		DiameterETEIDTable[i].comp_ref := null;
+		DiameterETEIDTable[i].ete_id := omit;
+	}
+}
+
 function f_DIAMETER_get_imsi(PDU_DIAMETER pdu) return template (omit) IMSI
 {
 	var template (omit) AVP imsi_avp;
@@ -280,6 +348,7 @@
 	var Result res;
 	g_diameter_id := id;
 	f_imsi_table_init();
+	f_eteid_table_init();
 	f_expect_table_init();
 
 	map(self:DIAMETER, system:DIAMETER_CODEC_PT);
@@ -311,6 +380,7 @@
 		var DIAMETER_ConnHdlr vc_conn;
 		var template IMSI imsi_t;
 		var hexstring imsi;
+		var UINT32 ete_id;
 		var DIAMETER_RecvFrom mrf;
 		var PDU_DIAMETER msg;
 		var charstring vlr_name, mme_name;
@@ -357,7 +427,13 @@
 		/* DIAMETER from remote peer (IMSI based routing) */
 		[not ops.raw] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf {
 			imsi_t := f_DIAMETER_get_imsi(mrf.msg);
-			if (isvalue(imsi_t)) {
+			ete_id := mrf.msg.end_to_end_id;
+			if (f_ete_id_known(ete_id)) {
+				vc_conn := f_comp_by_ete_id(ete_id);
+				/* The ete_id is a single-time expect: */
+				f_eteid_table_del(vc_conn, ete_id);
+				DIAMETER_CLIENT.send(mrf.msg) to vc_conn;
+			} else if (isvalue(imsi_t)) {
 				imsi := valueof(imsi_t);
 				if (f_imsi_known(imsi)) {
 					vc_conn := f_comp_by_imsi(imsi);
@@ -377,9 +453,13 @@
 			}
 		[] DIAMETER.receive(tr_SctpAssocChange) { }
 		[] DIAMETER.receive(tr_SctpPeerAddrChange)  { }
-		[] DIAMETER_PROC.getcall(DIAMETEREM_register:{?,?}) -> param(imsi, vc_conn) {
+		[] DIAMETER_PROC.getcall(DIAMETEREM_register_imsi:{?,?}) -> param(imsi, vc_conn) {
 			f_create_expect(imsi, vc_conn);
-			DIAMETER_PROC.reply(DIAMETEREM_register:{imsi, vc_conn}) to vc_conn;
+			DIAMETER_PROC.reply(DIAMETEREM_register_imsi:{imsi, vc_conn}) to vc_conn;
+			}
+		[] DIAMETER_PROC.getcall(DIAMETEREM_register_eteid:{?,?}) -> param(ete_id, vc_conn) {
+			f_eteid_table_add(vc_conn, ete_id);
+			DIAMETER_PROC.reply(DIAMETEREM_register_eteid:{ete_id, vc_conn}) to vc_conn;
 			}
 
 		}
@@ -387,18 +467,29 @@
 	}
 }
 
-/* "Expect" Handling */
+/* "E2E ID Expect" Handling */
+type record ExpectDataE2EID {
+	UINT32 ete_id optional,
+	DIAMETER_ConnHdlr vc_conn
+}
+
+signature DIAMETEREM_register_eteid(in UINT32 ete_id, in DIAMETER_ConnHdlr hdlr);
+
+/* client/conn_hdlr side function to use procedure port to create expect in emulation */
+function f_diameter_expect_eteid(UINT32 ete_id) runs on DIAMETER_ConnHdlr {
+	DIAMETER_PROC.call(DIAMETEREM_register_eteid:{ete_id, self}) {
+		[] DIAMETER_PROC.getreply(DIAMETEREM_register_eteid:{?,?}) {};
+	}
+}
+
+/* "IMSI Expect" Handling */
 
 type record ExpectData {
 	hexstring imsi optional,
 	DIAMETER_ConnHdlr vc_conn
 }
 
-signature DIAMETEREM_register(in hexstring imsi, in DIAMETER_ConnHdlr hdlr);
-
-type port DIAMETEREM_PROC_PT procedure {
-	inout DIAMETEREM_register;
-} with { extension "internal" };
+signature DIAMETEREM_register_imsi(in hexstring imsi, in DIAMETER_ConnHdlr hdlr);
 
 /* Function that can be used as create_cb and will use the expect table */
 function ExpectedCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id)
@@ -446,9 +537,9 @@
 }
 
 /* client/conn_hdlr side function to use procedure port to create expect in emulation */
-function f_diameter_expect(hexstring imsi) runs on DIAMETER_ConnHdlr {
-	DIAMETER_PROC.call(DIAMETEREM_register:{imsi, self}) {
-		[] DIAMETER_PROC.getreply(DIAMETEREM_register:{?,?}) {};
+function f_diameter_expect_imsi(hexstring imsi) runs on DIAMETER_ConnHdlr {
+	DIAMETER_PROC.call(DIAMETEREM_register_imsi:{imsi, self}) {
+		[] DIAMETER_PROC.getreply(DIAMETEREM_register_imsi:{?,?}) {};
 	}
 }
 
@@ -466,6 +557,10 @@
 	return omit;
 }
 
+type port DIAMETEREM_PROC_PT procedure {
+	inout DIAMETEREM_register_imsi;
+	inout DIAMETEREM_register_eteid;
+} with { extension "internal" };
 
 function f_diameter_wait_capability(DIAMETER_PT pt)
 {
diff --git a/mme/MME_Tests.ttcn b/mme/MME_Tests.ttcn
index 17a4d60..cc75377 100644
--- a/mme/MME_Tests.ttcn
+++ b/mme/MME_Tests.ttcn
@@ -361,7 +361,7 @@
 	g_Tguard.start(t_guard);
 	activate(as_Tguard());
 	if (DIAMETER_PROC.checkstate("Connected")) {
-		f_diameter_expect(g_pars.ue_pars.imsi);
+		f_diameter_expect_imsi(g_pars.ue_pars.imsi);
 	}
 	if (SGsAP_PROC.checkstate("Connected")) {
 		/* Route all SGsAP mesages for our IMSIto us */
diff --git a/pgw/PGW_Tests.ttcn b/pgw/PGW_Tests.ttcn
index 7bb6910..4730040 100644
--- a/pgw/PGW_Tests.ttcn
+++ b/pgw/PGW_Tests.ttcn
@@ -72,7 +72,7 @@
 	var PDU_DIAMETER msg;
 
 	if (DIAMETER_PROC.checkstate("Connected")) {
-		f_diameter_expect(imsi);
+		f_diameter_expect_imsi(imsi);
 	}
 
 	while (true) {