asterisk: Split SIPConnHdlr to its own file

Change-Id: I168920887bfd05c0a1785c5cb7ac485edaef0df2
diff --git a/asterisk/SIP_ConnectionHandler.ttcn b/asterisk/SIP_ConnectionHandler.ttcn
new file mode 100644
index 0000000..13b5f04
--- /dev/null
+++ b/asterisk/SIP_ConnectionHandler.ttcn
@@ -0,0 +1,707 @@
+/* Component implementing a SIP UA towards Asterisk
+ * (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ * All rights reserved.
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+module SIP_ConnectionHandler {
+
+import from TCCOpenSecurity_Functions all;
+import from General_Types all;
+import from Osmocom_Types all;
+import from Native_Functions all;
+import from Misc_Helpers all;
+
+import from SDP_Types all;
+import from SDP_Templates all;
+
+import from SIP_Emulation all;
+import from SIPmsg_Types all;
+import from SIP_Templates all;
+
+type port Coord_PT message
+{
+	inout charstring;
+} with { extension "internal" };
+
+const charstring COORD_CMD_REGISTERED := "COORD_CMD_REGISTERED";
+const charstring COORD_CMD_START := "COORD_CMD_START";
+const charstring COORD_CMD_PICKUP := "COORD_CMD_PICKUP";
+const charstring COORD_CMD_CALL_ESTABLISHED := "COORD_CMD_CALL_ESTABLISHED";
+const charstring COORD_CMD_CALL_CANCELLED := "COORD_CMD_CALL_CANCELLED";
+const charstring COORD_CMD_HANGUP := "COORD_CMD_HANGUP";
+
+type component SIPConnHdlr extends SIP_ConnHdlr {
+	var charstring g_name;
+	var SIPConnHdlrPars g_pars;
+	timer g_Tguard;
+	var PDU_SIP_Request g_rx_sip_req;
+	var PDU_SIP_Response g_rx_sip_resp;
+
+	port Coord_PT COORD;
+}
+type record of SIPConnHdlr SIPConnHdlrList;
+
+type record SIPConnHdlrPars {
+	float t_guard,
+	charstring remote_sip_host,
+	uint16_t remote_sip_port,
+	charstring user,
+	charstring display_name,
+	charstring password,
+	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,
+	CallPars cp optional
+}
+type record of SIPConnHdlrPars SIPConnHdlrParsList;
+
+type record CallParsMT {
+	/* Whether to wait for COORD.receive(COORD_CMD_PICKUP) before accepting the call. */
+	boolean wait_coord_cmd_pickup,
+	/* Whether to expect CANCEL instead of ACK as answer to our OK */
+	boolean exp_cancel
+}
+template (value) CallParsMT t_CallParsMT := {
+	wait_coord_cmd_pickup := false,
+	exp_cancel := false
+}
+
+type record CallPars {
+	SipAddr calling optional,
+	SipAddr called optional,
+
+	SipAddr from_addr optional,
+	SipAddr to_addr optional,
+
+	CallidString sip_call_id,
+	integer sip_seq_nr,
+	charstring sip_body optional,
+
+	charstring local_rtp_addr,
+	uint16_t local_rtp_port,
+
+	SDP_Message peer_sdp optional,
+	CallParsMT mt
+}
+
+template (value) CallPars t_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_CallParsMT
+}
+
+template (value) SIPConnHdlrPars t_Pars(charstring local_sip_host,
+					uint16_t local_sip_port,
+					charstring remote_sip_host,
+					uint16_t remote_sip_port,
+					charstring user,
+					charstring display_name := "Anonymous",
+					charstring password := "secret",
+					template (omit) CallPars cp := omit) := {
+	t_guard := 30.0,
+	remote_sip_host := remote_sip_host,
+	remote_sip_port := remote_sip_port,
+	user := user,
+	display_name := f_sip_str_quote(display_name),
+	password := password,
+	registrar_sip_req_uri := valueof(ts_SipUrlHost(remote_sip_host)),
+	registrar_sip_record := ts_SipAddr(ts_HostPort(remote_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 SIPConnHdlr {
+	[] g_Tguard.timeout {
+		setverdict(fail, "Tguard timeout");
+		mtc.stop;
+	}
+}
+
+type function void_fn(charstring id) runs on SIPConnHdlr;
+function f_handler_init(void_fn fn, charstring id, SIPConnHdlrPars pars)
+runs on SIPConnHdlr {
+	g_name := id;
+	g_pars := pars;
+	g_Tguard.start(pars.t_guard);
+	activate(as_Tguard());
+
+	// Make sure the UA is deregistered before starting the test:
+	// sends REGISTER with Contact = "*" and Expires = 0
+	//f_SIP_deregister();
+
+	/* call the user-supied test case function */
+	fn.apply(id);
+}
+
+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.remote_sip_port) },
+		{ id := "received", paramValue := g_pars.remote_sip_host }
+	}; */
+	return 	tr_Via_from(via_req.viaBody[0].sentBy,
+			    via_resp_params);
+}
+
+private function f_tr_To_response(template (value) SipAddr to_req) return template (present) SipAddr {
+	return tr_SipAddr_from_val(to_req);
+}
+
+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_fail_req(charstring exp_msg_str := "") runs on SIPConnHdlr
+{
+	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 SIPConnHdlr
+{
+	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));
+	}
+}
+
+altstep as_SIP_expect_req(template (present) PDU_SIP_Request sip_expect, boolean fail_others := true) runs on SIPConnHdlr
+{
+	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);
+}
+
+altstep as_SIP_expect_resp(template (present) PDU_SIP_Response sip_expect, boolean fail_others := true) runs on SIPConnHdlr
+{
+	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 SIPConnHdlr
+{
+	[] SIP.receive(sip_expect) -> value g_rx_sip_resp {
+		log("Ignoring ", g_rx_sip_resp);
+		repeat;
+	}
+}
+
+private function f_gen_sdp() runs on SIPConnHdlr return charstring {
+	var charstring sdp :=
+		"v=0\r\n" &
+		"o=0502 2390 1824 IN IP4 " & g_pars.cp.local_rtp_addr & "\r\n" &
+		"s=Talk\r\n" &
+		"c=IN IP4 " & g_pars.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.cp.local_rtp_port) & " RTP/AVP 96 97 98 0 8 18 99 100 101\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.cp.local_rtp_port + 1) & "\r\n" &
+		"a=rtcp-fb:* trr-int 1000\r\n" &
+		"a=rtcp-fb:* ccm tmmbr\r\n";
+	return sdp;
+}
+
+function f_SIP_register() runs on SIPConnHdlr return PDU_SIP_Response
+{
+	var template (present) PDU_SIP_Response exp;
+	var Authorization authorization;
+	var Via via := g_pars.local_via;
+	var SipAddr from_sipaddr := g_pars.registrar_sip_record;
+	var charstring branch_value;
+
+	branch_value := f_sip_gen_branch(f_sip_SipAddr_to_str(g_pars.registrar_sip_record),
+					 f_sip_SipAddr_to_str(g_pars.registrar_sip_record),
+					 g_pars.registrar_sip_call_id,
+					 g_pars.registrar_sip_seq_nr);
+
+	via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "branch", branch_value);
+	from_sipaddr.params := f_sip_param_set(from_sipaddr.params, "tag", f_sip_rand_tag());
+	SIP.send(ts_SIP_REGISTER(g_pars.registrar_sip_req_uri,
+				 g_pars.registrar_sip_call_id,
+				 from_sipaddr,
+				 g_pars.registrar_sip_record,
+				 via,
+				 g_pars.registrar_sip_seq_nr,
+				 g_pars.local_contact,
+				 ts_Expires("7200")));
+
+	exp := tr_SIP_Response_Unauthorized(
+			g_pars.registrar_sip_call_id,
+			from_sipaddr,
+			f_tr_To_response(g_pars.registrar_sip_record),
+			f_tr_Via_response(via),
+			*,
+			tr_WwwAuthenticate({tr_Challenge_digestCln(?)}),
+			g_pars.registrar_sip_seq_nr);
+	as_SIP_expect_resp(exp);
+
+	/* Digest Auth: RFC 2617 */
+	authorization := f_sip_digest_gen_Authorization(g_rx_sip_resp.msgHeader.wwwAuthenticate,
+							g_pars.user, g_pars.password,
+							"REGISTER",
+							f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri))
+
+	/* New transaction: */
+	g_pars.registrar_sip_seq_nr := g_pars.registrar_sip_seq_nr + 1;
+	branch_value := f_sip_gen_branch(f_sip_SipAddr_to_str(g_pars.registrar_sip_record),
+					 f_sip_SipAddr_to_str(g_pars.registrar_sip_record),
+					 g_pars.registrar_sip_call_id,
+					 g_pars.registrar_sip_seq_nr);
+	via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "branch", branch_value);
+
+	SIP.send(ts_SIP_REGISTER(g_pars.registrar_sip_req_uri,
+				g_pars.registrar_sip_call_id,
+				from_sipaddr,
+				g_pars.registrar_sip_record,
+				via,
+				g_pars.registrar_sip_seq_nr,
+				g_pars.local_contact,
+				ts_Expires("7200"),
+				authorization := authorization));
+
+	/* Wait for OK answer */
+	exp := tr_SIP_Response(
+			g_pars.registrar_sip_call_id,
+			from_sipaddr,
+			f_tr_To_response(g_pars.registrar_sip_record),
+			f_tr_Via_response(via),
+			*,
+			"REGISTER", 200,
+			g_pars.registrar_sip_seq_nr, "OK");
+	as_SIP_expect_resp(exp);
+
+	/* Prepare for next use: */
+	g_pars.registrar_sip_seq_nr := g_pars.registrar_sip_seq_nr + 1;
+	return g_rx_sip_resp;
+}
+
+function f_SIP_mo_call_setup() runs on SIPConnHdlr
+{
+	var template (value) PDU_SIP_Request req;
+	var template (present) PDU_SIP_Response exp;
+	var Via via;
+	var charstring tx_sdp := f_gen_sdp();
+	var default d_trying, d_ringing;
+	var charstring branch_value;
+
+	/* RFC 3261 8.1.1.3 From */
+	g_pars.cp.from_addr := g_pars.cp.calling;
+	g_pars.cp.from_addr.params := f_sip_param_set(g_pars.cp.from_addr.params, "tag", f_sip_rand_tag());
+	g_pars.cp.to_addr := g_pars.cp.called;
+	branch_value := f_sip_gen_branch(f_sip_SipAddr_to_str(g_pars.cp.from_addr),
+					 f_sip_SipAddr_to_str(valueof(g_pars.cp.to_addr)),
+					 g_pars.cp.sip_call_id,
+					 g_pars.cp.sip_seq_nr);
+	via := g_pars.local_via;
+	via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "branch", branch_value);
+
+	req := ts_SIP_INVITE(g_pars.cp.sip_call_id,
+			     g_pars.cp.from_addr,
+			     g_pars.cp.to_addr,
+			     via,
+			     g_pars.local_contact,
+			     g_pars.cp.sip_seq_nr,
+			     body := tx_sdp);
+
+	SIP.send(req);
+
+	/* RFC 3261 22.2: */
+	exp := tr_SIP_Response_Unauthorized(
+			g_pars.cp.sip_call_id,
+			f_tr_From(g_pars.cp.from_addr),
+			f_tr_To_response(g_pars.cp.to_addr),
+			f_tr_Via_response(via),
+			*,
+			tr_WwwAuthenticate({tr_Challenge_digestCln(?)}),
+			g_pars.cp.sip_seq_nr, "INVITE");
+	as_SIP_expect_resp(exp);
+
+	/* Digest Auth: RFC 2617 */
+	req.msgHeader.authorization := f_sip_digest_gen_Authorization(
+						g_rx_sip_resp.msgHeader.wwwAuthenticate,
+						g_pars.user, g_pars.password,
+						"INVITE",
+						f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri))
+	g_pars.cp.sip_seq_nr := g_pars.cp.sip_seq_nr + 1;
+	f_sip_Request_inc_seq_nr(req);
+	SIP.send(req);
+
+	/* Conditionally match and accept 100 Trying. */
+	exp := tr_SIP_Response_Trying(g_pars.cp.sip_call_id,
+			g_pars.cp.from_addr,
+			f_tr_To_response(g_pars.cp.to_addr),
+			f_tr_Via_response(via),
+			g_pars.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.cp.sip_call_id,
+			g_pars.cp.from_addr,
+			f_tr_To_response(g_pars.cp.to_addr),
+			f_tr_Via_response(via),
+			g_pars.cp.sip_seq_nr, "INVITE");
+	d_ringing := activate(as_SIP_ignore_resp(exp));
+
+	/* Wait for OK answer */
+	exp := tr_SIP_Response(
+			g_pars.cp.sip_call_id,
+			g_pars.cp.from_addr,
+			f_tr_To_response(g_pars.cp.to_addr),
+			f_tr_Via_response(via),
+			*,
+			"INVITE", 200,
+			g_pars.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.cp.to_addr := valueof(ts_SipAddr_from_Addr_Union(g_rx_sip_resp.msgHeader.toField.addressField,
+								g_rx_sip_resp.msgHeader.toField.toParams));
+
+	/* Transmit ACK */
+	g_pars.cp.sip_seq_nr := g_pars.cp.sip_seq_nr + 1;
+	req := ts_SIP_ACK(g_pars.cp.sip_call_id,
+			  g_pars.cp.from_addr,
+			  g_pars.cp.to_addr,
+			  via,
+			  g_pars.cp.sip_seq_nr,
+			  omit);
+	SIP.send(req);
+	g_pars.cp.sip_seq_nr := g_pars.cp.sip_seq_nr + 1;
+}
+
+private function f_ConnHdlr_parse_initial_SIP_INVITE(PDU_SIP_Request rx_sip_req) runs on SIPConnHdlr
+{
+	f_SDP_decodeMessage(rx_sip_req.messageBody, g_pars.cp.peer_sdp);
+	log("Rx Initial MT INVITE decoded SDP: ", g_pars.cp.peer_sdp);
+
+	/* Obtain params: */
+	g_pars.cp.sip_call_id := rx_sip_req.msgHeader.callId.callid;
+	g_pars.cp.from_addr := valueof(ts_SipAddr_from_Addr_Union(rx_sip_req.msgHeader.fromField.addressField,
+								rx_sip_req.msgHeader.fromField.fromParams));
+	g_pars.cp.to_addr := valueof(ts_SipAddr_from_Addr_Union(rx_sip_req.msgHeader.toField.addressField,
+								rx_sip_req.msgHeader.toField.toParams));
+	g_pars.cp.to_addr.params := f_sip_param_set(g_pars.cp.to_addr.params, "tag", f_sip_rand_tag());
+	g_pars.cp.sip_seq_nr := rx_sip_req.msgHeader.cSeq.seqNumber;
+}
+
+/* Peer is calling us, accept it: */
+altstep as_SIP_mt_call_accept(boolean exp_update_to_direct_rtp := true,
+			      boolean fail_others := true) runs on SIPConnHdlr
+{
+	var template (present) PDU_SIP_Request exp_req :=
+		tr_SIP_INVITE(f_tr_SipUrl_opt_defport(g_pars.local_sip_url_ext),
+			      ?,
+			      f_tr_From(g_pars.cp.calling),
+			      g_pars.cp.called,
+			      tr_Via_from(f_tr_HostPort(g_pars.remote_sip_host, g_pars.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.cp.sip_call_id,
+						g_pars.cp.from_addr,
+						g_pars.cp.to_addr,
+						via,
+						g_pars.cp.sip_seq_nr);
+		SIP.send(tx_resp);
+
+		if (g_pars.cp.mt.wait_coord_cmd_pickup) {
+			COORD.receive(COORD_CMD_PICKUP);
+		}
+
+		/* Tx 200 OK */
+		tx_sdp := f_gen_sdp();
+		tx_resp := ts_SIP_Response(g_pars.cp.sip_call_id,
+					g_pars.cp.from_addr,
+					g_pars.cp.to_addr,
+					"INVITE", 200,
+					g_pars.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(g_pars.local_sip_url_ext),
+				g_pars.cp.sip_call_id,
+				g_pars.cp.from_addr,
+				g_pars.cp.to_addr,
+				f_tr_Via_response(via),
+				g_pars.cp.sip_seq_nr, *);
+		as_SIP_expect_req(exp_req);
+
+		if (exp_update_to_direct_rtp) {
+			/* Asterisk will now update the session to connect us to MO directly: */
+			/* Via is not kept since anyway "branch" will change upon following INVITE. */
+			as_SIP_exp_call_update(g_pars.cp.sip_seq_nr + 1);
+		}
+	}
+	[fail_others] as_SIP_fail_resp(sip_expect_str);
+	[fail_others] as_SIP_fail_req(sip_expect_str);
+
+}
+
+/* Peer is calling us, but cancells it during ringing: */
+altstep as_SIP_mt_call_cancelled(boolean fail_others := true) runs on SIPConnHdlr
+{
+	var template (present) PDU_SIP_Request exp_req :=
+		tr_SIP_INVITE(f_tr_SipUrl_opt_defport(g_pars.local_sip_url_ext),
+			      ?,
+			      f_tr_From(g_pars.cp.calling),
+			      g_pars.cp.called,
+			      tr_Via_from(f_tr_HostPort(g_pars.remote_sip_host, g_pars.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 template (present) SipAddr exp_to_addr;
+		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.cp.sip_call_id,
+						g_pars.cp.from_addr,
+						g_pars.cp.to_addr,
+						via,
+						g_pars.cp.sip_seq_nr);
+		SIP.send(tx_resp);
+
+		if (g_pars.cp.mt.wait_coord_cmd_pickup) {
+			COORD.receive(COORD_CMD_PICKUP);
+		}
+
+		/* Wait for CANCEL */
+		/* Cancel may come even before we send Ringing, hence To's "tag"
+		 * may not be known by peer, so g_pars.to_addr can't be used here: */
+		exp_to_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.toField.addressField,
+							  g_rx_sip_req.msgHeader.toField.toParams);
+		exp_req := tr_SIP_CANCEL(f_tr_SipUrl_opt_defport(g_pars.local_sip_url_ext),
+					 g_pars.cp.sip_call_id,
+					 g_pars.cp.from_addr,
+					 exp_to_addr,
+					 f_tr_Via_response(via),
+					 g_pars.cp.sip_seq_nr, *);
+		as_SIP_expect_req(exp_req);
+
+		/* Tx 200 OK */
+		tx_sdp := f_gen_sdp();
+		tx_resp := ts_SIP_Response(g_pars.cp.sip_call_id,
+					   g_pars.cp.from_addr,
+					   g_pars.cp.to_addr,
+					   "CANCEL", 200,
+					   g_pars.cp.sip_seq_nr,
+					   "OK",
+					   via,
+					   body := omit);
+		SIP.send(tx_resp);
+	}
+	[fail_others] as_SIP_fail_resp(sip_expect_str);
+	[fail_others] as_SIP_fail_req(sip_expect_str);
+
+}
+
+/* New INVITE arrives after MT call is established. Accept it: */
+altstep as_SIP_exp_call_update(template (present) integer exp_seq_nr := ?, boolean fail_others := true) runs on SIPConnHdlr
+{
+	var template (present) PDU_SIP_Request exp_req :=
+		tr_SIP_INVITE(f_tr_SipUrl_opt_defport(g_pars.local_sip_url_ext),
+				 		      g_pars.cp.sip_call_id,
+				 		      g_pars.cp.from_addr,
+				 		      g_pars.cp.to_addr,
+				 		      tr_Via_from(f_tr_HostPort(g_pars.remote_sip_host, g_pars.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;
+
+		f_SDP_decodeMessage(g_rx_sip_req.messageBody, g_pars.cp.peer_sdp);
+		log("Rx Update MT INVITE decoded SDP: ", g_pars.cp.peer_sdp);
+
+		/* Update parameters: */
+		g_pars.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.cp.sip_call_id,
+					g_pars.cp.from_addr,
+					g_pars.cp.to_addr,
+					"INVITE", 200,
+					g_pars.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(g_pars.local_sip_url_ext),
+				g_pars.cp.sip_call_id,
+				g_pars.cp.from_addr,
+				g_pars.cp.to_addr,
+				f_tr_Via_response(via),
+				g_pars.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);
+}
+
+/* Tx BYE: */
+function f_SIP_do_call_hangup() runs on SIPConnHdlr
+{
+	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_SipAddr_to_str(g_pars.cp.from_addr),
+					 f_sip_SipAddr_to_str(valueof(g_pars.cp.to_addr)),
+					 g_pars.cp.sip_call_id,
+					 g_pars.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.cp.sip_call_id,
+			  g_pars.cp.from_addr,
+			  g_pars.cp.to_addr,
+			  via,
+			  g_pars.cp.sip_seq_nr,
+			  omit);
+	SIP.send(req);
+
+	/* Wait for OK answer */
+	exp_resp := tr_SIP_Response(
+			g_pars.cp.sip_call_id,
+			g_pars.cp.from_addr,
+			f_tr_To_response(g_pars.cp.to_addr),
+			f_tr_Via_response(via),
+			*,
+			"BYE", 200,
+			g_pars.cp.sip_seq_nr, "OK");
+	as_SIP_expect_resp(exp_resp);
+
+	g_pars.cp.sip_seq_nr := g_pars.cp.sip_seq_nr + 1;
+}
+
+/* Call is terminated by peer: */
+altstep as_SIP_exp_call_hangup(template (present) integer exp_seq_nr := ?, boolean fail_others := true) runs on SIPConnHdlr
+{
+	var template (present) PDU_SIP_Request exp_req :=
+		tr_SIP_BYE(f_tr_SipUrl_opt_defport(g_pars.local_sip_url_ext),
+				 		   g_pars.cp.sip_call_id,
+				 		   g_pars.cp.from_addr,
+				 		   g_pars.cp.to_addr,
+				 		   tr_Via_from(f_tr_HostPort(g_pars.remote_sip_host, g_pars.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.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.cp.sip_call_id,
+					g_pars.cp.from_addr,
+					g_pars.cp.to_addr,
+					"BYE", 200,
+					g_pars.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);
+}
+
+}