/* Component implementing a SIP UA towards Asterisk * (C) 2024 by sysmocom - s.f.m.c. GmbH * Author: Pau Espin Pedrol * 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); } }