asterisk: Introduce test TC_ims_call_mt

This test validates scenario where a call for Asterisk
from IMS Core comes in, Asterisk forwards it to the only
local SIP UA registered, the call becomes established and
then finally IMS Core hangs up after one second.

Change-Id: I2af846f92b6ebca21d1286f5137cb004b6284ddf
diff --git a/asterisk/Asterisk_Tests.ttcn b/asterisk/Asterisk_Tests.ttcn
index c4cad38..75c51e1 100644
--- a/asterisk/Asterisk_Tests.ttcn
+++ b/asterisk/Asterisk_Tests.ttcn
@@ -225,11 +225,13 @@
 		return;
 	}
 
-	as_SIP_mt_call_accept();
+	as_SIP_mt_call_accept(exp_update_to_direct_rtp := g_pars.cp.exp_update_to_direct_rtp);
 	COORD.send(COORD_CMD_CALL_ESTABLISHED);
 
-	/* Once MO hangs up, Asterisk updates us to point RTP to it: */
-	as_SIP_exp_call_update(g_pars.cp.sip_seq_nr + 1);
+	if (g_pars.cp.exp_update_to_direct_rtp) {
+		/* Once MO hangs up, Asterisk updates us to point RTP to it: */
+		as_SIP_exp_call_update(g_pars.cp.sip_seq_nr + 1);
+	}
 	as_SIP_exp_call_hangup(g_pars.cp.sip_seq_nr + 1);
 
 	setverdict(pass);
@@ -491,6 +493,74 @@
 	f_shutdown();
 }
 
+/* Test SIP registration of local clients */
+private function f_TC_ims_call_mt(charstring id) runs on IMS_ConnHdlr {
+	f_create_sip_expect(valueof(ts_SipUrl_from_Addr_Union(g_pars.subscr.registrar_sip_record.addr)));
+	as_IMS_register();
+	COORD.send(IMS_COORD_CMD_REGISTERED);
+	setverdict(pass);
+
+	COORD.receive(IMS_COORD_CMD_START);
+	f_IMS_mt_call_setup();
+	setverdict(pass);
+	COORD.send(IMS_COORD_CMD_CALL_ESTABLISHED);
+
+	COORD.receive(IMS_COORD_CMD_HANGUP);
+	f_IMS_do_call_hangup();
+	setverdict(pass);
+
+	as_IMS_unregister();
+}
+testcase TC_ims_call_mt() runs on test_CT {
+	var SIPConnHdlrPars sip_pars;
+	var IMS_ConnHdlrPars ims_pars;
+	var SIPConnHdlr vc_conn_sip;
+	var IMS_ConnHdlr vc_conn_ims;
+	const charstring c_ext_msisdn := "90829";
+
+	f_init();
+
+	sip_pars := f_init_ConnHdlrPars(idx := 1);
+	ims_pars := f_init_IMS_ConnHdlrPars();
+
+	sip_pars.cp.exp_update_to_direct_rtp := false;
+	sip_pars.cp.calling := valueof(ts_SipAddr(ts_HostPort(sip_pars.remote_sip_host),
+						 ts_UserInfo(c_ext_msisdn)));
+	sip_pars.cp.called := sip_pars.local_sip_record;
+	ims_pars.subscr.cp.calling := valueof(ts_SipAddr(ts_HostPort(ims_pars.realm),
+							ts_UserInfo(c_ext_msisdn)));
+	ims_pars.subscr.cp.called := valueof(ts_SipAddr(ts_HostPort(ims_pars.realm),
+							 ts_UserInfo(ims_pars.subscr.msisdn)));
+
+	vc_conn_ims := f_start_handler_IMS(refers(f_TC_ims_call_mt), ims_pars);
+	vc_conn_sip := f_start_handler(refers(f_TC_internal_call_mt), sip_pars);
+
+	COORD.receive(COORD_CMD_REGISTERED) from vc_conn_sip;
+
+	f_AMI_IMS_register(ims_pars);
+	IMS_COORD.receive(IMS_COORD_CMD_REGISTERED) from vc_conn_ims;
+
+	IMS_COORD.send(IMS_COORD_CMD_START) to vc_conn_ims;
+	IMS_COORD.receive(IMS_COORD_CMD_CALL_ESTABLISHED) from vc_conn_ims;
+	COORD.receive(COORD_CMD_CALL_ESTABLISHED) from vc_conn_sip;
+
+	/* TODO: "Action: DedicatedBearerStatus" */
+
+	/* Call on-going */
+	f_sleep(1.0);
+
+	IMS_COORD.send(IMS_COORD_CMD_HANGUP) to vc_conn_ims;
+
+	/* Trigger unregistration: */
+	f_sleep(1.0);
+	AMI_CLIENT.clear;
+	f_AMI_IMS_unregister(ims_pars);
+
+	vc_conn_sip.done;
+	vc_conn_ims.done;
+	f_shutdown();
+}
+
 control {
 	execute( TC_internal_registration() );
 	execute( TC_internal_call_momt() );
@@ -499,6 +569,7 @@
 	execute( TC_internal_call_all_4registered() );
 	execute( TC_ims_registration() );
 	execute( TC_ims_call_mo() );
+	execute( TC_ims_call_mt() );
 }
 
 }
diff --git a/asterisk/IMS_ConnectionHandler.ttcn b/asterisk/IMS_ConnectionHandler.ttcn
index c7eb84c..3c3a51b 100644
--- a/asterisk/IMS_ConnectionHandler.ttcn
+++ b/asterisk/IMS_ConnectionHandler.ttcn
@@ -43,7 +43,10 @@
 	inout charstring;
 } with { extension "internal" };
 
-const charstring IMS_COORD_CMD_REGISTERED := "COORD_CMD_REGISTERED";
+const charstring IMS_COORD_CMD_REGISTERED := "IMS_COORD_CMD_REGISTERED";
+const charstring IMS_COORD_CMD_START := "IMS_COORD_CMD_START";
+const charstring IMS_COORD_CMD_CALL_ESTABLISHED := "IMS_COORD_CMD_CALL_ESTABLISHED";
+const charstring IMS_COORD_CMD_HANGUP := "IMS_COORD_CMD_HANGUP";
 
 type component IMS_ConnHdlr extends SIP_ConnHdlr {
 	var charstring g_name;
@@ -87,7 +90,7 @@
 	integer registrar_sip_seq_nr,
 	SipUrl local_sip_url_ext,
 	SipAddr local_sip_record,
-	Contact local_contact,
+	Contact registered_contact optional,
 	P_Associated_Uri p_associated_uri,
 	IMS_CallPars cp optional
 }
@@ -190,14 +193,7 @@
 				       ts_UserInfo(imsi)),
 	local_sip_record := ts_SipAddr(ts_HostPort(domain),
 				       ts_UserInfo(imsi)),
-	local_contact := valueof(ts_Contact({
-					ts_ContactAddress(
-						ts_Addr_Union_SipUrl(ts_SipUrl(ts_HostPort(
-										 domain,
-										 local_sip_port),
-								     ts_UserInfo(imsi))),
-						omit)
-				})),
+	registered_contact := omit,
 	p_associated_uri := ts_P_Associated_Uri({}),
 	cp := cp
 }
@@ -253,6 +249,22 @@
 	}
 }
 
+altstep as_SIP_expect_resp(template (present) PDU_SIP_Response sip_expect, boolean fail_others := true) runs on IMS_ConnHdlr
+{
+	var charstring sip_expect_str := log2str(sip_expect);
+	[] SIP.receive(sip_expect) -> value g_rx_sip_resp;
+	[fail_others] as_SIP_fail_resp(sip_expect_str);
+	[fail_others] as_SIP_fail_req(sip_expect_str);
+}
+
+altstep as_SIP_ignore_resp(template PDU_SIP_Response sip_expect := ?) runs on IMS_ConnHdlr
+{
+	[] SIP.receive(sip_expect) -> value g_rx_sip_resp {
+		log("Ignoring ", g_rx_sip_resp);
+		repeat;
+	}
+}
+
 private function f_nonce_from_rand_autn(octetstring rand, octetstring autn) return charstring {
 	var octetstring concat := rand & autn;
 	var charstring nonce := enc_MIME_Base64(concat);
@@ -510,6 +522,7 @@
 
 		contact := g_rx_sip_req.msgHeader.contact;
 		f_ims_validate_register_contact(contact);
+		g_pars.subscr.registered_contact := contact;
 
 		/* Validate P-Access-Network-Info: rfc7315 6.4:
 		 * "3GPP will use the P-Access-Network-Info header field to
@@ -722,6 +735,7 @@
 		for (var integer i := 0; i < lengthof(contact.contactBody.contactAddresses); i := i + 1) {
 			contact.contactBody.contactAddresses[i].contactParams := valueof({ ts_Param("expires", "0") });
 		}
+		g_pars.subscr.registered_contact := omit;
 		/* Tx 200 OK */
 		to_addr.toParams := f_sip_param_set(to_addr.toParams, "tag", f_sip_rand_tag());
 		tx_resp := ts_SIP_Response(sip_call_id,
@@ -743,6 +757,130 @@
 	[fail_others] as_SIP_fail_req(sip_expect_str);
 }
 
+function f_IMS_mt_call_setup() runs on IMS_ConnHdlr
+{
+	var template (value) PDU_SIP_Request req;
+	var template (present) PDU_SIP_Response exp;
+	var template (present) From from_addr_exp;
+	var template (present) To to_addr_exp;
+	var Via via;
+	var charstring tx_sdp := f_gen_sdp();
+	var default d_trying, d_ringing;
+	var charstring branch_value;
+	var Contact calling_contact;
+
+	/* RFC 3261 8.1.1.3 From */
+	g_pars.subscr.cp.from_addr := valueof(ts_From(g_pars.subscr.cp.calling.addr, g_pars.subscr.cp.calling.params));
+	g_pars.subscr.cp.from_addr.fromParams := f_sip_param_set(g_pars.subscr.cp.from_addr.fromParams, "tag", f_sip_rand_tag());
+	g_pars.subscr.cp.to_addr := valueof(ts_To(g_pars.subscr.cp.called.addr, g_pars.subscr.cp.called.params));
+	from_addr_exp := tr_From(tr_Addr_Union_from_val(g_pars.subscr.cp.from_addr.addressField), *);
+	to_addr_exp := tr_To(tr_Addr_Union_from_val(g_pars.subscr.cp.to_addr.addressField), *);
+	branch_value := f_sip_gen_branch(f_sip_Addr_Union_to_str(g_pars.subscr.cp.from_addr.addressField),
+					 f_sip_Addr_Union_to_str(valueof(g_pars.subscr.cp.to_addr.addressField)),
+					 g_pars.subscr.cp.sip_call_id,
+					 g_pars.subscr.cp.sip_seq_nr);
+	via := g_pars.local_via;
+	via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "branch", branch_value);
+
+	calling_contact := valueof(ts_Contact({
+					ts_ContactAddress(g_pars.subscr.cp.calling.addr, omit)
+				}));
+
+	req := ts_SIP_INVITE(g_pars.subscr.cp.sip_call_id,
+			     g_pars.subscr.cp.from_addr,
+			     g_pars.subscr.cp.to_addr,
+			     via,
+			     calling_contact,
+			     g_pars.subscr.cp.sip_seq_nr,
+			     body := tx_sdp);
+
+	SIP.send(req);
+
+	/* Conditionally match and accept 100 Trying. */
+	exp := tr_SIP_Response_Trying(g_pars.subscr.cp.sip_call_id,
+			from_addr_exp,
+			to_addr_exp,
+			f_tr_Via_response(via),
+			g_pars.subscr.cp.sip_seq_nr, "INVITE");
+	d_trying := activate(as_SIP_ignore_resp(exp));
+
+	/* Conditionally match and accept 180 Ringing */
+	exp := tr_SIP_Response_Ringing(g_pars.subscr.cp.sip_call_id,
+			from_addr_exp,
+			to_addr_exp,
+			f_tr_Via_response(via),
+			g_pars.subscr.cp.sip_seq_nr, "INVITE");
+	d_ringing := activate(as_SIP_ignore_resp(exp));
+
+	/* Wait for OK answer */
+	exp := tr_SIP_Response(
+			g_pars.subscr.cp.sip_call_id,
+			from_addr_exp,
+			to_addr_exp,
+			f_tr_Via_response(via),
+			*,
+			"INVITE", 200,
+			g_pars.subscr.cp.sip_seq_nr, "OK",
+			body := ?);
+	as_SIP_expect_resp(exp, fail_others := false);
+
+	deactivate(d_trying);
+	deactivate(d_ringing);
+
+	/* Update To with the tags received from peer: */
+	g_pars.subscr.cp.to_addr := g_rx_sip_resp.msgHeader.toField;
+
+	/* Transmit ACK */
+	g_pars.subscr.cp.sip_seq_nr := g_pars.subscr.cp.sip_seq_nr + 1;
+	req := ts_SIP_ACK(g_pars.subscr.cp.sip_call_id,
+			  g_pars.subscr.cp.from_addr,
+			  g_pars.subscr.cp.to_addr,
+			  via,
+			  g_pars.subscr.cp.sip_seq_nr,
+			  omit);
+	SIP.send(req);
+	g_pars.subscr.cp.sip_seq_nr := g_pars.subscr.cp.sip_seq_nr + 1;
+}
+
+/* Tx BYE: */
+function f_IMS_do_call_hangup() runs on IMS_ConnHdlr
+{
+	var template (value) PDU_SIP_Request req;
+	var template (present) PDU_SIP_Response exp_resp;
+	var Via via;
+	var charstring branch_value;
+
+	branch_value := f_sip_gen_branch(f_sip_Addr_Union_to_str(g_pars.subscr.cp.from_addr.addressField),
+					 f_sip_Addr_Union_to_str(valueof(g_pars.subscr.cp.to_addr.addressField)),
+					 g_pars.subscr.cp.sip_call_id,
+					 g_pars.subscr.cp.sip_seq_nr);
+
+	via := g_pars.local_via;
+	via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "branch", branch_value);
+
+	/* Transmit ACK */
+	req := ts_SIP_BYE(g_pars.subscr.cp.sip_call_id,
+			  g_pars.subscr.cp.from_addr,
+			  g_pars.subscr.cp.to_addr,
+			  via,
+			  g_pars.subscr.cp.sip_seq_nr,
+			  omit);
+	SIP.send(req);
+
+	/* Wait for OK answer */
+	exp_resp := tr_SIP_Response(
+			g_pars.subscr.cp.sip_call_id,
+			g_pars.subscr.cp.from_addr,
+			tr_To(tr_Addr_Union_from_val(g_pars.subscr.cp.to_addr.addressField), *),
+			f_tr_Via_response(via),
+			*,
+			"BYE", 200,
+			g_pars.subscr.cp.sip_seq_nr, "OK");
+	as_SIP_expect_resp(exp_resp);
+
+	g_pars.subscr.cp.sip_seq_nr := g_pars.subscr.cp.sip_seq_nr + 1;
+}
+
 private function f_ConnHdlr_parse_initial_SIP_INVITE(PDU_SIP_Request rx_sip_req) runs on IMS_ConnHdlr
 {
 	f_SDP_decodeMessage(rx_sip_req.messageBody, g_pars.subscr.cp.peer_sdp);
diff --git a/asterisk/SIP_ConnectionHandler.ttcn b/asterisk/SIP_ConnectionHandler.ttcn
index cd50be6..05f872f 100644
--- a/asterisk/SIP_ConnectionHandler.ttcn
+++ b/asterisk/SIP_ConnectionHandler.ttcn
@@ -90,6 +90,8 @@
 	charstring local_rtp_addr,
 	uint16_t local_rtp_port,
 
+	/* Whether to expect Asterisk to re-INVITE to make RTP flow directly to peer. */
+	boolean exp_update_to_direct_rtp,
 	SDP_Message peer_sdp optional,
 	CallParsMT mt
 }
@@ -107,6 +109,7 @@
 	sip_body := omit,
 	local_rtp_addr := local_rtp_addr,
 	local_rtp_port := local_rtp_port,
+	exp_update_to_direct_rtp := true,
 	peer_sdp := omit,
 	mt := t_CallParsMT
 }
diff --git a/asterisk/expected-results.xml b/asterisk/expected-results.xml
index 78c5ee4..44d6c01 100644
--- a/asterisk/expected-results.xml
+++ b/asterisk/expected-results.xml
@@ -7,4 +7,5 @@
     <testcase classname='Asterisk_Tests' name='TC_internal_call_all_4registered' time='MASKED'/>
     <testcase classname='Asterisk_Tests' name='TC_ims_registration' time='MASKED'/>
     <testcase classname='Asterisk_Tests' name='TC_ims_call_mo' time='MASKED'/>
+    <testcase classname='Asterisk_Tests' name='TC_ims_call_mt' time='MASKED'/>
 </testsuite>