blob: 3de7169d1943c56236405ef33c8f5c87222ba175 [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
Harald Welteafec4712018-03-19 22:52:17 +010065template RequestLine tr_ReqLine(template Method method) := {
66 method := method,
67 requestUri := ?,
68 sipVersion := ?
69}
70
Pau Espin Pedrol37ee0ed2024-03-28 21:17:12 +010071private template PDU_SIP_Request tr_SIP_REGISTER := {
72 requestLine := tr_ReqLine(REGISTER_E),
73 msgHeader := t_SIP_msgHeader_any,
74 messageBody := *,
75 payload := *
76}
77
Harald Welteb0d93602018-03-20 18:09:34 +010078private template PDU_SIP_Request tr_SIP_INVITE := {
Harald Welteafec4712018-03-19 22:52:17 +010079 requestLine := tr_ReqLine(INVITE_E),
80 msgHeader := t_SIP_msgHeader_any,
81 messageBody := *,
82 payload := *
83}
84
Harald Welteafec4712018-03-19 22:52:17 +010085template SipUrl tr_SIP_Url(template charstring user_or_num,
86 template charstring host := *,
87 template integer portField := *) := {
88 scheme := "sip",
89 userInfo := {
90 userOrTelephoneSubscriber := user_or_num,
91 password := *
92 },
93 hostPort := {
94 host := host,
95 portField := portField
96 },
97 urlParameters := *,
98 headers := *
99}
100template (value) SipUrl ts_SIP_Url(charstring user_or_num,
101 template (omit) charstring host := omit,
102 template (omit) integer portField := omit) := {
103 scheme := "sip",
104 userInfo := {
105 userOrTelephoneSubscriber := user_or_num,
106 password := omit
107 },
108 hostPort := {
109 host := host,
110 portField := portField
111 },
112 urlParameters := omit,
113 headers := omit
114}
115
116template Addr_Union tr_SIP_Addr(template SipUrl sip_url) := {
117 nameAddr := {
118 displayName := *,
119 addrSpec := sip_url
120 }
121}
122template (value) Addr_Union ts_SIP_Addr(template (value) SipUrl sip_url) := {
123 nameAddr := {
124 displayName := omit,
125 addrSpec := sip_url
126 }
127}
128
129template To tr_SIP_To(template Addr_Union addr) := {
130 fieldName := TO_E,
131 addressField := addr,
132 toParams := *
133}
134template (value) To ts_SIP_To(template (value) Addr_Union addr) := {
135 fieldName := TO_E,
136 addressField := addr,
137 toParams := omit
138}
139
140/* resolve component reference by connection ID */
141private function f_call_id_known(CallidString call_id)
142runs on SIP_Emulation_CT return boolean {
143 var integer i;
144 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
145 if (SipCallTable[i].call_id == call_id) {
146 return true;
147 }
148 }
149 return false;
150}
151
152/* resolve component reference by connection ID */
153private function f_comp_by_call_id(CallidString call_id)
154runs on SIP_Emulation_CT return SIP_ConnHdlr {
155 var integer i;
156 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
157 if (SipCallTable[i].call_id == call_id) {
158 return SipCallTable[i].comp_ref;
159 }
160 }
161 setverdict(fail, "SIP Call table not found by SIP Call ID ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200162 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100163}
164
165/* resolve connection ID by component reference */
166private function f_call_id_by_comp(SIP_ConnHdlr client)
167runs on SIP_Emulation_CT return CallidString {
168 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
169 if (SipCallTable[i].comp_ref == client) {
170 return SipCallTable[i].call_id;
171 }
172 }
173 setverdict(fail, "SIP Call table not found by component ", client);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200174 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100175}
176
177private function f_expect_table_init()
178runs on SIP_Emulation_CT {
179 for (var integer i := 0; i < sizeof(SipExpectTable); i := i+1) {
180 SipExpectTable[i].sip_to := omit;
181 SipExpectTable[i].vc_conn := null;
182 }
183}
184
185private function f_call_table_init()
186runs on SIP_Emulation_CT {
187 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
188 SipCallTable[i].comp_ref := null;
189 SipCallTable[i].call_id := "";
190 }
191}
192
193private function f_call_table_add(SIP_ConnHdlr comp_ref, CallidString call_id)
194runs on SIP_Emulation_CT {
195 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
196 if (SipCallTable[i].call_id == "") {
197 SipCallTable[i].comp_ref := comp_ref;
198 SipCallTable[i].call_id := call_id;
199 log("Added SIP Call Table entry [", i, "] for ", call_id, " at ", comp_ref);
200 return;
201 }
202 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200203 testcase.stop("SIP Call table full");
Harald Welteafec4712018-03-19 22:52:17 +0100204}
205
206private function f_call_table_del(CallidString call_id)
207runs on SIP_Emulation_CT {
208 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
209 if (SipCallTable[i].call_id == call_id) {
210 SipCallTable[i].comp_ref := null;
211 SipCallTable[i].call_id := "";
212 log("Deleted SIP Call Table entry [", i, "] for ", call_id);
213 return;
214 }
215 }
216 setverdict(fail, "SIP Call table attempt to delete non-existant ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200217 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100218}
219
220/* call-back type, to be provided by specific implementation; called when new call connection
221 * arrives */
222type function SipCreateCallback(PDU_SIP_Request sip_req, charstring id)
223runs on SIP_Emulation_CT return SIP_ConnHdlr;
224
225type record SipOps {
226 SipCreateCallback create_cb
227};
228
Pau Espin Pedrol1a771342024-04-25 14:42:50 +0200229function f_init_sip(inout SIP_Emulation_CT ct, charstring id := "SIP_EMU") {
Harald Welteafec4712018-03-19 22:52:17 +0100230 var SipOps ops := {
231 create_cb := refers(SIP_Emulation.ExpectedCreateCallback)
232 };
233
Pau Espin Pedrole94a6482024-04-10 13:37:55 +0200234 ct := SIP_Emulation_CT.create(id) alive;
Harald Welteafec4712018-03-19 22:52:17 +0100235 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
Harald Welteafec4712018-03-19 22:52:17 +0100242 f_expect_table_init();
243 f_call_table_init();
244
245 while (true) {
246 var SIP_ConnHdlr vc_hdlr, vc_conn;
247 var PDU_SIP_Request sip_req;
248 var PDU_SIP_Response sip_resp;
249 var SipUrl sip_to;
250
251 alt {
Pau Espin Pedrol4757be72024-05-08 21:15:01 +0200252 /* SIP REGISTER was received on SIP socket/port */
253 [] SIP.receive(tr_SIP_REGISTER) -> value sip_req {
254 var CallidString call_id := sip_req.msgHeader.callId.callid;
255 if (f_call_id_known(call_id)) {
256 /* re-register? */
257 vc_conn := f_comp_by_call_id(call_id);
258 } else {
259 /* new REGISTER: check expect */
260 vc_conn := ops.create_cb.apply(sip_req, id);
261 f_call_table_add(vc_conn, call_id);
262 }
263 CLIENT.send(sip_req) to vc_conn;
264 }
Harald Welteafec4712018-03-19 22:52:17 +0100265 /* SIP INVITE was received on SIP socket/port */
266 [] SIP.receive(tr_SIP_INVITE) -> value sip_req {
267 var CallidString call_id := sip_req.msgHeader.callId.callid;
268 if (f_call_id_known(call_id)) {
269 /* re-invite? */
270 vc_conn := f_comp_by_call_id(call_id);
271 } else {
272 /* new INVITE: check expect */
273 vc_conn := ops.create_cb.apply(sip_req, id);
274 f_call_table_add(vc_conn, call_id);
275 }
276 CLIENT.send(sip_req) to vc_conn;
277 }
278 /* other SIP request was received on SIP socket/port */
279 [] SIP.receive(PDU_SIP_Request:?) -> value sip_req {
280 var CallidString call_id := sip_req.msgHeader.callId.callid;
281 if (f_call_id_known(call_id)) {
282 vc_conn := f_comp_by_call_id(call_id);
283 CLIENT.send(sip_req) to vc_conn;
284 } else {
285 setverdict(fail, "SIP Request for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200286 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100287 }
288 }
289 /* SIP response was received on SIP socket/port */
290 [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp {
291 var CallidString call_id := sip_resp.msgHeader.callId.callid;
292 if (f_call_id_known(call_id)) {
293 vc_conn := f_comp_by_call_id(call_id);
294 CLIENT.send(sip_resp) to vc_conn;
295 } else {
296 setverdict(fail, "SIP Response for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200297 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100298 }
299 }
300
Pau Espin Pedrol37ee0ed2024-03-28 21:17:12 +0100301 /* a ConnHdlr is sending us a SIP REGISTER: Forward to SIP port */
302 [] CLIENT.receive(tr_SIP_REGISTER) -> value sip_req sender vc_conn {
303 var CallidString call_id := sip_req.msgHeader.callId.callid;
304 if (f_call_id_known(call_id)) {
305 /* re-register */
306 vc_conn := f_comp_by_call_id(call_id);
307 } else {
308 /* new REGISTER: add to table */
309 f_call_table_add(vc_conn, call_id);
310 }
311 SIP.send(sip_req);
312 }
313
Harald Welteafec4712018-03-19 22:52:17 +0100314 /* a ConnHdlr is sending us a SIP INVITE: Forward to SIP port */
315 [] CLIENT.receive(tr_SIP_INVITE) -> value sip_req sender vc_conn {
316 var CallidString call_id := sip_req.msgHeader.callId.callid;
317 if (f_call_id_known(call_id)) {
318 /* re-invite? */
319 vc_conn := f_comp_by_call_id(call_id);
320 } else {
321 /* new INVITE: add to table */
322 f_call_table_add(vc_conn, call_id);
323 }
324 SIP.send(sip_req);
325 }
326 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
327 [] CLIENT.receive(PDU_SIP_Request:?) -> value sip_req sender vc_conn {
328 SIP.send(sip_req);
329 }
330 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
331 [] CLIENT.receive(PDU_SIP_Response:?) -> value sip_resp sender vc_conn {
332 SIP.send(sip_resp);
333 }
334
Pau Espin Pedrol0169e602024-04-08 20:59:19 +0200335 [] CLIENT_PROC.getcall(SIPEM_register:{?,?}) -> param(sip_to, vc_hdlr) sender vc_conn {
Harald Welteafec4712018-03-19 22:52:17 +0100336 f_create_expect(sip_to, vc_hdlr);
Pau Espin Pedrol0169e602024-04-08 20:59:19 +0200337 CLIENT_PROC.reply(SIPEM_register:{sip_to, vc_hdlr}) to vc_conn;
Harald Welteafec4712018-03-19 22:52:17 +0100338 }
339
340 }
341 }
342}
343
344/***********************************************************************
345 * "Expect" Handling (mapping for expected incoming SIP callds from IUT)
346 ***********************************************************************/
347
348/* data about an expected future incoming connection */
349type record ExpectData {
350 /* SIP "To" (destination number) based on which we can match */
351 SipUrl sip_to optional,
352 /* component reference registered for the connection */
353 SIP_ConnHdlr vc_conn
354}
355
356/* procedure based port to register for incoming calls */
357signature SIPEM_register(SipUrl sip_to, SIP_ConnHdlr vc_conn);
358
359type port SIPEM_PROC_PT procedure {
360 inout SIPEM_register;
361} with { extension "internal" };
362
363
364/* CreateCallback that can be used as create_cb and will use the expect table */
365function ExpectedCreateCallback(PDU_SIP_Request sip_req, charstring id)
366runs on SIP_Emulation_CT return SIP_ConnHdlr {
367 var SIP_ConnHdlr ret := null;
368 var SipUrl sip_to;
369 var integer i;
370
Pau Espin Pedrol4757be72024-05-08 21:15:01 +0200371 if (sip_req.requestLine.method != INVITE_E and
372 sip_req.requestLine.method != REGISTER_E) {
373 setverdict(fail, "SIP ExpectedCreateCallback needs REGISTER/INVITE");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200374 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100375 return ret;
376 }
377 sip_to := sip_req.msgHeader.toField.addressField.nameAddr.addrSpec;
378
379 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
380 if (not ispresent(SipExpectTable[i].sip_to)) {
381 continue;
382 }
383 /* build a template, use '*' for all 'omit' values */
384 var template SipUrl t_exp := SipExpectTable[i].sip_to;
385 if (not ispresent(t_exp.hostPort.host)) {
386 t_exp.hostPort.host := *;
387 }
388 if (not ispresent(t_exp.hostPort.portField)) {
389 t_exp.hostPort.portField := *;
Pau Espin Pedrol6331f5c2024-04-08 21:32:35 +0200390 } else if (valueof(t_exp.hostPort.portField) == 5060) {
391 /* if the port number is 5060, it may be omitted */
392 t_exp.hostPort.portField := 5060 ifpresent;
Harald Welteafec4712018-03-19 22:52:17 +0100393 }
394 if (not ispresent(t_exp.urlParameters)) {
395 t_exp.urlParameters := *;
396 }
397 if (not ispresent(t_exp.headers)) {
398 t_exp.headers := *;
399 }
400 /* match against the constructed template */
401 if (match(sip_to, t_exp)) {
402 ret := SipExpectTable[i].vc_conn;
403 /* release this entry to be used again */
404 SipExpectTable[i].sip_to := omit;
405 SipExpectTable[i].vc_conn := null;
406 log("Found SipExpect[", i, "] for ", sip_to, " handled at ", ret);
407 return ret;
408 }
409 }
410
411 setverdict(fail, "Couldn't find SipExpect for incoming call ", sip_to);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200412 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100413 return ret;
414}
415
416/* server/emulation side function to create expect */
417private function f_create_expect(SipUrl sip_to, SIP_ConnHdlr hdlr)
418runs on SIP_Emulation_CT {
419 var integer i;
420 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
421 if (not ispresent(SipExpectTable[i].sip_to)) {
422 SipExpectTable[i].sip_to := sip_to;
423 SipExpectTable[i].vc_conn := hdlr;
424 log("Created SipExpect[", i, "] for ", sip_to, " to be handled at ", hdlr);
425 return;
426 }
427 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200428 testcase.stop("No space left in SipExpectTable");
Harald Welteafec4712018-03-19 22:52:17 +0100429}
430
431/* client/conn_hdlr side function to use procedure port to create expect in emulation */
432function f_create_sip_expect(SipUrl sip_to) runs on SIP_ConnHdlr {
433 SIP_PROC.call(SIPEM_register:{sip_to, self}) {
434 [] SIP_PROC.getreply(SIPEM_register:{?,?}) {};
435 }
436}
437
438
439
440}