module SIP_Tests { /* osmo-sip-connector test suite in TTCN-3 * (C) 2018-2019 Harald Welte * 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 */ import from General_Types all; import from Osmocom_Types all; import from Native_Functions all; import from Misc_Helpers all; import from Osmocom_CTRL_Functions all; import from Osmocom_CTRL_Types all; import from Osmocom_CTRL_Adapter all; import from TELNETasp_PortType all; import from Osmocom_VTY_Functions all; import from MNCC_Emulation all; import from MNCC_Types 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; modulepar { charstring mp_local_host := "127.0.0.2"; charstring mp_osmosip_host := "127.0.0.1"; integer mp_osmosip_port_ctrl := -1; /* RFU */ charstring mp_mncc := "/tmp/mncc"; } type component test_CT extends CTRL_Adapter_CT { var MNCC_Emulation_CT vc_MNCC; var SIP_Emulation_CT vc_SIP; port TELNETasp_PT SIPVTY; } type component ConnHdlr extends SIP_ConnHdlr, MNCC_ConnHdlr { var ConnHdlrPars g_pars; timer g_Tguard; } type record ConnHdlrPars { float t_guard, CallPars g_cp optional } type record CallPars { boolean is_mo, charstring calling, charstring called, uint32_t mncc_call_id optional, CallParsComputed comp optional, charstring sip_rtp_addr, uint16_t sip_rtp_port, charstring cn_rtp_addr, uint16_t cn_rtp_port, /* Send SDP to MNCC, and expect to receive SDP from MNCC. mncc_with_sdp := false tests legacy compatibility to * the time when we did not include SDP in MNCC messages. mncc_with_sdp := true expects SDP to pass through the * SUT osmo-sip-connector unchanged. */ boolean mncc_with_sdp } type record CallParsComputed { CallidString sip_call_id, SipAddr sip_url_ext, SipAddr sip_url_gsm, charstring sip_body, integer sip_seq_nr } private template (value) CallPars t_CallPars(boolean is_mo, boolean mncc_with_sdp := true) := { is_mo := is_mo, calling := "12345", called := "98766", mncc_call_id := omit, comp := omit, sip_rtp_addr := "1.2.3.4", sip_rtp_port := 1234, cn_rtp_addr := "5.6.7.8", cn_rtp_port := 5678, mncc_with_sdp := mncc_with_sdp } private function f_CallPars_compute(inout CallPars cp) { if (cp.is_mo) { cp.comp.sip_url_ext := valueof(ts_SipAddr(ts_HostPort(mp_local_host, 5060), ts_UserInfo(cp.called))); cp.comp.sip_url_gsm := valueof(ts_SipAddr(ts_HostPort(mp_osmosip_host, 5060), ts_UserInfo(cp.calling))); cp.mncc_call_id := f_sip_rand_seq_nr(); } else { cp.comp.sip_url_ext := valueof(ts_SipAddr(ts_HostPort(mp_local_host, 5060), ts_UserInfo(cp.calling))); cp.comp.sip_url_gsm := valueof(ts_SipAddr(ts_HostPort(mp_osmosip_host, 5060), ts_UserInfo(cp.called))); cp.comp.sip_call_id := hex2str(f_rnd_hexstring(15)); } cp.comp.sip_seq_nr := f_sip_rand_seq_nr(); cp.comp.sip_body := ""; } function f_init_mncc(charstring id) runs on test_CT { id := id & "-MNCC"; var MnccOps ops := { create_cb := refers(MNCC_Emulation.ExpectedCreateCallback), unitdata_cb := refers(MNCC_Emulation.DummyUnitdataCallback) }; vc_MNCC := MNCC_Emulation_CT.create(id) alive; map(vc_MNCC:MNCC, system:MNCC_CODEC_PT); vc_MNCC.start(MNCC_Emulation.main(ops, id, mp_mncc, true)); } function f_init() runs on test_CT { //f_ipa_ctrl_start_client(mp_osmosip_host, mp_osmosip_port_ctrl); f_init_mncc("SIP_Test"); log("end of f_init_mncc"); f_init_sip(vc_SIP, "SIP_Test_SIP_EMU"); log("end of f_init_sip"); map(self:SIPVTY, system:SIPVTY); f_vty_set_prompts(SIPVTY); f_vty_transceive(SIPVTY, "enable"); log("end of f_init"); } type function void_fn(charstring id) runs on ConnHdlr; function f_start_handler(void_fn fn, ConnHdlrPars pars) runs on test_CT return ConnHdlr { var ConnHdlr vc_conn; var charstring id := testcasename(); vc_conn := ConnHdlr.create(id) alive; connect(vc_conn:SIP, vc_SIP:CLIENT); connect(vc_conn:SIP_PROC, vc_SIP:CLIENT_PROC); connect(vc_conn:MNCC, vc_MNCC:MNCC_CLIENT); connect(vc_conn:MNCC_PROC, vc_MNCC:MNCC_PROC); vc_conn.start(f_handler_init(fn, id, pars)); return vc_conn; } private altstep as_Tguard() runs on ConnHdlr { [] g_Tguard.timeout { setverdict(fail, "Tguard timeout"); mtc.stop; } } private function f_handler_init(void_fn fn, charstring id, ConnHdlrPars pars) runs on ConnHdlr { g_pars := pars; g_Tguard.start(pars.t_guard); activate(as_Tguard()); /* call the user-supied test case function */ fn.apply(id); } template (value) ConnHdlrPars t_Pars := { t_guard := 30.0, g_cp := omit } altstep as_SIP_expect_resp(template PDU_SIP_Response sip_expect) runs on ConnHdlr { [] SIP.receive(sip_expect); [] SIP.receive { log("FAIL: expected SIP message ", sip_expect); Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Received unexpected SIP message"); } } function f_SIP_expect_req(template PDU_SIP_Request sip_expect) runs on ConnHdlr return PDU_SIP_Request { var PDU_SIP_Request rx; alt { [] SIP.receive(sip_expect) -> value rx; [] SIP.receive { log("FAIL: expected SIP message ", sip_expect); Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Received unexpected SIP message"); } } return rx; } /* Update 'last_sdp', and match with expectation of what the current SDP should be. * Useful to ensure that MNCC or SIP send and possibly resend only the expected SDP. * last_sdp keeps the last non-empty rx_sdp, across multiple check_sdp() invocations. * rx_sdp is the SDP charstring just received. If it is nonempty, update last_sdp to rx_sdp. * After updating last_sdp as appropriate, match last_sdp with expect_sdp. */ private function check_sdp(inout charstring last_sdp, charstring rx_sdp, template charstring expect_sdp) { /* If there is new SDP, store it. */ if (lengthof(rx_sdp) > 0) { if (last_sdp != rx_sdp) { log("SDP update from ", last_sdp, " to ", rx_sdp); } /* If MNCC sent SDP data, remember it as the last valid SDP */ last_sdp := rx_sdp; } /* Validate expectations of the SDP data */ if (not match(last_sdp, expect_sdp)) { log("FAIL: expected SDP ", expect_sdp, " but got ", last_sdp); Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "unexpected SDP"); } } /* Establish a mobile terminated call described in 'cp' */ function f_establish_mt(inout CallPars cp) runs on ConnHdlr { var template (value) From from_addr := ts_From(cp.comp.sip_url_ext.addr, cp.comp.sip_url_ext.params); var template (value) To to_addr := ts_To(cp.comp.sip_url_gsm.addr, cp.comp.sip_url_gsm.params); var template (value) Via via := ts_Via_from(cp.comp.sip_url_ext.addr.nameAddr.addrSpec.hostPort); var template (present) From from_addr_exp := tr_From(tr_Addr_Union_from_val(cp.comp.sip_url_ext.addr), *); var template (present) To to_addr_exp := tr_To(tr_Addr_Union_from_val(cp.comp.sip_url_gsm.addr), *); var template (present) Via via_exp := tr_Via_from(f_tr_HostPort_opt_defport(from_addr_exp.addressField.nameAddr.addrSpec.hostPort)); var MNCC_PDU mncc; /* The last SDP that the MSC received via MNCC from osmo-sip-connector */ var charstring sdp_to_msc := ""; /* At first, allow any empty and nonempty SDP. As the test progresses, this may expect specific SDP instead. */ var template charstring expect_sdp_to_msc := *; /* If cp.mncc_with_sdp == true, expect SDP forwarding like this: * * SDP1: SIP agent's RTP and codec info * SDP2: osmo-msc's RTP and codec info * * MNCC osmo-sip-connector SIP * |<--SDP1----- SIP Invite * |-----------> SIP (Invite) Trying * <--SDP1-------| MNCC SETUP req * ------------->| MNCC CALL CONF ind * <-------------| MNCC RTP CREATE (SDP optional, still unchanged from SDP1) * -------SDP2-->| MNCC RTP CREATE * ------------->| MNCC ALERT ind * |--------------> SIP (Invite) Ringing * (MT picks up) | * ------------->| MNCC SETUP CNF * <-------------| MNCC RTP CONNECT (SDP optional, still unchanged from SDP1) * |--------SDP2--> SIP (Invite) OK * |<-------------- SIP ACK * <-------------| MNCC SETUP COMPL (SDP optional, still unchanged from SDP1) */ /* Ask MNCC_Emulation to "expect" a call to the given called number */ f_create_mncc_expect(cp.called); /* OSC <- SIP: A party sends SIP invite for a MT-call into OSC */ SIP.send(ts_SIP_INVITE(cp.comp.sip_call_id, from_addr, to_addr, via, ts_Contact_SipAddr(cp.comp.sip_url_ext), cp.comp.sip_seq_nr, body := cp.comp.sip_body)); if (cp.mncc_with_sdp) { /* We just sent SDP via SIP, now expect the same SDP in MNCC to the MSC */ expect_sdp_to_msc := cp.comp.sip_body; } /* OSC -> SIP */ as_SIP_expect_resp(tr_SIP_Response(cp.comp.sip_call_id, from_addr_exp, to_addr_exp, via_exp, *, "INVITE", 100, ?, "Trying", *)); alt { /* MSC <- OSC: OSC generates MNCC_SETUP_REQ from INVITE */ [] MNCC.receive(tr_MNCC_SETUP_req) -> value mncc { cp.mncc_call_id := mncc.u.signal.callref; /* Expect the SDP sent via SIP to arrive in MNCC */ check_sdp(sdp_to_msc, mncc.u.signal.sdp, expect_sdp_to_msc); } [] SIP.receive { setverdict(fail, "Received unexpected SIP response"); SIP.send(ts_SIP_ACK(cp.comp.sip_call_id, from_addr, to_addr, via, cp.comp.sip_seq_nr, omit)); mtc.stop; } } /* MSC -> OSC: After MS sends CALL CONF in response to SETUP */ MNCC.send(ts_MNCC_CALL_CONF_ind(cp.mncc_call_id)); /* MSC <- OSC: OSC asks MSC to create RTP socket */ MNCC.receive(tr_MNCC_RTP_CREATE(cp.mncc_call_id)) -> value mncc { check_sdp(sdp_to_msc, mncc.u.rtp.sdp, expect_sdp_to_msc); } /* MSC -> OSC: SDP that the MSC will send via MNCC */ var charstring cn_sdp := "v=0\r\no=Osmocom 0 0 IN IP4 1.1.1.1\r\ns=GSM Call\r\nc=IN " & f_sdp_addr2addrtype(cp.cn_rtp_addr) & " " & cp.cn_rtp_addr & "\r\nt=0 0\r\nm=audio " & int2str(cp.cn_rtp_port) & " RTP/AVP 0\r\na=rtpmap:0 GSM/8000\r\n"; /* OSC -> SIP: what SDP to expect in SIP from osmo-sip-connector */ var template charstring expect_sdp_to_sip := pattern "*" & cp.cn_rtp_addr & "*"; mncc := valueof(ts_MNCC_RTP_CREATE(cp.mncc_call_id)); mncc.u.rtp.is_ipv6 := f_addr_is_ipv6(cp.cn_rtp_addr); mncc.u.rtp.ip := f_addrstr2addr(cp.cn_rtp_addr); mncc.u.rtp.rtp_port := cp.cn_rtp_port; if (cp.mncc_with_sdp) { /* MSC -> OSC: tell OSC our RTP info in SDP form */ mncc.u.rtp.sdp := cn_sdp; /* OSC -> SIP: and expect it unchanged on SIP later, but allow osmo-sip-connector to append an * "a=sendrecv;" */ expect_sdp_to_sip := pattern cn_sdp & "*"; } MNCC.send(mncc); /* MSC -> OSC: After MS is ringing and sent CC ALERTING */ MNCC.send(ts_MNCC_ALERT_ind(cp.mncc_call_id)); /* Now expect SIP response "Ringing" back to MO, containing the same SDP information as in the MNCC RTP CREATE * sent to OSC above */ SIP.clear; /* 180 Ringing should not contain any SDP. */ as_SIP_expect_resp(tr_SIP_Response(cp.comp.sip_call_id, from_addr_exp, to_addr_exp, via_exp, *, "INVITE", 180, ?, "Ringing", omit)); /* MSC -> OSC: After MT user has picked up and sent CC CONNECT */ MNCC.send(ts_MNCC_SETUP_CNF(cp.mncc_call_id)); SIP.clear; /* MSC <- OSC: OSC asks MSC to connect its RTP stream to remote end */ MNCC.receive(tr_MNCC_RTP_CONNECT(cp.mncc_call_id, f_addrstr2addr(cp.sip_rtp_addr), cp.sip_rtp_port)) -> value mncc { check_sdp(sdp_to_msc, mncc.u.rtp.sdp, expect_sdp_to_msc); } /* OSC -> SIP: OSC confirms call establishment to SIP side */ as_SIP_expect_resp(tr_SIP_Response(cp.comp.sip_call_id, from_addr_exp, to_addr_exp, via_exp, contact := ?, method := "INVITE", status_code := 200, seq_nr := ?, reason := "OK", body := expect_sdp_to_sip)); /* OSC <- SIP: SIP world acknowledges "200 OK" */ SIP.send(ts_SIP_ACK(cp.comp.sip_call_id, from_addr, to_addr, via, cp.comp.sip_seq_nr, omit)); /* MSC <- OSC: OSC sends SETUP COMPL to MNCC (which triggers CC CONNECT ACK */ MNCC.receive(tr_MNCC_SETUP_COMPL_req(cp.mncc_call_id)) -> value mncc { check_sdp(sdp_to_msc, mncc.u.signal.sdp, expect_sdp_to_msc); } } /* Establish a mobile originated call described in 'cp' */ function f_establish_mo(inout CallPars cp) runs on ConnHdlr { var MNCC_number dst := valueof(ts_MNCC_number(cp.called, GSM48_TON_UNKNOWN)); var MNCC_number src := valueof(ts_MNCC_number(cp.calling, GSM48_TON_UNKNOWN)); var template (value) From from_addr := ts_From(cp.comp.sip_url_gsm.addr, cp.comp.sip_url_gsm.params); var template (value) To to_addr := ts_To(cp.comp.sip_url_ext.addr, cp.comp.sip_url_ext.params); var template (value) Via via := ts_Via_from(cp.comp.sip_url_gsm.addr.nameAddr.addrSpec.hostPort); var template (present) From from_addr_exp := tr_From(tr_Addr_Union_from_val(cp.comp.sip_url_gsm.addr), *); var template (present) To to_addr_exp := tr_To(tr_Addr_Union_from_val(cp.comp.sip_url_ext.addr), *); var template (present) Via via_exp := tr_Via_from(f_tr_HostPort_opt_defport(from_addr_exp.addressField.nameAddr.addrSpec.hostPort)); var PDU_SIP_Request sip_req; var integer seq_nr; var MNCC_PDU mncc; /* The last SDP that the MSC received via MNCC from osmo-sip-connector */ var charstring sdp_to_msc := ""; /* At first, allow any empty and nonempty SDP. As the test progresses, this may expect specific SDP instead. */ var template charstring expect_sdp_to_msc := *; /* If cp.mncc_with_sdp == true, expect SDP forwarding like this: * * SDP1: osmo-msc's RTP and codec info * SDP2: SIP agent's RTP and codec info * * MNCC osmo-sip-connector SIP * -------SDP1-->| MNCC SETUP ind * <-------------| MNCC RTP CREATE (?) * |-----SDP1--> SIP Invite * |<----------- SIP (Invite) Trying * <-------------| MNCC CALL PROC req * |<----------- SIP (Invite) Ringing * <-------------| MNCC ALERT req * | (MT picks up) * |<--SDP2----- SIP (Invite) OK * <--SDP2-------| MNCC RTP CONNECT (SDP optional, still unchanged from SDP2) * <-------------| MNCC SETUP rsp (SDP optional, still unchanged from SDP2) * ------------->| MNCC SETUP COMPL ind (SDP optional, still unchanged from SDP1) * |------------> SIP ACK */ var charstring cn_sdp := "v=0\r\no=Osmocom 0 0 IN IP4 1.1.1.1\r\ns=GSM Call\r\nc=IN " & f_sdp_addr2addrtype(cp.cn_rtp_addr) & " " & cp.cn_rtp_addr & "\r\nt=0 0\r\nm=audio " & int2str(cp.cn_rtp_port) & " RTP/AVP 0\r\na=rtpmap:0 GSM/8000\r\n"; f_create_sip_expect(cp.comp.sip_url_ext.addr.nameAddr.addrSpec); /* MSC -> OSC: MSC sends SETUP.ind after CC SETUP was received from MS */ mncc := valueof(ts_MNCC_SETUP_ind(cp.mncc_call_id, dst, src, "262420123456789")); if (cp.mncc_with_sdp) { mncc.u.signal.sdp := cn_sdp; } MNCC.send(mncc); /* MSC <- OSC: Create GSM side RTP socket */ MNCC.receive(tr_MNCC_RTP_CREATE(cp.mncc_call_id)) { mncc := valueof(ts_MNCC_RTP_CREATE(cp.mncc_call_id)); mncc.u.rtp.payload_msg_type := oct2int('0300'O); /* FIXME: makes no sense to send cp.cn_rtp_addr back to the cn. */ mncc.u.rtp.is_ipv6 := f_addr_is_ipv6(cp.cn_rtp_addr); mncc.u.rtp.ip := f_addrstr2addr(cp.cn_rtp_addr); mncc.u.rtp.rtp_port := cp.cn_rtp_port; MNCC.send(mncc); } /* OSC -> SIP: Send INVITE with GSM side IP/Port in SDP */ var template charstring expect_sdp_to_sip := ?; if (cp.mncc_with_sdp) { /* Expect the same SDP as sent to osmo-sip-connector in MNCC, and allow osmo-sip-connector to append an * "a=sendrecv;" */ expect_sdp_to_sip := pattern cn_sdp & "*"; } sip_req := f_SIP_expect_req(tr_SIP_INVITE(to_addr_exp.addressField.nameAddr.addrSpec, ?, from_addr_exp, to_addr_exp, via_exp, ?, body := expect_sdp_to_sip)); from_addr.fromParams := sip_req.msgHeader.fromField.fromParams; cp.comp.sip_call_id := sip_req.msgHeader.callId.callid; seq_nr := sip_req.msgHeader.cSeq.seqNumber; /* OSC <- SIP: Notify call is proceeding */ SIP.send(ts_SIP_Response(cp.comp.sip_call_id, from_addr, to_addr, "INVITE", 100, seq_nr, "Trying", sip_req.msgHeader.via)); /* MSC <- OSC: "100 Trying" translated to MNCC_CALL_PROC_REQ */ MNCC.receive(tr_MNCC_CALL_PROC_req(cp.mncc_call_id)) -> value mncc { check_sdp(sdp_to_msc, mncc.u.signal.sdp, ""); } /* OSC <- SIP: SIP-terminated user is ringing now. 180 Ringing should not contain any SDP. */ SIP.send(ts_SIP_Response(cp.comp.sip_call_id, from_addr, to_addr, "INVITE", 180, seq_nr, "Ringing", sip_req.msgHeader.via, omit)); /* MSC <- OSC: "180 Ringing" translated to MNCC_ALERT_REQ */ MNCC.receive(tr_MNCC_ALERT_req(cp.mncc_call_id)) -> value mncc { check_sdp(sdp_to_msc, mncc.u.signal.sdp, expect_sdp_to_msc); } /* OSC <- SIP: SIP-terminated user has accepted the call */ SIP.send(ts_SIP_Response(cp.comp.sip_call_id, from_addr, to_addr, "INVITE", 200, seq_nr, "OK", sip_req.msgHeader.via, body := cp.comp.sip_body)); if (cp.mncc_with_sdp) { /* If we expect SDP forwarding, from now on expect MNCC to reflect the SDP that we just sent on SIP. */ expect_sdp_to_msc := cp.comp.sip_body; } /* If we don't expect SDP forwarding, just keep expect_sdp_to_msc := *. */ MNCC.receive(tr_MNCC_RTP_CONNECT(cp.mncc_call_id)) -> value mncc { check_sdp(sdp_to_msc, mncc.u.rtp.sdp, expect_sdp_to_msc); } /* MSC <- OSC: "200 OK" translated to MNCC_SETUP_RSP */ MNCC.receive(tr_MNCC_SETUP_rsp(cp.mncc_call_id)) -> value mncc { check_sdp(sdp_to_msc, mncc.u.signal.sdp, expect_sdp_to_msc); } /* MSC -> OSC: CC CONNECT ACK was received from MS */ MNCC.send(ts_MNCC_SETUP_COMPL_ind(cp.mncc_call_id)); /* OSC -> SIP: Acknowledge the call */ SIP.receive(tr_SIP_ACK(to_addr_exp.addressField.nameAddr.addrSpec, cp.comp.sip_call_id, from_addr_exp, to_addr_exp, via_exp, ?, omit)); } /* Release call from the mobile side */ function f_release_mobile(inout CallPars cp) runs on ConnHdlr { var template (value) From from_addr := ts_From(cp.comp.sip_url_gsm.addr, cp.comp.sip_url_gsm.params); var template (value) To to_addr := ts_To(cp.comp.sip_url_ext.addr, cp.comp.sip_url_ext.params); var template (value) Via via := ts_Via_from(cp.comp.sip_url_gsm.addr.nameAddr.addrSpec.hostPort); var template (present) From from_addr_exp := tr_From(tr_Addr_Union_from_val(cp.comp.sip_url_gsm.addr), *); var template (present) To to_addr_exp := tr_To(tr_Addr_Union_from_val(cp.comp.sip_url_ext.addr), *); var template (present) Via via_exp := tr_Via_from(f_tr_HostPort_opt_defport(from_addr_exp.addressField.nameAddr.addrSpec.hostPort)); var PDU_SIP_Request sip_req; SIP.clear; /* MSC -> OSC: Simulate a CC DISCONNET from the MT user */ MNCC.send(ts_MNCC_DISC_ind(cp.mncc_call_id, ts_MNCC_cause(0))); /* OSC -> SIP: Expect BYE from OSC to SIP side */ sip_req := f_SIP_expect_req(tr_SIP_BYE(to_addr_exp.addressField.nameAddr.addrSpec, cp.comp.sip_call_id, from_addr_exp, to_addr_exp, via_exp, ?, *)); from_addr.fromParams := sip_req.msgHeader.fromField.fromParams; /* OSC <- SIP: Acknowledge the BYE */ SIP.send(ts_SIP_Response(cp.comp.sip_call_id, from_addr, to_addr, "BYE", 200, sip_req.msgHeader.cSeq.seqNumber, "OK", sip_req.msgHeader.via)); /* MSC <- OSC: Send REL_REQ to MSC, triggers CC RELEASE REQ to MS */ MNCC.receive(tr_MNCC_REL_req(cp.mncc_call_id)); // CAUSE? /* MSC -> OSC: MS has responded with CC CLEAR COMPL, triggers MNCC_REL_CNF */ MNCC.send(ts_MNCC_REL_cnf(cp.mncc_call_id, ts_MNCC_cause(0))); } /* Release call from the SIP side */ function f_release_sip(inout CallPars cp) runs on ConnHdlr { var template (value) From from_addr := ts_From(cp.comp.sip_url_ext.addr, cp.comp.sip_url_ext.params); var template (value) To to_addr := ts_To(cp.comp.sip_url_gsm.addr, cp.comp.sip_url_gsm.params); var template (value) Via via := ts_Via_from(cp.comp.sip_url_ext.addr.nameAddr.addrSpec.hostPort); var template (present) From from_addr_exp := tr_From(tr_Addr_Union_from_val(cp.comp.sip_url_ext.addr), *); var template (present) To to_addr_exp := tr_To(tr_Addr_Union_from_val(cp.comp.sip_url_gsm.addr), *); var template (present) Via via_exp := tr_Via_from(f_tr_HostPort_opt_defport(from_addr_exp.addressField.nameAddr.addrSpec.hostPort)); /* OSC <- SIP: SIP-side sends a BYE to OSC */ SIP.send(ts_SIP_BYE(cp.comp.sip_call_id, from_addr, to_addr, via, cp.comp.sip_seq_nr, omit)); /* MSC <- OSC: Expect OSC to cause MNCC Disconnect Request */ MNCC.receive(tr_MNCC_DISC_req(cp.mncc_call_id)); /* MSC -> OSC: Indicate GSM side release */ MNCC.send(ts_MNCC_REL_ind(cp.mncc_call_id, ts_MNCC_cause(0))); /* OSC -> SIP: Confirmation to SIP side */ as_SIP_expect_resp(tr_SIP_Response(cp.comp.sip_call_id, from_addr_exp, to_addr_exp, via_exp, *, "BYE", 200, cp.comp.sip_seq_nr, "OK", omit)); } /* Successful MT Call, which is subsequently released by GSM side */ private function f_TC_mt_success_rel_gsm(charstring id) runs on ConnHdlr { var CallPars cp := g_pars.g_cp; f_CallPars_compute(cp); cp.comp.sip_body := "v=0\r\no=Osmocom 0 0 IN IP4 1.1.1.1\r\ns=GSM Call\r\nc=IN " & f_sdp_addr2addrtype(cp.sip_rtp_addr) & " " & cp.sip_rtp_addr & "\r\nt=0 0\r\nm=audio " & int2str(cp.sip_rtp_port) & " RTP/AVP 0\r\na=rtpmap:0 GSM/8000\r\n"; f_sleep(3.0) f_establish_mt(cp); /* now call is fully established */ f_sleep(2.0); f_release_mobile(cp); setverdict(pass); } testcase TC_mt_success_rel_gsm() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(false, false)); vc_conn := f_start_handler(refers(f_TC_mt_success_rel_gsm), pars); vc_conn.done; } testcase TC_mt_success_rel_gsm_ipv6() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(false, false)); pars.g_cp.sip_rtp_addr := "::1"; pars.g_cp.cn_rtp_addr := "::2"; vc_conn := f_start_handler(refers(f_TC_mt_success_rel_gsm), pars); vc_conn.done; } /* Successful MT Call, which is subsequently released by SIP side */ private function f_TC_mt_success_rel_sip(charstring id) runs on ConnHdlr { var CallPars cp := g_pars.g_cp; f_CallPars_compute(cp); cp.comp.sip_body := "v=0\r\no=Osmocom 0 0 IN IP4 1.1.1.1\r\ns=GSM Call\r\nc=IN " & f_sdp_addr2addrtype(cp.sip_rtp_addr) & " " & cp.sip_rtp_addr & "\r\nt=0 0\r\nm=audio " & int2str(cp.sip_rtp_port) & " RTP/AVP 0\r\na=rtpmap:0 GSM/8000\r\n"; f_sleep(3.0) f_establish_mt(cp); /* now call is fully established */ f_sleep(2.0); f_release_sip(cp); setverdict(pass); } testcase TC_mt_success_rel_sip() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(false, false)); vc_conn := f_start_handler(refers(f_TC_mt_success_rel_sip), pars); vc_conn.done; } /* Successful MO Call, which is subsequently released by GSM side */ private function f_TC_mo_success_rel_gsm(charstring id) runs on ConnHdlr { var CallPars cp := g_pars.g_cp; f_CallPars_compute(cp); cp.comp.sip_body := "v=0\r\no=Osmocom 0 0 IN IP4 1.1.1.1\r\ns=GSM Call\r\nc=IN " & f_sdp_addr2addrtype(cp.sip_rtp_addr) & " " & cp.sip_rtp_addr & "\r\nt=0 0\r\nm=audio " & int2str(cp.sip_rtp_port) & " RTP/AVP 0\r\na=rtpmap:0 GSM/8000\r\n"; f_sleep(3.0) f_establish_mo(cp); /* now call is fully established */ f_sleep(2.0); f_release_mobile(cp); setverdict(pass); } testcase TC_mo_success_rel_gsm() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(true, false)); vc_conn := f_start_handler(refers(f_TC_mo_success_rel_gsm), pars); vc_conn.done; } testcase TC_mo_success_rel_gsm_ipv6() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(true, false)); pars.g_cp.sip_rtp_addr := "::1"; pars.g_cp.cn_rtp_addr := "::2"; vc_conn := f_start_handler(refers(f_TC_mo_success_rel_gsm), pars); vc_conn.done; } /* Successful MO Call, which is subsequently released by SIP side */ private function f_TC_mo_success_rel_sip(charstring id) runs on ConnHdlr { var CallPars cp := g_pars.g_cp; f_CallPars_compute(cp); cp.comp.sip_body := "v=0\r\no=Osmocom 0 0 IN IP4 1.1.1.1\r\ns=GSM Call\r\nc=IN " & f_sdp_addr2addrtype(cp.sip_rtp_addr) & " " & cp.sip_rtp_addr & "\r\nt=0 0\r\nm=audio " & int2str(cp.sip_rtp_port) & " RTP/AVP 0\r\na=rtpmap:0 GSM/8000\r\n"; f_sleep(3.0) f_establish_mo(cp); /* now call is fully established */ f_sleep(2.0); f_release_sip(cp); setverdict(pass); } testcase TC_mo_success_rel_sip() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(is_mo := true, mncc_with_sdp := false)); vc_conn := f_start_handler(refers(f_TC_mo_success_rel_sip), pars); vc_conn.done; } /* SETUP followed by DISC results in lingering B-leg (OS#3518)*/ private function f_TC_mo_setup_disc_late_rtp(charstring id) runs on ConnHdlr { var CallPars cp := g_pars.g_cp; f_CallPars_compute(cp); cp.comp.sip_body := "v=0\r\no=Osmocom 0 0 IN IP4 1.1.1.1\r\ns=GSM Call\r\nc=IN " & f_sdp_addr2addrtype(cp.sip_rtp_addr) & " " & cp.sip_rtp_addr & "\r\nt=0 0\r\nm=audio " & int2str(cp.sip_rtp_port) & " RTP/AVP 0\r\na=rtpmap:0 GSM/8000\r\n"; f_sleep(3.0); var MNCC_number dst := valueof(ts_MNCC_number(cp.called, GSM48_TON_UNKNOWN)); var MNCC_number src := valueof(ts_MNCC_number(cp.calling, GSM48_TON_UNKNOWN)); var template (value) From from_addr := ts_From(cp.comp.sip_url_gsm.addr, cp.comp.sip_url_gsm.params); var template (value) To to_addr := ts_To(cp.comp.sip_url_ext.addr, cp.comp.sip_url_ext.params); var template (value) Via via := ts_Via_from(cp.comp.sip_url_gsm.addr.nameAddr.addrSpec.hostPort); var template (present) From from_addr_exp := tr_From(tr_Addr_Union_from_val(cp.comp.sip_url_gsm.addr), *); var template (present) To to_addr_exp := tr_To(tr_Addr_Union_from_val(cp.comp.sip_url_ext.addr), *); var template (present) Via via_exp := tr_Via_from(f_tr_HostPort_opt_defport(from_addr_exp.addressField.nameAddr.addrSpec.hostPort)); f_create_sip_expect(cp.comp.sip_url_ext.addr.nameAddr.addrSpec); /* MSC -> OSC: MSC sends SETUP.ind after CC SETUP was received from MS */ MNCC.send(ts_MNCC_SETUP_ind(cp.mncc_call_id, dst, src, "262420123456789")); /* MSC -> OSC: Simulate a CC DISCONNET from the MT user *before* responding to the RTP_CREATE */ MNCC.send(ts_MNCC_DISC_ind(cp.mncc_call_id, ts_MNCC_cause(0))); /* MSC <- OSC: Create GSM side RTP socket (too late) */ MNCC.receive(tr_MNCC_RTP_CREATE(cp.mncc_call_id)) { var MNCC_PDU mncc := valueof(ts_MNCC_RTP_CREATE(cp.mncc_call_id)); mncc.u.rtp.payload_msg_type := oct2int('0300'O); mncc.u.rtp.is_ipv6 := f_addr_is_ipv6(cp.cn_rtp_addr); mncc.u.rtp.ip := f_addrstr2addr(cp.cn_rtp_addr); mncc.u.rtp.rtp_port := cp.cn_rtp_port; MNCC.send(mncc); } /* OSC -> SIP: We should never receive INVITE */ timer T := 10.0; T.start; alt { [] SIP.receive(tr_SIP_INVITE(to_addr_exp.addressField.nameAddr.addrSpec, ?, from_addr_exp, to_addr_exp, via_exp, ?, ?)) { setverdict(fail, "Received unexpected INVITE"); } [] T.timeout { setverdict(pass); } } } testcase TC_mo_setup_disc_late_rtp() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(is_mo := true, mncc_with_sdp := false)); vc_conn := f_start_handler(refers(f_TC_mo_setup_disc_late_rtp), pars); vc_conn.done; } testcase TC_mt_with_sdp() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(is_mo := false, mncc_with_sdp := true)); vc_conn := f_start_handler(refers(f_TC_mt_success_rel_gsm), pars); vc_conn.done; } testcase TC_mo_with_sdp() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars); pars.g_cp := valueof(t_CallPars(is_mo := true, mncc_with_sdp := true)); vc_conn := f_start_handler(refers(f_TC_mo_success_rel_sip), pars); vc_conn.done; } control { execute( TC_mt_success_rel_gsm() ); execute( TC_mt_success_rel_gsm_ipv6() ); execute( TC_mt_success_rel_sip() ); execute( TC_mo_success_rel_gsm() ); execute( TC_mo_success_rel_gsm_ipv6() ); execute( TC_mo_success_rel_sip() ); execute( TC_mo_setup_disc_late_rtp() ); execute( TC_mt_with_sdp() ); execute( TC_mo_with_sdp() ); } }