module SIP_Emulation { /* SIP Emulation, runs on top of SIPmsg_PT. It multiplexes/demultiplexes * the individual calls, so there can be separate TTCN-3 components handling * each of the calls * * The SIP_Emulation.main() function processes SIP message from the SIPmsg * socket via the SIPmsg_PT, and dispatches them to the per-connection components. * * Outbound SIP calls are initiated by sending a PDU_SIP_Request messages * to the component running the SIP_Emulation.main() function. * * For each new inbound call, the SipOps.create_cb() is called. It can create * or resolve a TTCN-3 component, and returns a component reference to which that inbound * call is routed/dispatched. * * If a pre-existing component wants to register to handle a future inbound call, it can * do so by registering an "expect" with the expected destination phone number. This is e.g. useful * if you are simulating MNCC + SIP, and first trigger a connection from MNCC side in a * component which then subsequently should also handle the SIP emulation. * * (C) 2018 by 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 SIPmsg_Types all; import from SIPmsg_PortType all; type component SIP_ConnHdlr { /* ports towards SIP Emulation core / call dispatcher */ port SIP_Conn_PT SIP; port SIPEM_PROC_PT SIP_PROC; } /* port between individual per-call components and this dispatcher */ type port SIP_Conn_PT message { inout PDU_SIP_Request, PDU_SIP_Response; } with { extension "internal" }; /* represents a single SIP Call */ type record CallData { /* reference to the instance of the per-connection component */ SIP_ConnHdlr comp_ref, CallidString call_id } type component SIP_Emulation_CT { /* SIP test port on bottom side */ port SIPmsg_PT SIP; /* SIP port to the per-call clients */ port SIP_Conn_PT CLIENT; var CallData SipCallTable[16]; var ExpectData SipExpectTable[16]; /* procedure based port to register for incoming connections */ port SIPEM_PROC_PT CLIENT_PROC; }; template RequestLine tr_ReqLine(template Method method) := { method := method, requestUri := ?, sipVersion := ? } private template PDU_SIP_Request tr_SIP_REGISTER := { requestLine := tr_ReqLine(REGISTER_E), msgHeader := t_SIP_msgHeader_any, messageBody := *, payload := * } private template PDU_SIP_Request tr_SIP_INVITE := { requestLine := tr_ReqLine(INVITE_E), msgHeader := t_SIP_msgHeader_any, messageBody := *, payload := * } template SipUrl tr_SIP_Url(template charstring user_or_num, template charstring host := *, template integer portField := *) := { scheme := "sip", userInfo := { userOrTelephoneSubscriber := user_or_num, password := * }, hostPort := { host := host, portField := portField }, urlParameters := *, headers := * } template (value) SipUrl ts_SIP_Url(charstring user_or_num, template (omit) charstring host := omit, template (omit) integer portField := omit) := { scheme := "sip", userInfo := { userOrTelephoneSubscriber := user_or_num, password := omit }, hostPort := { host := host, portField := portField }, urlParameters := omit, headers := omit } template Addr_Union tr_SIP_Addr(template SipUrl sip_url) := { nameAddr := { displayName := *, addrSpec := sip_url } } template (value) Addr_Union ts_SIP_Addr(template (value) SipUrl sip_url) := { nameAddr := { displayName := omit, addrSpec := sip_url } } template To tr_SIP_To(template Addr_Union addr) := { fieldName := TO_E, addressField := addr, toParams := * } template (value) To ts_SIP_To(template (value) Addr_Union addr) := { fieldName := TO_E, addressField := addr, toParams := omit } /* resolve component reference by connection ID */ private function f_call_id_known(CallidString call_id) runs on SIP_Emulation_CT return boolean { var integer i; for (i := 0; i < sizeof(SipCallTable); i := i+1) { if (SipCallTable[i].call_id == call_id) { return true; } } return false; } /* resolve component reference by connection ID */ private function f_comp_by_call_id(CallidString call_id) runs on SIP_Emulation_CT return SIP_ConnHdlr { var integer i; for (i := 0; i < sizeof(SipCallTable); i := i+1) { if (SipCallTable[i].call_id == call_id) { return SipCallTable[i].comp_ref; } } setverdict(fail, "SIP Call table not found by SIP Call ID ", call_id); mtc.stop; } /* resolve connection ID by component reference */ private function f_call_id_by_comp(SIP_ConnHdlr client) runs on SIP_Emulation_CT return CallidString { for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) { if (SipCallTable[i].comp_ref == client) { return SipCallTable[i].call_id; } } setverdict(fail, "SIP Call table not found by component ", client); mtc.stop; } private function f_expect_table_init() runs on SIP_Emulation_CT { for (var integer i := 0; i < sizeof(SipExpectTable); i := i+1) { SipExpectTable[i].sip_to := omit; SipExpectTable[i].vc_conn := null; } } private function f_call_table_init() runs on SIP_Emulation_CT { for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) { SipCallTable[i].comp_ref := null; SipCallTable[i].call_id := ""; } } private function f_call_table_add(SIP_ConnHdlr comp_ref, CallidString call_id) runs on SIP_Emulation_CT { for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) { if (SipCallTable[i].call_id == "") { SipCallTable[i].comp_ref := comp_ref; SipCallTable[i].call_id := call_id; log("Added SIP Call Table entry [", i, "] for ", call_id, " at ", comp_ref); return; } } testcase.stop("SIP Call table full"); } private function f_call_table_del(CallidString call_id) runs on SIP_Emulation_CT { for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) { if (SipCallTable[i].call_id == call_id) { SipCallTable[i].comp_ref := null; SipCallTable[i].call_id := ""; log("Deleted SIP Call Table entry [", i, "] for ", call_id); return; } } setverdict(fail, "SIP Call table attempt to delete non-existant ", call_id); mtc.stop; } /* call-back type, to be provided by specific implementation; called when new call connection * arrives */ type function SipCreateCallback(PDU_SIP_Request sip_req, charstring id) runs on SIP_Emulation_CT return SIP_ConnHdlr; type record SipOps { SipCreateCallback create_cb }; function f_init_sip(inout SIP_Emulation_CT ct, charstring id) { id := id & "-SIP"; var SipOps ops := { create_cb := refers(SIP_Emulation.ExpectedCreateCallback) }; ct := SIP_Emulation_CT.create(id) alive; map(ct:SIP, system:SIP); ct.start(SIP_Emulation.main(ops, id)); } function main(SipOps ops, charstring id) runs on SIP_Emulation_CT { f_expect_table_init(); f_call_table_init(); while (true) { var SIP_ConnHdlr vc_hdlr, vc_conn; var PDU_SIP_Request sip_req; var PDU_SIP_Response sip_resp; var SipUrl sip_to; alt { /* SIP INVITE was received on SIP socket/port */ [] SIP.receive(tr_SIP_INVITE) -> value sip_req { var CallidString call_id := sip_req.msgHeader.callId.callid; if (f_call_id_known(call_id)) { /* re-invite? */ vc_conn := f_comp_by_call_id(call_id); } else { /* new INVITE: check expect */ vc_conn := ops.create_cb.apply(sip_req, id); f_call_table_add(vc_conn, call_id); } CLIENT.send(sip_req) to vc_conn; } /* other SIP request was received on SIP socket/port */ [] SIP.receive(PDU_SIP_Request:?) -> value sip_req { var CallidString call_id := sip_req.msgHeader.callId.callid; if (f_call_id_known(call_id)) { vc_conn := f_comp_by_call_id(call_id); CLIENT.send(sip_req) to vc_conn; } else { setverdict(fail, "SIP Request for unknown call ", call_id); mtc.stop; } } /* SIP response was received on SIP socket/port */ [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp { var CallidString call_id := sip_resp.msgHeader.callId.callid; if (f_call_id_known(call_id)) { vc_conn := f_comp_by_call_id(call_id); CLIENT.send(sip_resp) to vc_conn; } else { setverdict(fail, "SIP Response for unknown call ", call_id); mtc.stop; } } /* a ConnHdlr is sending us a SIP REGISTER: Forward to SIP port */ [] CLIENT.receive(tr_SIP_REGISTER) -> value sip_req sender vc_conn { var CallidString call_id := sip_req.msgHeader.callId.callid; if (f_call_id_known(call_id)) { /* re-register */ vc_conn := f_comp_by_call_id(call_id); } else { /* new REGISTER: add to table */ f_call_table_add(vc_conn, call_id); } SIP.send(sip_req); } /* a ConnHdlr is sending us a SIP INVITE: Forward to SIP port */ [] CLIENT.receive(tr_SIP_INVITE) -> value sip_req sender vc_conn { var CallidString call_id := sip_req.msgHeader.callId.callid; if (f_call_id_known(call_id)) { /* re-invite? */ vc_conn := f_comp_by_call_id(call_id); } else { /* new INVITE: add to table */ f_call_table_add(vc_conn, call_id); } SIP.send(sip_req); } /* a ConnHdlr is sending us a SIP request: Forward to SIP port */ [] CLIENT.receive(PDU_SIP_Request:?) -> value sip_req sender vc_conn { SIP.send(sip_req); } /* a ConnHdlr is sending us a SIP request: Forward to SIP port */ [] CLIENT.receive(PDU_SIP_Response:?) -> value sip_resp sender vc_conn { SIP.send(sip_resp); } [] CLIENT_PROC.getcall(SIPEM_register:{?,?}) -> param(sip_to, vc_hdlr) sender vc_conn { f_create_expect(sip_to, vc_hdlr); CLIENT_PROC.reply(SIPEM_register:{sip_to, vc_hdlr}) to vc_conn; } } } } /*********************************************************************** * "Expect" Handling (mapping for expected incoming SIP callds from IUT) ***********************************************************************/ /* data about an expected future incoming connection */ type record ExpectData { /* SIP "To" (destination number) based on which we can match */ SipUrl sip_to optional, /* component reference registered for the connection */ SIP_ConnHdlr vc_conn } /* procedure based port to register for incoming calls */ signature SIPEM_register(SipUrl sip_to, SIP_ConnHdlr vc_conn); type port SIPEM_PROC_PT procedure { inout SIPEM_register; } with { extension "internal" }; /* CreateCallback that can be used as create_cb and will use the expect table */ function ExpectedCreateCallback(PDU_SIP_Request sip_req, charstring id) runs on SIP_Emulation_CT return SIP_ConnHdlr { var SIP_ConnHdlr ret := null; var SipUrl sip_to; var integer i; if (sip_req.requestLine.method != INVITE_E) { setverdict(fail, "SIP ExpectedCreateCallback needs INVITE"); mtc.stop return ret; } sip_to := sip_req.msgHeader.toField.addressField.nameAddr.addrSpec; for (i := 0; i < sizeof(SipExpectTable); i := i+1) { if (not ispresent(SipExpectTable[i].sip_to)) { continue; } /* build a template, use '*' for all 'omit' values */ var template SipUrl t_exp := SipExpectTable[i].sip_to; if (not ispresent(t_exp.hostPort.host)) { t_exp.hostPort.host := *; } if (not ispresent(t_exp.hostPort.portField)) { t_exp.hostPort.portField := *; } else if (valueof(t_exp.hostPort.portField) == 5060) { /* if the port number is 5060, it may be omitted */ t_exp.hostPort.portField := 5060 ifpresent; } if (not ispresent(t_exp.urlParameters)) { t_exp.urlParameters := *; } if (not ispresent(t_exp.headers)) { t_exp.headers := *; } /* match against the constructed template */ if (match(sip_to, t_exp)) { ret := SipExpectTable[i].vc_conn; /* release this entry to be used again */ SipExpectTable[i].sip_to := omit; SipExpectTable[i].vc_conn := null; log("Found SipExpect[", i, "] for ", sip_to, " handled at ", ret); return ret; } } setverdict(fail, "Couldn't find SipExpect for incoming call ", sip_to); mtc.stop return ret; } /* server/emulation side function to create expect */ private function f_create_expect(SipUrl sip_to, SIP_ConnHdlr hdlr) runs on SIP_Emulation_CT { var integer i; for (i := 0; i < sizeof(SipExpectTable); i := i+1) { if (not ispresent(SipExpectTable[i].sip_to)) { SipExpectTable[i].sip_to := sip_to; SipExpectTable[i].vc_conn := hdlr; log("Created SipExpect[", i, "] for ", sip_to, " to be handled at ", hdlr); return; } } testcase.stop("No space left in SipExpectTable"); } /* client/conn_hdlr side function to use procedure port to create expect in emulation */ function f_create_sip_expect(SipUrl sip_to) runs on SIP_ConnHdlr { SIP_PROC.call(SIPEM_register:{sip_to, self}) { [] SIP_PROC.getreply(SIPEM_register:{?,?}) {}; } } }