/* 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";
const charstring COORD_CMD_CALL_FINISHED := "COORD_CMD_CALL_FINISHED";
const charstring COORD_CMD_UNREGISTER := "IMS_COORD_CMD_UNREGISTER";

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,
	Authorization authorization optional,
	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,

	From from_addr optional,
	To to_addr optional,

	CallidString sip_call_id,
	integer sip_seq_nr,
	charstring sip_body optional,

	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
}

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,
	exp_update_to_direct_rtp := true,
	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)
				})),
	authorization := 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_req.viaBody[0].sentProtocol.transport,
			    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(charstring dir := "sendrecv") 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 8\r\n" &
		"a=" & dir & "\r\n" &
		"a=rtpmap:8 PCMA/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 From from_addr := valueof(ts_From(g_pars.registrar_sip_record.addr, g_pars.registrar_sip_record.params));
	var To to_addr := valueof(ts_To(g_pars.registrar_sip_record.addr, g_pars.registrar_sip_record.params));
	var template (present) To to_addr_exp := tr_To(tr_Addr_Union_from_val(to_addr.addressField), *);
	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_addr.fromParams := f_sip_param_set(from_addr.fromParams, "tag", f_sip_rand_tag());
	SIP.send(ts_SIP_REGISTER(g_pars.registrar_sip_req_uri,
				 g_pars.registrar_sip_call_id,
				 from_addr,
				 to_addr,
				 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_addr,
			to_addr_exp,
			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 */
	g_pars.authorization :=
		f_sip_digest_gen_Authorization_MD5(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_addr,
				to_addr,
				via,
				g_pars.registrar_sip_seq_nr,
				g_pars.local_contact,
				ts_Expires("7200"),
				authorization := g_pars.authorization));

	/* Wait for OK answer */
	exp := tr_SIP_Response(
			g_pars.registrar_sip_call_id,
			from_addr,
			to_addr_exp,
			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_unregister() runs on SIPConnHdlr return PDU_SIP_Response
{
	var template (present) PDU_SIP_Response exp;
	var Via via := g_pars.local_via;
	var From from_addr := valueof(ts_From(g_pars.registrar_sip_record.addr, g_pars.registrar_sip_record.params));
	var To to_addr := valueof(ts_To(g_pars.registrar_sip_record.addr, g_pars.registrar_sip_record.params));
	var template (present) To to_addr_exp := tr_To(tr_Addr_Union_from_val(to_addr.addressField), *);
	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_addr.fromParams := f_sip_param_set(from_addr.fromParams, "tag", f_sip_rand_tag());
	SIP.clear;
	SIP.send(ts_SIP_REGISTER(g_pars.registrar_sip_req_uri,
				 g_pars.registrar_sip_call_id,
				 from_addr,
				 to_addr,
				 via,
				 g_pars.registrar_sip_seq_nr,
				 g_pars.local_contact,
				 ts_Expires("0"),
				 authorization := g_pars.authorization));
	alt {
	/* Wait for OK answer */
	[] SIP.receive(tr_SIP_Response(
		       g_pars.registrar_sip_call_id,
		       from_addr,
		       to_addr_exp,
		       f_tr_Via_response(via),
		       *,
		       "REGISTER", 200,
		       g_pars.registrar_sip_seq_nr, "OK")) -> value g_rx_sip_resp;

	/* May require re-auth: */
	[] SIP.receive(tr_SIP_Response_Unauthorized(
		       g_pars.registrar_sip_call_id,
		       from_addr,
		       to_addr_exp,
		       f_tr_Via_response(via),
		       *,
		       tr_WwwAuthenticate({tr_Challenge_digestCln(?)}),
		       g_pars.registrar_sip_seq_nr)) -> value g_rx_sip_resp {

		/* Digest Auth: RFC 2617 */
		g_pars.authorization :=
			f_sip_digest_gen_Authorization_MD5(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_addr,
					to_addr,
					via,
					g_pars.registrar_sip_seq_nr,
					g_pars.local_contact,
					ts_Expires("0"),
					authorization := g_pars.authorization));

		/* Wait for OK answer */
		exp := tr_SIP_Response(
				g_pars.registrar_sip_call_id,
				from_addr,
				to_addr_exp,
				f_tr_Via_response(via),
				*,
				"REGISTER", 200,
				g_pars.registrar_sip_seq_nr, "OK");
		as_SIP_expect_resp(exp);
		}
	[] as_SIP_fail_resp("(un)REGISTER 200 OK");
	[] as_SIP_fail_req("(un)REGISTER 200 OK");
	}

	/* Prepare for next use: */
	g_pars.registrar_sip_seq_nr := g_pars.registrar_sip_seq_nr + 1;
	return g_rx_sip_resp;
}

private function f_SIP_mo_call_tx_invite(Via via) runs on SIPConnHdlr
{
	var template (value) PDU_SIP_Request req;
	var charstring tx_sdp := f_gen_sdp();
	var template (present) PDU_SIP_Response exp;
	var template (present) From from_addr_exp;
	var template (present) To to_addr_exp;

	from_addr_exp := tr_From(tr_Addr_Union_from_val(g_pars.cp.from_addr.addressField), *);
	to_addr_exp := tr_To(tr_Addr_Union_from_val(g_pars.cp.to_addr.addressField), *);

	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,
			from_addr_exp,
			to_addr_exp,
			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_MD5(
						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);
}

function f_SIP_mo_call_setup(template (present) integer exp_status_code := 200,
			     template (present) charstring exp_reason := "OK",
			     boolean exp_update_to_direct_rtp := false) runs on SIPConnHdlr
{
	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, d_sessprog;
	var charstring branch_value;

	/* RFC 3261 8.1.1.3 From */
	g_pars.cp.from_addr := valueof(ts_From(g_pars.cp.calling.addr, g_pars.cp.calling.params));
	g_pars.cp.from_addr.fromParams := f_sip_param_set(g_pars.cp.from_addr.fromParams, "tag", f_sip_rand_tag());
	g_pars.cp.to_addr := valueof(ts_To(g_pars.cp.called.addr, g_pars.cp.called.params));
	from_addr_exp := tr_From(tr_Addr_Union_from_val(g_pars.cp.from_addr.addressField), *);
	to_addr_exp := tr_To(tr_Addr_Union_from_val(g_pars.cp.to_addr.addressField), *);
	branch_value := f_sip_gen_branch(f_sip_Addr_Union_to_str(g_pars.cp.from_addr.addressField),
					 f_sip_Addr_Union_to_str(valueof(g_pars.cp.to_addr.addressField)),
					 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);

	f_SIP_mo_call_tx_invite(via);

	/* Conditionally match and accept 100 Trying. */
	exp := tr_SIP_Response_Trying(g_pars.cp.sip_call_id,
			from_addr_exp,
			to_addr_exp,
			f_tr_Via_response(via),
			g_pars.cp.sip_seq_nr, "INVITE");
	d_trying := activate(as_SIP_ignore_resp(exp));

	/* Conditionally match and accept 183 Session Progress */
	exp := tr_SIP_Response_SessionProgress(g_pars.cp.sip_call_id,
			from_addr_exp,
			to_addr_exp,
			f_tr_Via_response(via),
			g_pars.cp.sip_seq_nr, "INVITE");
	d_sessprog := activate(as_SIP_ignore_resp(exp));

	/* Conditionally match and accept 180 Ringing */
	exp := tr_SIP_Response_Ringing(g_pars.cp.sip_call_id,
			from_addr_exp,
			to_addr_exp,
			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,
			from_addr_exp,
			to_addr_exp,
			f_tr_Via_response(via),
			*,
			"INVITE", exp_status_code,
			g_pars.cp.sip_seq_nr, exp_reason,
			body := *);
	as_SIP_expect_resp(exp, fail_others := false);

	deactivate(d_trying);
	deactivate(d_sessprog);
	deactivate(d_ringing);

	/* Update To with the tags received from peer: */
	g_pars.cp.to_addr := g_rx_sip_resp.msgHeader.toField;

	/* Transmit ACK */
	req := ts_SIP_ACK(g_pars.cp.to_addr.addressField.nameAddr.addrSpec,
			  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);

	if (exp_update_to_direct_rtp) {
		/* Asterisk will now update the session to connect us to MT directly: */
		/* Via is not kept since anyway "branch" will change upon following INVITE. */
		as_SIP_exp_mo_call_update(?);
	}

	g_pars.cp.sip_seq_nr := g_pars.cp.sip_seq_nr + 1;
}

/* New (re-)INVITE arrives after MO call is established. Accept it: */
altstep as_SIP_exp_mo_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),
						      ?,
						      tr_From(tr_Addr_Union_from_val(g_pars.cp.called.addr), *),
						      tr_To(tr_Addr_Union_from_val(g_pars.cp.calling.addr), *),
						      tr_Via_from(f_tr_HostPort(g_pars.remote_sip_host, g_pars.remote_sip_port)),
						      exp_seq_nr,
						      body := ?);
	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;

		f_SDP_decodeMessage(g_rx_sip_req.messageBody, g_pars.cp.peer_sdp);
		log("Rx Update MO INVITE decoded SDP: ", g_pars.cp.peer_sdp);

		/* Tx 200 OK */
		tx_sdp := f_gen_sdp();
		tx_resp := ts_SIP_Response(g_rx_sip_req.msgHeader.callId.callid,
					   g_rx_sip_req.msgHeader.fromField,
					   g_rx_sip_req.msgHeader.toField,
					   "INVITE", 200,
					   g_rx_sip_req.msgHeader.cSeq.seqNumber,
					   "OK",
					   g_rx_sip_req.msgHeader.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_rx_sip_req.msgHeader.callId.callid,
				      g_rx_sip_req.msgHeader.fromField,
				      g_rx_sip_req.msgHeader.toField,
				      f_tr_Via_response(g_rx_sip_req.msgHeader.via),
				      g_rx_sip_req.msgHeader.cSeq.seqNumber,
				      *);
		as_SIP_expect_req(exp_req);
	}
	[fail_others] as_SIP_fail_resp(sip_expect_str);
	[fail_others] as_SIP_fail_req(sip_expect_str);
}

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 := rx_sip_req.msgHeader.fromField;
	g_pars.cp.to_addr := rx_sip_req.msgHeader.toField;
	g_pars.cp.to_addr.toParams := f_sip_param_set(g_pars.cp.to_addr.toParams, "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),
			      ?,
			      tr_From(tr_Addr_Union_from_val(g_pars.cp.calling.addr), *),
			      tr_To(tr_Addr_Union_from_val(g_pars.cp.called.addr), *),
			      tr_Via_from(f_tr_HostPort(g_pars.remote_sip_host, g_pars.remote_sip_port)),
			      ?,
			      body := ?);
	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_mt_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),
			      ?,
			      tr_From(tr_Addr_Union_from_val(g_pars.cp.calling.addr), *),
			      tr_To(tr_Addr_Union_from_val(g_pars.cp.called.addr), *),
			      tr_Via_from(f_tr_HostPort(g_pars.remote_sip_host, g_pars.remote_sip_port)),
			      ?, body := ?);
	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) To 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 := g_rx_sip_req.msgHeader.toField;
		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_mt_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,
						      body := ?);
	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_Addr_Union_to_str(g_pars.cp.from_addr.addressField),
					 f_sip_Addr_Union_to_str(valueof(g_pars.cp.to_addr.addressField)),
					 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 BYE */
	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,
			tr_To(tr_Addr_Union_from_val(g_pars.cp.to_addr.addressField), *),
			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);
}

/* initiate HOLD Tx RE-INVITE (3GPP TS 24.610 A.1.1): */
private function f_SIP_do_call_holdresume(charstring dir := "sendonly",
					  template (present) SDP_attribute exp_resp_dir := tr_SDP_recvonly) runs on SIPConnHdlr
{
	var template (value) PDU_SIP_Request req;
	var template (present) PDU_SIP_Response exp_resp;
	var template (present) From from_addr_exp;
	var template (present) To to_addr_exp;
	var Via via;
	var charstring branch_value;
	var charstring tx_sdp := f_gen_sdp(dir := dir);

	/* Reuse g_pars.cp.from_addr and g_pars.cp.to_addr from established called */
	from_addr_exp := tr_From(tr_Addr_Union_from_val(g_pars.cp.from_addr.addressField), *);
	to_addr_exp := tr_To(tr_Addr_Union_from_val(g_pars.cp.to_addr.addressField), *);
	branch_value := f_sip_gen_branch(f_sip_Addr_Union_to_str(g_pars.cp.from_addr.addressField),
					 f_sip_Addr_Union_to_str(valueof(g_pars.cp.to_addr.addressField)),
					 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 re-INVITE (HOLD) */
	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);

	/* Wait for OK answer */
	exp_resp := tr_SIP_Response(
			g_pars.cp.sip_call_id,
			from_addr_exp,
			to_addr_exp,
			f_tr_Via_response(via),
			*,
			"INVITE", 200,
			g_pars.cp.sip_seq_nr, "OK",
			body := ?);
	as_SIP_expect_resp(exp_resp);
	f_SDP_decodeMessage(g_rx_sip_resp.messageBody, g_pars.cp.peer_sdp);

	/* Check a=$exp_resp_dir in SDP */
	if (not ispresent(g_pars.cp.peer_sdp.media_list[0].attributes) or
		not match (g_pars.cp.peer_sdp.media_list[0].attributes, superset(exp_resp_dir))) {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
					log2str(g_name & ": Expected 200 OK (re-INVITE) SDP attribute a=", exp_resp_dir, " in: ",
						g_pars.cp.peer_sdp.media_list[0].attributes));
	}

	/* Update To with the tags received from peer: */
	g_pars.cp.to_addr := g_rx_sip_resp.msgHeader.toField;

	/* Transmit ACK */
	req := ts_SIP_ACK(g_pars.cp.to_addr.addressField.nameAddr.addrSpec,
			  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;
}
function f_SIP_do_call_hold() runs on SIPConnHdlr
{
	f_SIP_do_call_holdresume(dir := "sendonly", exp_resp_dir := tr_SDP_recvonly);
}
function f_SIP_do_call_resume() runs on SIPConnHdlr
{
	f_SIP_do_call_holdresume(dir := "sendrecv", exp_resp_dir := tr_SDP_sendrecv);
}

}
