asterisk: Initial IMS registration

This patch is a step towards testing IMS.
So far only the code infrstrastructure to handle the 1st REGISTER
(outside IPSEC) is provided. It can already be seen how Asterisk
sends the second REGISTER over IPSEC, but there's no means to test it
yet in TTCN-3. This will be done in a follow-up patch, which may take
some work.

Change-Id: Idb3b19ccd82cad25948106b2c72aa424d7f79cd8
diff --git a/asterisk/IMS_ConnectionHandler.ttcn b/asterisk/IMS_ConnectionHandler.ttcn
index a1baeb4..18404f1 100644
--- a/asterisk/IMS_ConnectionHandler.ttcn
+++ b/asterisk/IMS_ConnectionHandler.ttcn
@@ -23,6 +23,9 @@
 import from SIPmsg_Types all;
 import from SIP_Templates all;
 
+const char c_sip_server_name := "osmo-ttcn3-hacks/0.23";
+
+
 type port IMSCoord_PT message
 {
 	inout charstring;
@@ -43,11 +46,18 @@
 
 type record IMS_ConnHdlrPars {
 	float t_guard,
-	charstring remote_sip_host,
-	uint16_t remote_sip_port,
+	charstring realm,
+	charstring local_sip_host,
+	uint16_t local_sip_port,
+	charstring remote_sip_host optional,
+	uint16_t remote_sip_port optional,
 	charstring user,
 	charstring display_name,
 	charstring password,
+	integer ipsec_local_spi_c,
+	integer ipsec_local_spi_s,
+	integer ipsec_remote_spi_c optional,
+	integer ipsec_remote_spi_s optional,
 	SipUrl registrar_sip_req_uri,
 	SipAddr registrar_sip_record,
 	CallidString registrar_sip_call_id,
@@ -66,6 +76,10 @@
 	/* Whether to expect CANCEL instead of ACK as answer to our OK */
 	boolean exp_cancel
 }
+template (value) IMS_CallParsMT t_IMS_CallParsMT := {
+	wait_coord_cmd_pickup := false,
+	exp_cancel := false
+}
 
 type record IMS_CallPars {
 	SipAddr calling optional,
@@ -85,4 +99,300 @@
 	IMS_CallParsMT mt
 }
 
+template (value) IMS_CallPars t_IMS_CallPars(charstring local_rtp_addr,
+					     uint16_t local_rtp_port := 0,
+					     template (omit) SipAddr calling := omit,
+					     template (omit) SipAddr called := omit) := {
+	calling := calling,
+	called := called,
+	from_addr := omit,
+	to_addr := omit,
+	sip_call_id := hex2str(f_rnd_hexstring(15)),
+	sip_seq_nr := f_sip_rand_seq_nr(),
+	sip_body := omit,
+	local_rtp_addr := local_rtp_addr,
+	local_rtp_port := local_rtp_port,
+	peer_sdp := omit,
+	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,
+	remote_sip_host := omit,
+	remote_sip_port := omit,
+	user := user,
+	display_name := f_sip_str_quote(display_name),
+	password := password,
+	ipsec_local_spi_c := 4142,
+	ipsec_local_spi_s := 4143,
+	ipsec_remote_spi_c := omit,
+	ipsec_remote_spi_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),
+					   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)),
+	local_sip_record := ts_SipAddr(ts_HostPort(local_sip_host),
+				       ts_UserInfo(user)),
+	local_contact := valueof(ts_Contact({
+					ts_ContactAddress(
+						ts_Addr_Union_SipUrl(ts_SipUrl(ts_HostPort(
+										 local_sip_host,
+										 local_sip_port),
+								     ts_UserInfo(user))),
+						omit)
+				})),
+	cp := cp
+}
+
+private altstep as_Tguard() runs on IMS_ConnHdlr {
+	[] g_Tguard.timeout {
+		setverdict(fail, "Tguard timeout");
+		mtc.stop;
+	}
+}
+
+type function ims_void_fn(charstring id) runs on IMS_ConnHdlr;
+function f_ims_handler_init(ims_void_fn fn, charstring id, IMS_ConnHdlrPars pars)
+runs on IMS_ConnHdlr {
+	g_name := id;
+	g_pars := pars;
+	g_Tguard.start(pars.t_guard);
+	activate(as_Tguard());
+
+	/* call the user-supied test case function */
+	fn.apply(id);
+}
+
+private altstep as_SIP_fail_req(charstring exp_msg_str := "") runs on IMS_ConnHdlr
+{
+	var PDU_SIP_Request sip_req;
+	[] SIP.receive(PDU_SIP_Request:?) -> value sip_req {
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+					log2str(g_name & ": Received unexpected SIP Req message := ", sip_req, "\nvs exp := ", exp_msg_str));
+	}
+}
+
+private altstep as_SIP_fail_resp(charstring exp_msg_str := "") runs on IMS_ConnHdlr
+{
+	var PDU_SIP_Response sip_resp;
+	[] SIP.receive(PDU_SIP_Response:?) -> value sip_resp {
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+					log2str(g_name & ": Received unexpected SIP Resp message := ", sip_resp, "\nvs exp := ", exp_msg_str));
+	}
+}
+
+private function f_ims_validate_register_contact(Contact rx_contact)
+{
+/* IMS contact shows up like this:
+ * Contact: <sip:8adf9f3d-9342-4060-aa4f-a909f37fd6f6@192.168.101.2:5060>;+g.3gpp.accesstype="cellular2";video;audio;+g.3gpp.smsip;+g.3gpp.nw-init-ussi;+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel";+sip.instance="<urn:gsma:imei:35589811-338445-0>"
+ */
+ /* TODO: "that the UE must include the IMS Communication Service Identifier (ICSI)
+in the contact: header to indicate IMS Multimedia Telephony." */
+ /* TODO: "The UE must include an IMEI URN in the +sip.instance header field
+parameter of the contact: header." */
+ /* TODO: "If the UE supports SMS over IP, it must include the feature tag
+“+g.3gpp.smsip” in the contact: header." */
+ /* TODO: "If the UE supports conversational audio and video service, then this must
+be indicated by adding a “video” media feature tag to the contact: header." */
+}
+
+private function f_ims_parse_security_client(Security_client security_client) runs on IMS_ConnHdlr
+{
+	var boolean found := false;
+	for (var integer i := 0; i < lengthof(security_client.sec_mechanism_list); i := i + 1) {
+		var Security_mechanism sec_mec := security_client.sec_mechanism_list[i];
+		if (sec_mec.mechanism_name != "ipsec-3gpp") {
+			log("Skipping Security Mechansim: ", sec_mec.mechanism_name);
+			continue;
+		}
+		var SemicolonParam_List sec_pars := sec_mec.mechanism_params;
+		var charstring par_val;
+		par_val := f_sip_param_get_value_present_or_fail(sec_pars, "alg");
+		if (par_val != "hmac-sha-1-96") {
+			log("Skipping Security Mechansim Algo: ", par_val);
+			continue;
+		}
+		par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-c");
+		g_pars.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);
+		found := true;
+		break;
+	}
+
+	if (not found) {
+		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+					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);
+}
+
+private function f_ims_setup_ipsec(PDU_SIP_Request req_req) runs on IMS_ConnHdlr
+{
+	var Security_client security_client := req_req.msgHeader.security_client;
+	f_ims_parse_security_client(security_client);
+}
+
+/* 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
+{
+	var template (present) PDU_SIP_Request exp_req :=
+		tr_SIP_REGISTER(g_pars.registrar_sip_req_uri,
+				?,
+				tr_SipAddr(),
+				tr_SipAddr(),
+				tr_Via_from(?),
+				require := tr_Require(superset("sec-agree")),
+				security_client := tr_Security_client(superset(tr_Security_mechanism("ipsec-3gpp",
+												     superset(tr_Param("alg","hmac-sha-1-96"))))),
+				supported := tr_Supported(superset("path", "sec-agree")));
+	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 CallidString sip_call_id;
+		var Contact contact;
+		var template (value) SipAddr from_addr;
+		var template (value) SipAddr to_addr;
+		var template (value) CommaParam_List digestCln ;
+		var template (value) WwwAuthenticate wwwAuthenticate;
+		var template (value) Security_server security_server;
+		var template (value) Server server_name := ts_Server({c_sip_server_name});
+		var template (value) Supported supported := ts_Supported({"sec-agree"});
+		var Authorization authorization;
+		var integer sip_seq_nr;
+		var charstring tx_sdp;
+
+		sip_call_id := g_rx_sip_req.msgHeader.callId.callid;
+		via := g_rx_sip_req.msgHeader.via;
+		via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "rport", "1234"); /* TODO: set remote src port of the REGISTER */
+		from_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.fromField.addressField,
+							g_rx_sip_req.msgHeader.fromField.fromParams);
+		to_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.toField.addressField,
+						      g_rx_sip_req.msgHeader.toField.toParams);
+		sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber;
+
+		contact := g_rx_sip_req.msgHeader.contact;
+		f_ims_validate_register_contact(contact);
+
+		/* TODO: Validate "Expires" is 600000 */
+		/* TODO: validate presence of:
+		 * Security-Client: ipsec-3gpp; alg=hmac-md5-96; ealg=des-ede3-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-md5-96; ealg=aes-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-md5-96; ealg=null; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-sha-1-96; ealg=des-ede3-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-sha-1-96; ealg=aes-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-sha-1-96; ealg=null; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718
+		 */
+
+		/* Tx 100 Tyring */
+		tx_resp := ts_SIP_Response_Trying(sip_call_id,
+					from_addr,
+					to_addr,
+					via,
+					sip_seq_nr,
+					"REGISTER",
+					allow := omit,
+					server := server_name,
+					userAgent := omit);
+		SIP.send(tx_resp);
+
+		f_ims_setup_ipsec(g_rx_sip_req);
+
+		to_addr.params := f_sip_param_set(to_addr.params, "tag", f_sip_rand_tag());
+
+		digestCln := {
+			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("FJh2MfZfjjeIoHmLbrzQjvbhmnzLAoAAoGsZyVRFFuU="))
+			/* "opaque not needed in IMS "*/
+		};
+		wwwAuthenticate := ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } )
+
+		/* Security-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-c=4096;spi-s=4097;port-c=5104;port-s=6104;alg=hmac-sha-1-96;ealg=null */
+		var template (value) SemicolonParam_List sec_params := {
+			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("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"),
+			ts_Param("ealg", "null")
+		};
+		security_server := ts_Security_server({
+			ts_Security_mechanism("ipsec-3gpp", sec_params)
+		});
+		/* Tx 401 Unauthorized
+		 * TODO: with IMS params */
+		tx_resp := ts_SIP_Response_Unauthorized(sip_call_id,
+					from_addr,
+					to_addr,
+					via,
+					wwwAuthenticate,
+					sip_seq_nr,
+					"REGISTER",
+					security_server := security_server,
+					server := server_name,
+					supported := supported,
+					userAgent := omit);
+		SIP.send(tx_resp);
+
+		/* TODO: Generate expected Authoritzation based on AKAv1-MD5: */
+		/*authorization := f_sip_digest_gen_Authorization(valueof(wwwAuthenticate),
+								g_pars.user, g_pars.password,
+								"REGISTER",
+								f_sip_SipUrl_to_str(g_pars.registrar_sip_record.addr.nameAddr.addrSpec))
+		*/
+		/* TODO: match Authorization from above: */
+		exp_req :=
+		tr_SIP_REGISTER(g_pars.registrar_sip_req_uri,
+				?,
+				tr_SipAddr(),
+				tr_SipAddr(),
+				tr_Via_from(?));
+		SIP.receive(exp_req) -> value g_rx_sip_req;
+
+		sip_call_id := g_rx_sip_req.msgHeader.callId.callid;
+		via := g_rx_sip_req.msgHeader.via;
+		from_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.fromField.addressField,
+							g_rx_sip_req.msgHeader.fromField.fromParams);
+		to_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.toField.addressField,
+						      g_rx_sip_req.msgHeader.toField.toParams);
+		to_addr.params := f_sip_param_set(to_addr.params, "tag", f_sip_rand_tag());
+		sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber;
+
+		/* TODO: Add following fields:
+		 * Supported: sec-agree
+		 * Security-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-c=4096;spi-s=4097;port-c=5104;port-s=6104;alg=hmac-sha-1-96;ealg=null
+		 * */
+
+		tx_resp := ts_SIP_Response(sip_call_id,
+			from_addr,
+			to_addr,
+			"REGISTER", 200,
+			sip_seq_nr,
+			"OK",
+			via);
+		SIP.send(tx_resp);
+	}
+	[fail_others] as_SIP_fail_resp(sip_expect_str);
+	[fail_others] as_SIP_fail_req(sip_expect_str);
+
+}
+
 }