blob: 4aef536d4769f0432f15ba8fb93f52de7092f27d [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
Pau Espin Pedrol37ee0ed2024-03-28 21:17:12 +010075private template PDU_SIP_Request tr_SIP_REGISTER := {
76 requestLine := tr_ReqLine(REGISTER_E),
77 msgHeader := t_SIP_msgHeader_any,
78 messageBody := *,
79 payload := *
80}
81
Harald Welteb0d93602018-03-20 18:09:34 +010082private template PDU_SIP_Request tr_SIP_INVITE := {
Harald Welteafec4712018-03-19 22:52:17 +010083 requestLine := tr_ReqLine(INVITE_E),
84 msgHeader := t_SIP_msgHeader_any,
85 messageBody := *,
86 payload := *
87}
88
Harald Welteafec4712018-03-19 22:52:17 +010089template SipUrl tr_SIP_Url(template charstring user_or_num,
90 template charstring host := *,
91 template integer portField := *) := {
92 scheme := "sip",
93 userInfo := {
94 userOrTelephoneSubscriber := user_or_num,
95 password := *
96 },
97 hostPort := {
98 host := host,
99 portField := portField
100 },
101 urlParameters := *,
102 headers := *
103}
104template (value) SipUrl ts_SIP_Url(charstring user_or_num,
105 template (omit) charstring host := omit,
106 template (omit) integer portField := omit) := {
107 scheme := "sip",
108 userInfo := {
109 userOrTelephoneSubscriber := user_or_num,
110 password := omit
111 },
112 hostPort := {
113 host := host,
114 portField := portField
115 },
116 urlParameters := omit,
117 headers := omit
118}
119
120template Addr_Union tr_SIP_Addr(template SipUrl sip_url) := {
121 nameAddr := {
122 displayName := *,
123 addrSpec := sip_url
124 }
125}
126template (value) Addr_Union ts_SIP_Addr(template (value) SipUrl sip_url) := {
127 nameAddr := {
128 displayName := omit,
129 addrSpec := sip_url
130 }
131}
132
133template To tr_SIP_To(template Addr_Union addr) := {
134 fieldName := TO_E,
135 addressField := addr,
136 toParams := *
137}
138template (value) To ts_SIP_To(template (value) Addr_Union addr) := {
139 fieldName := TO_E,
140 addressField := addr,
141 toParams := omit
142}
143
144/* resolve component reference by connection ID */
145private function f_call_id_known(CallidString call_id)
146runs on SIP_Emulation_CT return boolean {
147 var integer i;
148 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
149 if (SipCallTable[i].call_id == call_id) {
150 return true;
151 }
152 }
153 return false;
154}
155
156/* resolve component reference by connection ID */
157private function f_comp_by_call_id(CallidString call_id)
158runs on SIP_Emulation_CT return SIP_ConnHdlr {
159 var integer i;
160 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
161 if (SipCallTable[i].call_id == call_id) {
162 return SipCallTable[i].comp_ref;
163 }
164 }
165 setverdict(fail, "SIP Call table not found by SIP Call ID ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200166 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100167}
168
169/* resolve connection ID by component reference */
170private function f_call_id_by_comp(SIP_ConnHdlr client)
171runs on SIP_Emulation_CT return CallidString {
172 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
173 if (SipCallTable[i].comp_ref == client) {
174 return SipCallTable[i].call_id;
175 }
176 }
177 setverdict(fail, "SIP Call table not found by component ", client);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200178 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100179}
180
181private function f_expect_table_init()
182runs on SIP_Emulation_CT {
183 for (var integer i := 0; i < sizeof(SipExpectTable); i := i+1) {
184 SipExpectTable[i].sip_to := omit;
185 SipExpectTable[i].vc_conn := null;
186 }
187}
188
189private function f_call_table_init()
190runs on SIP_Emulation_CT {
191 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
192 SipCallTable[i].comp_ref := null;
193 SipCallTable[i].call_id := "";
194 }
195}
196
197private function f_call_table_add(SIP_ConnHdlr comp_ref, CallidString call_id)
198runs on SIP_Emulation_CT {
199 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
200 if (SipCallTable[i].call_id == "") {
201 SipCallTable[i].comp_ref := comp_ref;
202 SipCallTable[i].call_id := call_id;
203 log("Added SIP Call Table entry [", i, "] for ", call_id, " at ", comp_ref);
204 return;
205 }
206 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200207 testcase.stop("SIP Call table full");
Harald Welteafec4712018-03-19 22:52:17 +0100208}
209
210private function f_call_table_del(CallidString call_id)
211runs on SIP_Emulation_CT {
212 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
213 if (SipCallTable[i].call_id == call_id) {
214 SipCallTable[i].comp_ref := null;
215 SipCallTable[i].call_id := "";
216 log("Deleted SIP Call Table entry [", i, "] for ", call_id);
217 return;
218 }
219 }
220 setverdict(fail, "SIP Call table attempt to delete non-existant ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200221 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100222}
223
224/* call-back type, to be provided by specific implementation; called when new call connection
225 * arrives */
226type function SipCreateCallback(PDU_SIP_Request sip_req, charstring id)
227runs on SIP_Emulation_CT return SIP_ConnHdlr;
228
229type record SipOps {
230 SipCreateCallback create_cb
231};
232
233function f_init_sip(inout SIP_Emulation_CT ct, charstring id) {
234 id := id & "-SIP";
235
236 var SipOps ops := {
237 create_cb := refers(SIP_Emulation.ExpectedCreateCallback)
238 };
239
240 ct := SIP_Emulation_CT.create(id);
241 map(ct:SIP, system:SIP);
242 ct.start(SIP_Emulation.main(ops, id));
243}
244
245function main(SipOps ops, charstring id)
246runs on SIP_Emulation_CT {
247
248 f_sip_init();
249 f_expect_table_init();
250 f_call_table_init();
251
252 while (true) {
253 var SIP_ConnHdlr vc_hdlr, vc_conn;
254 var PDU_SIP_Request sip_req;
255 var PDU_SIP_Response sip_resp;
256 var SipUrl sip_to;
257
258 alt {
259 /* SIP INVITE was received on SIP socket/port */
260 [] SIP.receive(tr_SIP_INVITE) -> value sip_req {
261 var CallidString call_id := sip_req.msgHeader.callId.callid;
262 if (f_call_id_known(call_id)) {
263 /* re-invite? */
264 vc_conn := f_comp_by_call_id(call_id);
265 } else {
266 /* new INVITE: check expect */
267 vc_conn := ops.create_cb.apply(sip_req, id);
268 f_call_table_add(vc_conn, call_id);
269 }
270 CLIENT.send(sip_req) to vc_conn;
271 }
272 /* other SIP request was received on SIP socket/port */
273 [] SIP.receive(PDU_SIP_Request:?) -> value sip_req {
274 var CallidString call_id := sip_req.msgHeader.callId.callid;
275 if (f_call_id_known(call_id)) {
276 vc_conn := f_comp_by_call_id(call_id);
277 CLIENT.send(sip_req) to vc_conn;
278 } else {
279 setverdict(fail, "SIP Request for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200280 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100281 }
282 }
283 /* SIP response was received on SIP socket/port */
284 [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp {
285 var CallidString call_id := sip_resp.msgHeader.callId.callid;
286 if (f_call_id_known(call_id)) {
287 vc_conn := f_comp_by_call_id(call_id);
288 CLIENT.send(sip_resp) to vc_conn;
289 } else {
290 setverdict(fail, "SIP Response for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200291 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100292 }
293 }
294
Pau Espin Pedrol37ee0ed2024-03-28 21:17:12 +0100295 /* a ConnHdlr is sending us a SIP REGISTER: Forward to SIP port */
296 [] CLIENT.receive(tr_SIP_REGISTER) -> value sip_req sender vc_conn {
297 var CallidString call_id := sip_req.msgHeader.callId.callid;
298 if (f_call_id_known(call_id)) {
299 /* re-register */
300 vc_conn := f_comp_by_call_id(call_id);
301 } else {
302 /* new REGISTER: add to table */
303 f_call_table_add(vc_conn, call_id);
304 }
305 SIP.send(sip_req);
306 }
307
Harald Welteafec4712018-03-19 22:52:17 +0100308 /* a ConnHdlr is sending us a SIP INVITE: Forward to SIP port */
309 [] CLIENT.receive(tr_SIP_INVITE) -> value sip_req sender vc_conn {
310 var CallidString call_id := sip_req.msgHeader.callId.callid;
311 if (f_call_id_known(call_id)) {
312 /* re-invite? */
313 vc_conn := f_comp_by_call_id(call_id);
314 } else {
315 /* new INVITE: add to table */
316 f_call_table_add(vc_conn, call_id);
317 }
318 SIP.send(sip_req);
319 }
320 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
321 [] CLIENT.receive(PDU_SIP_Request:?) -> value sip_req sender vc_conn {
322 SIP.send(sip_req);
323 }
324 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
325 [] CLIENT.receive(PDU_SIP_Response:?) -> value sip_resp sender vc_conn {
326 SIP.send(sip_resp);
327 }
328
Pau Espin Pedrol0169e602024-04-08 20:59:19 +0200329 [] CLIENT_PROC.getcall(SIPEM_register:{?,?}) -> param(sip_to, vc_hdlr) sender vc_conn {
Harald Welteafec4712018-03-19 22:52:17 +0100330 f_create_expect(sip_to, vc_hdlr);
Pau Espin Pedrol0169e602024-04-08 20:59:19 +0200331 CLIENT_PROC.reply(SIPEM_register:{sip_to, vc_hdlr}) to vc_conn;
Harald Welteafec4712018-03-19 22:52:17 +0100332 }
333
334 }
335 }
336}
337
338/***********************************************************************
339 * "Expect" Handling (mapping for expected incoming SIP callds from IUT)
340 ***********************************************************************/
341
342/* data about an expected future incoming connection */
343type record ExpectData {
344 /* SIP "To" (destination number) based on which we can match */
345 SipUrl sip_to optional,
346 /* component reference registered for the connection */
347 SIP_ConnHdlr vc_conn
348}
349
350/* procedure based port to register for incoming calls */
351signature SIPEM_register(SipUrl sip_to, SIP_ConnHdlr vc_conn);
352
353type port SIPEM_PROC_PT procedure {
354 inout SIPEM_register;
355} with { extension "internal" };
356
357
358/* CreateCallback that can be used as create_cb and will use the expect table */
359function ExpectedCreateCallback(PDU_SIP_Request sip_req, charstring id)
360runs on SIP_Emulation_CT return SIP_ConnHdlr {
361 var SIP_ConnHdlr ret := null;
362 var SipUrl sip_to;
363 var integer i;
364
365 if (sip_req.requestLine.method != INVITE_E) {
366 setverdict(fail, "SIP ExpectedCreateCallback needs INVITE");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200367 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100368 return ret;
369 }
370 sip_to := sip_req.msgHeader.toField.addressField.nameAddr.addrSpec;
371
372 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
373 if (not ispresent(SipExpectTable[i].sip_to)) {
374 continue;
375 }
376 /* build a template, use '*' for all 'omit' values */
377 var template SipUrl t_exp := SipExpectTable[i].sip_to;
378 if (not ispresent(t_exp.hostPort.host)) {
379 t_exp.hostPort.host := *;
380 }
381 if (not ispresent(t_exp.hostPort.portField)) {
382 t_exp.hostPort.portField := *;
Pau Espin Pedrol6331f5c2024-04-08 21:32:35 +0200383 } else if (valueof(t_exp.hostPort.portField) == 5060) {
384 /* if the port number is 5060, it may be omitted */
385 t_exp.hostPort.portField := 5060 ifpresent;
Harald Welteafec4712018-03-19 22:52:17 +0100386 }
387 if (not ispresent(t_exp.urlParameters)) {
388 t_exp.urlParameters := *;
389 }
390 if (not ispresent(t_exp.headers)) {
391 t_exp.headers := *;
392 }
393 /* match against the constructed template */
394 if (match(sip_to, t_exp)) {
395 ret := SipExpectTable[i].vc_conn;
396 /* release this entry to be used again */
397 SipExpectTable[i].sip_to := omit;
398 SipExpectTable[i].vc_conn := null;
399 log("Found SipExpect[", i, "] for ", sip_to, " handled at ", ret);
400 return ret;
401 }
402 }
403
404 setverdict(fail, "Couldn't find SipExpect for incoming call ", sip_to);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200405 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100406 return ret;
407}
408
409/* server/emulation side function to create expect */
410private function f_create_expect(SipUrl sip_to, SIP_ConnHdlr hdlr)
411runs on SIP_Emulation_CT {
412 var integer i;
413 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
414 if (not ispresent(SipExpectTable[i].sip_to)) {
415 SipExpectTable[i].sip_to := sip_to;
416 SipExpectTable[i].vc_conn := hdlr;
417 log("Created SipExpect[", i, "] for ", sip_to, " to be handled at ", hdlr);
418 return;
419 }
420 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200421 testcase.stop("No space left in SipExpectTable");
Harald Welteafec4712018-03-19 22:52:17 +0100422}
423
424/* client/conn_hdlr side function to use procedure port to create expect in emulation */
425function f_create_sip_expect(SipUrl sip_to) runs on SIP_ConnHdlr {
426 SIP_PROC.call(SIPEM_register:{sip_to, self}) {
427 [] SIP_PROC.getreply(SIPEM_register:{?,?}) {};
428 }
429}
430
431
432
433}