asterisk: Introduce TC_ims_call_mo

The test validates establishing and hanging up a MO call:
SIP-UA -> Asterisk -> IMS-CORE.

SYS#6782
Change-Id: I3c6d8c109c392fa6e1036dcb69a7abb90b22fec7
diff --git a/asterisk/IMS_ConnectionHandler.ttcn b/asterisk/IMS_ConnectionHandler.ttcn
index a253c9f..3f0c380 100644
--- a/asterisk/IMS_ConnectionHandler.ttcn
+++ b/asterisk/IMS_ConnectionHandler.ttcn
@@ -56,14 +56,10 @@
 }
 type record of IMS_ConnHdlr IMS_ConnHdlrList;
 
-type record IMS_ConnHdlrPars {
-	float t_guard,
-	charstring realm,
-	charstring local_sip_host,
-	uint16_t local_sip_port,
+type record IMS_ConnHdlrSubscrPars {
 	charstring remote_sip_host optional,
 	uint16_t remote_sip_port optional,
-	charstring user,
+	charstring imsi,
 	charstring display_name,
 	charstring password,
 	charstring msisdn,
@@ -78,16 +74,26 @@
 	integer ipsec_remote_spi_s optional,
 	uint16_t ipsec_remote_port_c optional,
 	uint16_t ipsec_remote_port_s optional,
-	SipUrl registrar_sip_req_uri,
 	SipAddr registrar_sip_record,
 	CallidString registrar_sip_call_id,
 	integer registrar_sip_seq_nr,
-	Via local_via,
 	SipUrl local_sip_url_ext,
 	SipAddr local_sip_record,
 	Contact local_contact,
 	IMS_CallPars cp optional
 }
+type record of IMS_ConnHdlrSubscrPars IMS_ConnHdlrSubscrParsList;
+
+
+type record IMS_ConnHdlrPars {
+	float t_guard,
+	charstring realm,
+	charstring local_sip_host,
+	uint16_t local_sip_port,
+	SipUrl registrar_sip_req_uri,
+	Via local_via,
+	IMS_ConnHdlrSubscrPars subscr optional
+}
 type record of IMS_ConnHdlrPars IMS_ConnHdlrParsList;
 
 type record IMS_CallParsMT {
@@ -105,8 +111,8 @@
 	SipAddr calling optional,
 	SipAddr called optional,
 
-	SipAddr from_addr optional,
-	SipAddr to_addr optional,
+	From from_addr optional,
+	To to_addr optional,
 
 	CallidString sip_call_id,
 	integer sip_seq_nr,
@@ -136,22 +142,19 @@
 	mt := t_IMS_CallParsMT
 }
 
-template (value) IMS_ConnHdlrPars t_IMS_Pars(charstring local_sip_host,
-					uint16_t local_sip_port,
-					charstring user,
-					charstring display_name := "Anonymous",
-					charstring password := "secret",
-					template (omit) IMS_CallPars cp := omit) := {
-	t_guard := 30.0,
-	realm := local_sip_host,
-	local_sip_host := local_sip_host,
-	local_sip_port := local_sip_port,
+template (value) IMS_ConnHdlrSubscrPars t_IMS_SubscrPars(charstring local_sip_host,
+						   uint16_t local_sip_port,
+						   charstring imsi,
+						   charstring msisdn := "90828",
+						   charstring display_name := "Anonymous",
+						   charstring password := "secret",
+						   template (omit) IMS_CallPars cp := omit) := {
 	remote_sip_host := omit,
 	remote_sip_port := omit,
-	user := user,
+	imsi := imsi,
 	display_name := f_sip_str_quote(display_name),
 	password := password,
-	msisdn := "90828",
+	msisdn := msisdn,
 	uli_str := "2380100010000101",
 	/* The Nonce field is the Base64 encoded version of the RAND value and concatenated with the AUTN: */
 	rand := '14987631f65f8e3788a0798b6ebcd08e'O,
@@ -163,28 +166,39 @@
 	ipsec_remote_spi_s := omit,
 	ipsec_remote_port_c := omit,
 	ipsec_remote_port_s := omit,
-	registrar_sip_req_uri := valueof(ts_SipUrlHost(local_sip_host)),
 	registrar_sip_record := ts_SipAddr(ts_HostPort(local_sip_host),
-					   ts_UserInfo(user),
+					   ts_UserInfo(imsi),
 					   f_sip_str_quote(display_name)),
 	registrar_sip_call_id := hex2str(f_rnd_hexstring(15)) & "@" & local_sip_host,
 	registrar_sip_seq_nr := f_sip_rand_seq_nr(),
-	local_via := ts_Via_from(ts_HostPort(local_sip_host, local_sip_port)),
 	local_sip_url_ext := ts_SipUrl(ts_HostPort(local_sip_host, local_sip_port),
-				       ts_UserInfo(user)),
+				       ts_UserInfo(imsi)),
 	local_sip_record := ts_SipAddr(ts_HostPort(local_sip_host),
-				       ts_UserInfo(user)),
+				       ts_UserInfo(imsi)),
 	local_contact := valueof(ts_Contact({
 					ts_ContactAddress(
 						ts_Addr_Union_SipUrl(ts_SipUrl(ts_HostPort(
 										 local_sip_host,
 										 local_sip_port),
-								     ts_UserInfo(user))),
+								     ts_UserInfo(imsi))),
 						omit)
 				})),
 	cp := cp
 }
 
+template (value) IMS_ConnHdlrPars t_IMS_Pars(charstring local_sip_host,
+					uint16_t local_sip_port,
+					charstring imsi,
+					template (omit) IMS_CallPars cp := omit) := {
+	t_guard := 30.0,
+	realm := local_sip_host,
+	local_sip_host := local_sip_host,
+	local_sip_port := local_sip_port,
+	registrar_sip_req_uri := valueof(ts_SipUrlHost(local_sip_host)),
+	local_via := ts_Via_from(ts_HostPort(local_sip_host, local_sip_port)),
+	subscr := t_IMS_SubscrPars(local_sip_host, local_sip_port, imsi := imsi, cp := cp)
+}
+
 private altstep as_Tguard() runs on IMS_ConnHdlr {
 	[] g_Tguard.timeout {
 		setverdict(fail, "Tguard timeout");
@@ -293,7 +307,7 @@
 
 	/* exp_present: */
 	var template (present) P_Access_Network_Info expl_tmpl :=
-		tr_P_Access_Network_Info({ tr_Access_net_spec_EUTRAN(g_pars.uli_str) });
+		tr_P_Access_Network_Info({ tr_Access_net_spec_EUTRAN(g_pars.subscr.uli_str) });
 
 	if (not ispresent(g_rx_sip_req.msgHeader.p_access_network_info)) {
 		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
@@ -325,13 +339,13 @@
 			continue;
 		}
 		par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-c");
-		g_pars.ipsec_remote_spi_c := str2int(par_val);
+		g_pars.subscr.ipsec_remote_spi_c := str2int(par_val);
 		par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-s");
-		g_pars.ipsec_remote_spi_s := str2int(par_val);
+		g_pars.subscr.ipsec_remote_spi_s := str2int(par_val);
 		par_val := f_sip_param_get_value_present_or_fail(sec_pars, "port-c");
-		g_pars.ipsec_remote_port_c := str2int(par_val);
+		g_pars.subscr.ipsec_remote_port_c := str2int(par_val);
 		par_val := f_sip_param_get_value_present_or_fail(sec_pars, "port-s");
-		g_pars.ipsec_remote_port_s := str2int(par_val);
+		g_pars.subscr.ipsec_remote_port_s := str2int(par_val);
 		found := true;
 		break;
 	}
@@ -341,8 +355,8 @@
 					log2str(g_name & "alg=hmac-sha-1-96 not found: ", security_client));
 	}
 
-	log("ipsec: remote_spi_c=", g_pars.ipsec_remote_spi_c, " remote_spi_s=", g_pars.ipsec_remote_spi_s,
-	    "local_spi_c=", g_pars.ipsec_local_spi_c, " local_spi_s=", g_pars.ipsec_local_spi_s);
+	log("ipsec: remote_spi_c=", g_pars.subscr.ipsec_remote_spi_c, " remote_spi_s=", g_pars.subscr.ipsec_remote_spi_s,
+	    "local_spi_c=", g_pars.subscr.ipsec_local_spi_c, " local_spi_s=", g_pars.subscr.ipsec_local_spi_s);
 }
 
 private function f_IMS_exec_sync(charstring cmdline, template (present) integer rc := 0)
@@ -362,12 +376,12 @@
 
 	var charstring cmd := mp_ipsec_setup_script_path & " " &
 		g_pars.local_sip_host & " " &
-		int2str(g_pars.local_sip_port) & " " & int2str(g_pars.ipsec_local_spi_c) & " " &
-		int2str(g_pars.local_sip_port) & " " & int2str(g_pars.ipsec_local_spi_s) & " " &
-		g_pars.remote_sip_host & " " &
-		int2str(g_pars.ipsec_remote_port_c) & " " & int2str(g_pars.ipsec_remote_spi_c) & " " &
-		int2str(g_pars.ipsec_remote_port_s) & " " & int2str(g_pars.ipsec_remote_spi_s) & " " &
-		g_pars.ipsec_auth_key;
+		int2str(g_pars.local_sip_port) & " " & int2str(g_pars.subscr.ipsec_local_spi_c) & " " &
+		int2str(g_pars.local_sip_port) & " " & int2str(g_pars.subscr.ipsec_local_spi_s) & " " &
+		g_pars.subscr.remote_sip_host & " " &
+		int2str(g_pars.subscr.ipsec_remote_port_c) & " " & int2str(g_pars.subscr.ipsec_remote_spi_c) & " " &
+		int2str(g_pars.subscr.ipsec_remote_port_s) & " " & int2str(g_pars.subscr.ipsec_remote_spi_s) & " " &
+		g_pars.subscr.ipsec_auth_key;
 
 	res := f_IMS_exec_sync(cmd);
 
@@ -381,6 +395,57 @@
 	*/
 }
 
+private function f_tr_Via_response(Via via_req) return template (present) Via {
+	template (present) SemicolonParam_List via_resp_params := ?;
+
+	/*via_resp_params := {
+		{ id := "rport", paramValue := int2str(g_pars.subscr.remote_sip_port.subscr.remote_sip_port) },
+		{ id := "received", paramValue := g_pars.subscr.remote_sip_host }
+	}; */
+	return 	tr_Via_from(via_req.viaBody[0].sentBy,
+			    via_req.viaBody[0].sentProtocol.transport,
+			    via_resp_params);
+}
+
+private function f_tr_From(template (value) SipAddr from_req) return template (present) SipAddr {
+	return tr_SipAddr_from_val(from_req);
+}
+
+private altstep as_SIP_expect_req(template (present) PDU_SIP_Request 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_req;
+	[fail_others] as_SIP_fail_req(sip_expect_str);
+	[fail_others] as_SIP_fail_resp(sip_expect_str);
+}
+
+private function f_gen_sdp() runs on IMS_ConnHdlr return charstring {
+	var charstring sdp :=
+		"v=0\r\n" &
+		"o=0502 2390 1824 IN IP4 " & g_pars.subscr.cp.local_rtp_addr & "\r\n" &
+		"s=Talk\r\n" &
+		"c=IN IP4 " & g_pars.subscr.cp.local_rtp_addr & "\r\n" &
+		"t=0 0\r\n" &
+		"a=rtcp-xr:rcvr-rtt=all:10000 stat-summary=loss,dup,jitt,TTL voip-metrics\r\n" &
+		"a=record:off\r\n" &
+		"m=audio " & int2str(g_pars.subscr.cp.local_rtp_port) & " RTP/AVP 8 96 97 98 0 18 99 100 101\r\n" &
+		"a=rtpmap:8 PCMA/8000\r\n" &
+		"a=rtpmap:96 opus/48000/2\r\n" &
+		"a=fmtp:96 useinbandfec=1\r\n" &
+		"a=rtpmap:97 speex/16000\r\n" &
+		"a=fmtp:97 vbr=on\r\n" &
+		"a=rtpmap:98 speex/8000\r\n" &
+		"a=fmtp:98 vbr=on\r\n" &
+		"a=fmtp:18 annexb=yes\r\n" &
+		"a=rtpmap:99 telephone-event/48000\r\n" &
+		"a=rtpmap:100 telephone-event/16000\r\n" &
+		"a=rtpmap:101 telephone-event/8000\r\n" &
+		"a=rtcp:" & int2str(g_pars.subscr.cp.local_rtp_port + 1) & "\r\n" &
+		"a=rtcp-fb:* trr-int 1000\r\n" &
+		"a=rtcp-fb:* ccm tmmbr\r\n";
+	return sdp;
+}
+
 /* Peer is calling us, accept it: */
 altstep as_IMS_register(boolean exp_update_to_direct_rtp := true,
 			boolean fail_others := true) runs on IMS_ConnHdlr
@@ -449,7 +514,13 @@
 					userAgent := omit);
 		SIP.send(tx_resp);
 
-		g_pars.remote_sip_host := valueof(contact.contactBody.contactAddresses[0].addressField.nameAddr.addrSpec.hostPort.host);
+		var HostPort hp := valueof(contact.contactBody.contactAddresses[0].addressField.nameAddr.addrSpec.hostPort);
+		g_pars.subscr.remote_sip_host := hp.host;
+		if (ispresent(hp.portField)) {
+			g_pars.subscr.remote_sip_port := hp.portField;
+		} else {
+			g_pars.subscr.remote_sip_port := 5060;
+		}
 		f_ims_parse_security_client(g_rx_sip_req.msgHeader.security_client);
 		f_ims_setup_ipsec();
 
@@ -459,7 +530,7 @@
 			ts_Param("realm", f_sip_str_quote(g_pars.realm)),
 			ts_Param("qop", f_sip_str_quote("auth")),
 			ts_Param("algorithm", "AKAv1-MD5"),
-			ts_Param("nonce", f_sip_str_quote(f_nonce_from_rand_autn(g_pars.rand, g_pars.autn)))
+			ts_Param("nonce", f_sip_str_quote(f_nonce_from_rand_autn(g_pars.subscr.rand, g_pars.subscr.autn)))
 			/* "opaque not needed in IMS "*/
 		};
 		wwwAuthenticate := ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } )
@@ -469,8 +540,8 @@
 			ts_Param("q", "0.1"),
 			ts_Param("prot", "esp"),
 			ts_Param("mod", "trans"),
-			ts_Param("spi-c", int2str(g_pars.ipsec_local_spi_c)),
-			ts_Param("spi-s", int2str(g_pars.ipsec_local_spi_s)),
+			ts_Param("spi-c", int2str(g_pars.subscr.ipsec_local_spi_c)),
+			ts_Param("spi-s", int2str(g_pars.subscr.ipsec_local_spi_s)),
 			ts_Param("port-c", int2str(g_pars.local_sip_port)),
 			ts_Param("port-s", int2str(g_pars.local_sip_port)),
 			ts_Param("alg", "hmac-sha-1-96"),
@@ -499,12 +570,12 @@
 
 		/* TODO: Generate expected Authoritzation based on AKAv1-MD5: */
 		/*authorization := f_sip_digest_gen_Authorization(valueof(wwwAuthenticate),
-								g_pars.user, g_pars.password,
+								g_pars.user, g_pars.subscr.password,
 								"REGISTER",
-								f_sip_SipUrl_to_str(g_pars.registrar_sip_record.addr.nameAddr.addrSpec))
+								f_sip_SipUrl_to_str(g_pars.subscr.registrar_sip_record.addr.nameAddr.addrSpec))
 		*/
 		authorization := f_tr_Authorization_AKAv1MD5(valueof(wwwAuthenticate),
-							     g_pars.user & "@" & g_pars.realm,
+							     g_pars.subscr.imsi & "@" & g_pars.realm,
 							     f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri));
 		/* TODO: match Authorization from above: */
 		exp_req :=
@@ -539,9 +610,9 @@
 
 		p_associated_uri := ts_P_Associated_Uri({
 				ts_P_Assoc_uri_spec(ts_NameAddr(ts_SipUrl(ts_HostPort(g_pars.realm),
-									  ts_UserInfo(g_pars.msisdn),
+									  ts_UserInfo(g_pars.subscr.msisdn),
 									  scheme := "sip"))),
-				ts_P_Assoc_uri_spec(ts_NameAddr(ts_SipUrl(ts_HostPort(g_pars.msisdn),
+				ts_P_Assoc_uri_spec(ts_NameAddr(ts_SipUrl(ts_HostPort(g_pars.subscr.msisdn),
 									  omit,
 									  scheme := "tel"))),
 				ts_P_Assoc_uri_spec(g_rx_sip_req.msgHeader.toField.addressField.nameAddr)
@@ -568,4 +639,115 @@
 
 }
 
+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);
+	log("Rx Initial MO INVITE decoded SDP: ", g_pars.subscr.cp.peer_sdp);
+
+	/* Obtain params: */
+	g_pars.subscr.cp.sip_call_id := rx_sip_req.msgHeader.callId.callid;
+	g_pars.subscr.cp.from_addr := rx_sip_req.msgHeader.fromField;
+	g_pars.subscr.cp.to_addr := rx_sip_req.msgHeader.toField;
+	g_pars.subscr.cp.to_addr.toParams := f_sip_param_set(g_pars.subscr.cp.to_addr.toParams, "tag", f_sip_rand_tag());
+	g_pars.subscr.cp.sip_seq_nr := rx_sip_req.msgHeader.cSeq.seqNumber;
+}
+
+/* Peer is calling us, accept it: */
+altstep as_IMS_mo_call_accept(boolean exp_update_to_direct_rtp := false,
+			      boolean fail_others := true) runs on IMS_ConnHdlr
+{
+	var template (present) PDU_SIP_Request exp_req :=
+		tr_SIP_INVITE(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)),
+			      ?,
+			      ( /* FIXME: We should be sending with MSISDN here, aka 2nd option below, but we receive IMSI (first opt): */
+			        tr_From(tr_Addr_Union_from_val(g_pars.subscr.local_sip_record.addr), *),
+			        tr_From(tr_Addr_Union_from_val(g_pars.subscr.cp.calling.addr), *)
+			      ),
+			      tr_To(tr_Addr_Union_from_val(g_pars.subscr.cp.called.addr), *),
+			      tr_Via_from(f_tr_HostPort(g_pars.subscr.remote_sip_host, g_pars.subscr.remote_sip_port)),
+			      ?, ?);
+	var charstring sip_expect_str := log2str(exp_req);
+
+	[] SIP.receive(exp_req) -> value g_rx_sip_req {
+		var template (value) PDU_SIP_Response tx_resp;
+		var Via via;
+		var charstring tx_sdp;
+
+		/* Obtain params: */
+		f_ConnHdlr_parse_initial_SIP_INVITE(g_rx_sip_req);
+		via := g_rx_sip_req.msgHeader.via;
+
+
+		/* Tx 180 Ringing */
+		tx_resp := ts_SIP_Response_Ringing(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);
+		SIP.send(tx_resp);
+
+		/* Tx 200 OK */
+		tx_sdp := f_gen_sdp();
+		tx_resp := ts_SIP_Response(g_pars.subscr.cp.sip_call_id,
+					   g_pars.subscr.cp.from_addr,
+					   g_pars.subscr.cp.to_addr,
+					   "INVITE", 200,
+					   g_pars.subscr.cp.sip_seq_nr,
+					   "OK",
+					   via,
+					   body := tx_sdp);
+		SIP.send(tx_resp);
+
+		/* Wait for ACK */
+		exp_req := tr_SIP_ACK(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)),
+				      g_pars.subscr.cp.sip_call_id,
+				      g_pars.subscr.cp.from_addr,
+				      g_pars.subscr.cp.to_addr,
+				      f_tr_Via_response(via),
+				      g_pars.subscr.cp.sip_seq_nr, *);
+		as_SIP_expect_req(exp_req);
+	}
+	[fail_others] as_SIP_fail_resp(sip_expect_str);
+	[fail_others] as_SIP_fail_req(sip_expect_str);
+
+}
+
+/* Call is terminated by peer: */
+altstep as_IMS_exp_call_hangup(template (present) integer exp_seq_nr := ?, boolean fail_others := true) runs on IMS_ConnHdlr
+{
+	var template (present) PDU_SIP_Request exp_req :=
+		tr_SIP_BYE(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)),
+			   g_pars.subscr.cp.sip_call_id,
+			   g_pars.subscr.cp.from_addr,
+			   g_pars.subscr.cp.to_addr,
+			   tr_Via_from(f_tr_HostPort(g_pars.subscr.remote_sip_host, g_pars.subscr.remote_sip_port)),
+			   exp_seq_nr);
+	var charstring sip_expect_str := log2str(exp_req);
+
+	[] SIP.receive(exp_req) -> value g_rx_sip_req {
+		var template (value) PDU_SIP_Response tx_resp;
+		var charstring tx_sdp;
+		var Via via;
+
+		/* Update parameters: */
+		g_pars.subscr.cp.sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber;
+		/* "branch" has changed: */
+		via := g_rx_sip_req.msgHeader.via;
+
+		/* Tx 200 OK */
+		tx_sdp := f_gen_sdp();
+		tx_resp := ts_SIP_Response(g_pars.subscr.cp.sip_call_id,
+					g_pars.subscr.cp.from_addr,
+					g_pars.subscr.cp.to_addr,
+					"BYE", 200,
+					g_pars.subscr.cp.sip_seq_nr,
+					"OK",
+					via,
+					body := tx_sdp);
+		SIP.send(tx_resp);
+	}
+	[fail_others] as_SIP_fail_resp(sip_expect_str);
+	[fail_others] as_SIP_fail_req(sip_expect_str);
+}
+
 }