sip: Add SIP_Emulation and first osmo-sip-connector test case
Change-Id: Ifd47b0d48c609b4a678ea47aa7f89f5c12e6c0d2
diff --git a/library/MNCC_Emulation.ttcn b/library/MNCC_Emulation.ttcn
index 60ec58b..856575e 100644
--- a/library/MNCC_Emulation.ttcn
+++ b/library/MNCC_Emulation.ttcn
@@ -193,7 +193,7 @@
}
-function f_connect(charstring sock) runs on MNCC_Emulation_CT {
+private function f_connect(charstring sock) runs on MNCC_Emulation_CT {
var UD_connect_result res;
timer T := 5.0;
@@ -215,6 +215,40 @@
}
}
+private function f_listen(charstring sock) runs on MNCC_Emulation_CT {
+ var UD_listen_result res;
+ var UD_connected udc;
+ timer T := 5.0;
+
+ T.start;
+ MNCC.send(UD_listen:{sock});
+ alt {
+ [] MNCC.receive(UD_listen_result:?) -> value res {
+ if (ispresent(res.result) and ispresent(res.result.result_code) and res.result.result_code == ERROR) {
+ setverdict(fail, "Error listening to MNCC socket", res);
+ self.stop;
+ } else {
+ g_mncc_ud_id := res.id;
+ }
+ }
+ [] T.timeout {
+ setverdict(fail, "Timeout listening to MNCC socket");
+ self.stop;
+ }
+ }
+
+ T.start;
+ alt {
+ [] MNCC.receive(UD_connected:?) -> value udc {
+ g_mncc_ud_id := res.id;
+ }
+ [] T.timeout {
+ setverdict(fail, "Timeout waiting for MNCC connection");
+ self.stop;
+ }
+ }
+}
+
/* call-back type, to be provided by specific implementation; called when new SCCP connection
* arrives */
type function MnccCreateCallback(MNCC_PDU conn_ind, charstring id)
@@ -228,9 +262,14 @@
MnccUnitdataCallback unitdata_cb
}
-function main(MnccOps ops, charstring id, charstring sock) runs on MNCC_Emulation_CT {
+function main(MnccOps ops, charstring id, charstring sock, boolean role_server := false)
+runs on MNCC_Emulation_CT {
- f_connect(sock);
+ if (role_server) {
+ f_listen(sock);
+ } else {
+ f_connect(sock);
+ }
f_expect_table_init();
f_call_table_init();
@@ -289,15 +328,19 @@
f_call_table_del(call_id);
}
- /* Client -> MNCC Socket: SETUP.req: forward + add call table entry */
- [] MNCC_CLIENT.receive(MNCC_PDU:{msg_type := MNCC_SETUP_REQ, u:=?}) -> value mncc sender vc_conn {
- /* add to call table */
- f_call_table_add(vc_conn, f_mncc_get_call_id(mncc));
+ /* Client -> MNCC Socket: Normal message */
+ [] MNCC_CLIENT.receive(MNCC_PDU:?) -> value mncc sender vc_conn {
+ if (mncc.msg_type == MNCC_SETUP_REQ and not role_server) {
+ /* ConnHdlr -> MNCC Server: SETUP.req: add to call table */
+ f_call_table_add(vc_conn, f_mncc_get_call_id(mncc));
+ } else if (mncc.msg_type == MNCC_SETUP_IND and role_server) {
+ /* ConnHdlr -> MNCC Client: SETUP.ind: add to call table */
+ f_call_table_add(vc_conn, f_mncc_get_call_id(mncc));
+ }
/* forward to MNCC socket */
MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
}
- /* Client -> MNCC Socket: Normal message */
[] MNCC_CLIENT.receive(MNCC_PDU:?) -> value mncc sender vc_conn {
/* forward to MNCC socket */
MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
diff --git a/library/MNCC_Types.ttcn b/library/MNCC_Types.ttcn
index 001b4b8..8d47efb 100644
--- a/library/MNCC_Types.ttcn
+++ b/library/MNCC_Types.ttcn
@@ -561,7 +561,38 @@
lchan_mode := ?
}
}
-
+}
+template (value) MNCC_PDU ts_MNCC_SETUP_ind(uint32_t call_id, MNCC_number called,
+ template (omit) MNCC_number calling := omit,
+ template (omit) charstring imsi := omit,
+ template (value) MNCC_bearer_cap bcap := ts_MNCC_bcap_voice) := {
+ msg_type := MNCC_SETUP_IND,
+ u := {
+ signal := { /* See 24.008 9.3.23.2 */
+ callref := call_id,
+ bearer_cap := valueof(bcap), /* mandatory */
+ called := called, /* mandatory */
+ calling := calling, /* optional */
+ redirecting := omit,
+ connected := omit,
+ cause := omit,
+ progress := omit,
+ useruser := omit, /* optional */
+ facility := omit, /* optional */
+ cccap := omit, /* optional */
+ ssversion := omit, /* optional */
+ clir_sup := 0, /* optional */
+ clir_inv := 0, /* optional */
+ signal := omit,
+ keypad := omit,
+ more := 0,
+ notify := 0,
+ emergency := omit,
+ imsi := imsi,
+ lchan_type := 0,
+ lchan_mode := 0
+ }
+ }
}
/* MO: MSC <- MNCC: SETUP.cnf; Response to SETUP.ind */
diff --git a/library/SIP_Emulation.ttcn b/library/SIP_Emulation.ttcn
new file mode 100644
index 0000000..57ec704
--- /dev/null
+++ b/library/SIP_Emulation.ttcn
@@ -0,0 +1,408 @@
+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 <laforge@gnumonks.org>
+ * All rights reserved.
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ */
+
+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;
+};
+
+private function f_sip_init() runs on SIP_Emulation_CT {
+ map(self:SIP, system:SIP);
+}
+
+template RequestLine tr_ReqLine(template Method method) := {
+ method := method,
+ requestUri := ?,
+ sipVersion := ?
+}
+
+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);
+ self.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);
+ self.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;
+ }
+ }
+ setverdict(fail, "SIP Call table full");
+ self.stop;
+}
+
+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);
+ self.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);
+ map(ct:SIP, system:SIP);
+ ct.start(SIP_Emulation.main(ops, id));
+}
+
+function main(SipOps ops, charstring id)
+runs on SIP_Emulation_CT {
+
+ f_sip_init();
+ 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);
+ self.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);
+ self.stop;
+ }
+ }
+
+ /* 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) {
+ f_create_expect(sip_to, vc_hdlr);
+ CLIENT_PROC.reply(SIPEM_register:{sip_to, vc_hdlr});
+ }
+
+ }
+ }
+}
+
+/***********************************************************************
+ * "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");
+ 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 := *;
+ }
+ 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);
+ 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;
+ }
+ }
+ setverdict(fail, "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:{?,?}) {};
+ }
+}
+
+
+
+}