blob: e71c611b90d8854c2bd97bbf7a8d992186ebe080 [file] [log] [blame]
Harald Welteafec4712018-03-19 22:52:17 +01001module SIP_Emulation {
2
3/* SIP Emulation, runs on top of SIPmsg_PT. It multiplexes/demultiplexes
4 * the individual calls, so there can be separate TTCN-3 components handling
5 * each of the calls
6 *
7 * The SIP_Emulation.main() function processes SIP message from the SIPmsg
8 * socket via the SIPmsg_PT, and dispatches them to the per-connection components.
9 *
10 * Outbound SIP calls are initiated by sending a PDU_SIP_Request messages
11 * to the component running the SIP_Emulation.main() function.
12 *
13 * For each new inbound call, the SipOps.create_cb() is called. It can create
14 * or resolve a TTCN-3 component, and returns a component reference to which that inbound
15 * call is routed/dispatched.
16 *
17 * If a pre-existing component wants to register to handle a future inbound call, it can
18 * do so by registering an "expect" with the expected destination phone number. This is e.g. useful
19 * if you are simulating MNCC + SIP, and first trigger a connection from MNCC side in a
20 * component which then subsequently should also handle the SIP emulation.
21 *
22 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
23 * All rights reserved.
24 *
25 * Released under the terms of GNU General Public License, Version 2 or
26 * (at your option) any later version.
Harald Welte34b5a952019-05-27 11:54:11 +020027 *
28 * SPDX-License-Identifier: GPL-2.0-or-later
Harald Welteafec4712018-03-19 22:52:17 +010029 */
30
31import from SIPmsg_Types all;
32import from SIPmsg_PortType all;
33
34type component SIP_ConnHdlr {
35 /* ports towards SIP Emulation core / call dispatcher */
36 port SIP_Conn_PT SIP;
37 port SIPEM_PROC_PT SIP_PROC;
38}
39
40/* port between individual per-call components and this dispatcher */
41type port SIP_Conn_PT message {
42 inout PDU_SIP_Request, PDU_SIP_Response;
43} with { extension "internal" };
44
45/* represents a single SIP Call */
46type record CallData {
47 /* reference to the instance of the per-connection component */
48 SIP_ConnHdlr comp_ref,
49 CallidString call_id
50}
51
52type component SIP_Emulation_CT {
53 /* SIP test port on bottom side */
54 port SIPmsg_PT SIP;
55 /* SIP port to the per-call clients */
56 port SIP_Conn_PT CLIENT;
57
58 var CallData SipCallTable[16];
59 var ExpectData SipExpectTable[16];
60
61 /* procedure based port to register for incoming connections */
62 port SIPEM_PROC_PT CLIENT_PROC;
63};
64
65private function f_sip_init() runs on SIP_Emulation_CT {
66 map(self:SIP, system:SIP);
67}
68
69template RequestLine tr_ReqLine(template Method method) := {
70 method := method,
71 requestUri := ?,
72 sipVersion := ?
73}
74
Harald Welteb0d93602018-03-20 18:09:34 +010075private template PDU_SIP_Request tr_SIP_INVITE := {
Harald Welteafec4712018-03-19 22:52:17 +010076 requestLine := tr_ReqLine(INVITE_E),
77 msgHeader := t_SIP_msgHeader_any,
78 messageBody := *,
79 payload := *
80}
81
82
83template SipUrl tr_SIP_Url(template charstring user_or_num,
84 template charstring host := *,
85 template integer portField := *) := {
86 scheme := "sip",
87 userInfo := {
88 userOrTelephoneSubscriber := user_or_num,
89 password := *
90 },
91 hostPort := {
92 host := host,
93 portField := portField
94 },
95 urlParameters := *,
96 headers := *
97}
98template (value) SipUrl ts_SIP_Url(charstring user_or_num,
99 template (omit) charstring host := omit,
100 template (omit) integer portField := omit) := {
101 scheme := "sip",
102 userInfo := {
103 userOrTelephoneSubscriber := user_or_num,
104 password := omit
105 },
106 hostPort := {
107 host := host,
108 portField := portField
109 },
110 urlParameters := omit,
111 headers := omit
112}
113
114template Addr_Union tr_SIP_Addr(template SipUrl sip_url) := {
115 nameAddr := {
116 displayName := *,
117 addrSpec := sip_url
118 }
119}
120template (value) Addr_Union ts_SIP_Addr(template (value) SipUrl sip_url) := {
121 nameAddr := {
122 displayName := omit,
123 addrSpec := sip_url
124 }
125}
126
127template To tr_SIP_To(template Addr_Union addr) := {
128 fieldName := TO_E,
129 addressField := addr,
130 toParams := *
131}
132template (value) To ts_SIP_To(template (value) Addr_Union addr) := {
133 fieldName := TO_E,
134 addressField := addr,
135 toParams := omit
136}
137
138/* resolve component reference by connection ID */
139private function f_call_id_known(CallidString call_id)
140runs on SIP_Emulation_CT return boolean {
141 var integer i;
142 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
143 if (SipCallTable[i].call_id == call_id) {
144 return true;
145 }
146 }
147 return false;
148}
149
150/* resolve component reference by connection ID */
151private function f_comp_by_call_id(CallidString call_id)
152runs on SIP_Emulation_CT return SIP_ConnHdlr {
153 var integer i;
154 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
155 if (SipCallTable[i].call_id == call_id) {
156 return SipCallTable[i].comp_ref;
157 }
158 }
159 setverdict(fail, "SIP Call table not found by SIP Call ID ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200160 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100161}
162
163/* resolve connection ID by component reference */
164private function f_call_id_by_comp(SIP_ConnHdlr client)
165runs on SIP_Emulation_CT return CallidString {
166 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
167 if (SipCallTable[i].comp_ref == client) {
168 return SipCallTable[i].call_id;
169 }
170 }
171 setverdict(fail, "SIP Call table not found by component ", client);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200172 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100173}
174
175private function f_expect_table_init()
176runs on SIP_Emulation_CT {
177 for (var integer i := 0; i < sizeof(SipExpectTable); i := i+1) {
178 SipExpectTable[i].sip_to := omit;
179 SipExpectTable[i].vc_conn := null;
180 }
181}
182
183private function f_call_table_init()
184runs on SIP_Emulation_CT {
185 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
186 SipCallTable[i].comp_ref := null;
187 SipCallTable[i].call_id := "";
188 }
189}
190
191private function f_call_table_add(SIP_ConnHdlr comp_ref, CallidString call_id)
192runs on SIP_Emulation_CT {
193 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
194 if (SipCallTable[i].call_id == "") {
195 SipCallTable[i].comp_ref := comp_ref;
196 SipCallTable[i].call_id := call_id;
197 log("Added SIP Call Table entry [", i, "] for ", call_id, " at ", comp_ref);
198 return;
199 }
200 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200201 testcase.stop("SIP Call table full");
Harald Welteafec4712018-03-19 22:52:17 +0100202}
203
204private function f_call_table_del(CallidString call_id)
205runs on SIP_Emulation_CT {
206 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
207 if (SipCallTable[i].call_id == call_id) {
208 SipCallTable[i].comp_ref := null;
209 SipCallTable[i].call_id := "";
210 log("Deleted SIP Call Table entry [", i, "] for ", call_id);
211 return;
212 }
213 }
214 setverdict(fail, "SIP Call table attempt to delete non-existant ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200215 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100216}
217
218/* call-back type, to be provided by specific implementation; called when new call connection
219 * arrives */
220type function SipCreateCallback(PDU_SIP_Request sip_req, charstring id)
221runs on SIP_Emulation_CT return SIP_ConnHdlr;
222
223type record SipOps {
224 SipCreateCallback create_cb
225};
226
227function f_init_sip(inout SIP_Emulation_CT ct, charstring id) {
228 id := id & "-SIP";
229
230 var SipOps ops := {
231 create_cb := refers(SIP_Emulation.ExpectedCreateCallback)
232 };
233
234 ct := SIP_Emulation_CT.create(id);
235 map(ct:SIP, system:SIP);
236 ct.start(SIP_Emulation.main(ops, id));
237}
238
239function main(SipOps ops, charstring id)
240runs on SIP_Emulation_CT {
241
242 f_sip_init();
243 f_expect_table_init();
244 f_call_table_init();
245
246 while (true) {
247 var SIP_ConnHdlr vc_hdlr, vc_conn;
248 var PDU_SIP_Request sip_req;
249 var PDU_SIP_Response sip_resp;
250 var SipUrl sip_to;
251
252 alt {
253 /* SIP INVITE was received on SIP socket/port */
254 [] SIP.receive(tr_SIP_INVITE) -> value sip_req {
255 var CallidString call_id := sip_req.msgHeader.callId.callid;
256 if (f_call_id_known(call_id)) {
257 /* re-invite? */
258 vc_conn := f_comp_by_call_id(call_id);
259 } else {
260 /* new INVITE: check expect */
261 vc_conn := ops.create_cb.apply(sip_req, id);
262 f_call_table_add(vc_conn, call_id);
263 }
264 CLIENT.send(sip_req) to vc_conn;
265 }
266 /* other SIP request was received on SIP socket/port */
267 [] SIP.receive(PDU_SIP_Request:?) -> value sip_req {
268 var CallidString call_id := sip_req.msgHeader.callId.callid;
269 if (f_call_id_known(call_id)) {
270 vc_conn := f_comp_by_call_id(call_id);
271 CLIENT.send(sip_req) to vc_conn;
272 } else {
273 setverdict(fail, "SIP Request for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200274 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100275 }
276 }
277 /* SIP response was received on SIP socket/port */
278 [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp {
279 var CallidString call_id := sip_resp.msgHeader.callId.callid;
280 if (f_call_id_known(call_id)) {
281 vc_conn := f_comp_by_call_id(call_id);
282 CLIENT.send(sip_resp) to vc_conn;
283 } else {
284 setverdict(fail, "SIP Response for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200285 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100286 }
287 }
288
289 /* a ConnHdlr is sending us a SIP INVITE: Forward to SIP port */
290 [] CLIENT.receive(tr_SIP_INVITE) -> value sip_req sender vc_conn {
291 var CallidString call_id := sip_req.msgHeader.callId.callid;
292 if (f_call_id_known(call_id)) {
293 /* re-invite? */
294 vc_conn := f_comp_by_call_id(call_id);
295 } else {
296 /* new INVITE: add to table */
297 f_call_table_add(vc_conn, call_id);
298 }
299 SIP.send(sip_req);
300 }
301 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
302 [] CLIENT.receive(PDU_SIP_Request:?) -> value sip_req sender vc_conn {
303 SIP.send(sip_req);
304 }
305 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
306 [] CLIENT.receive(PDU_SIP_Response:?) -> value sip_resp sender vc_conn {
307 SIP.send(sip_resp);
308 }
309
310 [] CLIENT_PROC.getcall(SIPEM_register:{?,?}) -> param(sip_to, vc_hdlr) {
311 f_create_expect(sip_to, vc_hdlr);
312 CLIENT_PROC.reply(SIPEM_register:{sip_to, vc_hdlr});
313 }
314
315 }
316 }
317}
318
319/***********************************************************************
320 * "Expect" Handling (mapping for expected incoming SIP callds from IUT)
321 ***********************************************************************/
322
323/* data about an expected future incoming connection */
324type record ExpectData {
325 /* SIP "To" (destination number) based on which we can match */
326 SipUrl sip_to optional,
327 /* component reference registered for the connection */
328 SIP_ConnHdlr vc_conn
329}
330
331/* procedure based port to register for incoming calls */
332signature SIPEM_register(SipUrl sip_to, SIP_ConnHdlr vc_conn);
333
334type port SIPEM_PROC_PT procedure {
335 inout SIPEM_register;
336} with { extension "internal" };
337
338
339/* CreateCallback that can be used as create_cb and will use the expect table */
340function ExpectedCreateCallback(PDU_SIP_Request sip_req, charstring id)
341runs on SIP_Emulation_CT return SIP_ConnHdlr {
342 var SIP_ConnHdlr ret := null;
343 var SipUrl sip_to;
344 var integer i;
345
346 if (sip_req.requestLine.method != INVITE_E) {
347 setverdict(fail, "SIP ExpectedCreateCallback needs INVITE");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200348 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100349 return ret;
350 }
351 sip_to := sip_req.msgHeader.toField.addressField.nameAddr.addrSpec;
352
353 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
354 if (not ispresent(SipExpectTable[i].sip_to)) {
355 continue;
356 }
357 /* build a template, use '*' for all 'omit' values */
358 var template SipUrl t_exp := SipExpectTable[i].sip_to;
359 if (not ispresent(t_exp.hostPort.host)) {
360 t_exp.hostPort.host := *;
361 }
362 if (not ispresent(t_exp.hostPort.portField)) {
363 t_exp.hostPort.portField := *;
364 }
365 if (not ispresent(t_exp.urlParameters)) {
366 t_exp.urlParameters := *;
367 }
368 if (not ispresent(t_exp.headers)) {
369 t_exp.headers := *;
370 }
371 /* match against the constructed template */
372 if (match(sip_to, t_exp)) {
373 ret := SipExpectTable[i].vc_conn;
374 /* release this entry to be used again */
375 SipExpectTable[i].sip_to := omit;
376 SipExpectTable[i].vc_conn := null;
377 log("Found SipExpect[", i, "] for ", sip_to, " handled at ", ret);
378 return ret;
379 }
380 }
381
382 setverdict(fail, "Couldn't find SipExpect for incoming call ", sip_to);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200383 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100384 return ret;
385}
386
387/* server/emulation side function to create expect */
388private function f_create_expect(SipUrl sip_to, SIP_ConnHdlr hdlr)
389runs on SIP_Emulation_CT {
390 var integer i;
391 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
392 if (not ispresent(SipExpectTable[i].sip_to)) {
393 SipExpectTable[i].sip_to := sip_to;
394 SipExpectTable[i].vc_conn := hdlr;
395 log("Created SipExpect[", i, "] for ", sip_to, " to be handled at ", hdlr);
396 return;
397 }
398 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200399 testcase.stop("No space left in SipExpectTable");
Harald Welteafec4712018-03-19 22:52:17 +0100400}
401
402/* client/conn_hdlr side function to use procedure port to create expect in emulation */
403function f_create_sip_expect(SipUrl sip_to) runs on SIP_ConnHdlr {
404 SIP_PROC.call(SIPEM_register:{sip_to, self}) {
405 [] SIP_PROC.getreply(SIPEM_register:{?,?}) {};
406 }
407}
408
409
410
411}