blob: 98f73710fe0c9fd0608c8e9ba62d31746949086a [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.
27 */
28
29import from SIPmsg_Types all;
30import from SIPmsg_PortType all;
31
32type component SIP_ConnHdlr {
33 /* ports towards SIP Emulation core / call dispatcher */
34 port SIP_Conn_PT SIP;
35 port SIPEM_PROC_PT SIP_PROC;
36}
37
38/* port between individual per-call components and this dispatcher */
39type port SIP_Conn_PT message {
40 inout PDU_SIP_Request, PDU_SIP_Response;
41} with { extension "internal" };
42
43/* represents a single SIP Call */
44type record CallData {
45 /* reference to the instance of the per-connection component */
46 SIP_ConnHdlr comp_ref,
47 CallidString call_id
48}
49
50type component SIP_Emulation_CT {
51 /* SIP test port on bottom side */
52 port SIPmsg_PT SIP;
53 /* SIP port to the per-call clients */
54 port SIP_Conn_PT CLIENT;
55
56 var CallData SipCallTable[16];
57 var ExpectData SipExpectTable[16];
58
59 /* procedure based port to register for incoming connections */
60 port SIPEM_PROC_PT CLIENT_PROC;
61};
62
63private function f_sip_init() runs on SIP_Emulation_CT {
64 map(self:SIP, system:SIP);
65}
66
67template RequestLine tr_ReqLine(template Method method) := {
68 method := method,
69 requestUri := ?,
70 sipVersion := ?
71}
72
Harald Welteb0d93602018-03-20 18:09:34 +010073private template PDU_SIP_Request tr_SIP_INVITE := {
Harald Welteafec4712018-03-19 22:52:17 +010074 requestLine := tr_ReqLine(INVITE_E),
75 msgHeader := t_SIP_msgHeader_any,
76 messageBody := *,
77 payload := *
78}
79
80
81template SipUrl tr_SIP_Url(template charstring user_or_num,
82 template charstring host := *,
83 template integer portField := *) := {
84 scheme := "sip",
85 userInfo := {
86 userOrTelephoneSubscriber := user_or_num,
87 password := *
88 },
89 hostPort := {
90 host := host,
91 portField := portField
92 },
93 urlParameters := *,
94 headers := *
95}
96template (value) SipUrl ts_SIP_Url(charstring user_or_num,
97 template (omit) charstring host := omit,
98 template (omit) integer portField := omit) := {
99 scheme := "sip",
100 userInfo := {
101 userOrTelephoneSubscriber := user_or_num,
102 password := omit
103 },
104 hostPort := {
105 host := host,
106 portField := portField
107 },
108 urlParameters := omit,
109 headers := omit
110}
111
112template Addr_Union tr_SIP_Addr(template SipUrl sip_url) := {
113 nameAddr := {
114 displayName := *,
115 addrSpec := sip_url
116 }
117}
118template (value) Addr_Union ts_SIP_Addr(template (value) SipUrl sip_url) := {
119 nameAddr := {
120 displayName := omit,
121 addrSpec := sip_url
122 }
123}
124
125template To tr_SIP_To(template Addr_Union addr) := {
126 fieldName := TO_E,
127 addressField := addr,
128 toParams := *
129}
130template (value) To ts_SIP_To(template (value) Addr_Union addr) := {
131 fieldName := TO_E,
132 addressField := addr,
133 toParams := omit
134}
135
136/* resolve component reference by connection ID */
137private function f_call_id_known(CallidString call_id)
138runs on SIP_Emulation_CT return boolean {
139 var integer i;
140 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
141 if (SipCallTable[i].call_id == call_id) {
142 return true;
143 }
144 }
145 return false;
146}
147
148/* resolve component reference by connection ID */
149private function f_comp_by_call_id(CallidString call_id)
150runs on SIP_Emulation_CT return SIP_ConnHdlr {
151 var integer i;
152 for (i := 0; i < sizeof(SipCallTable); i := i+1) {
153 if (SipCallTable[i].call_id == call_id) {
154 return SipCallTable[i].comp_ref;
155 }
156 }
157 setverdict(fail, "SIP Call table not found by SIP Call ID ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200158 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100159}
160
161/* resolve connection ID by component reference */
162private function f_call_id_by_comp(SIP_ConnHdlr client)
163runs on SIP_Emulation_CT return CallidString {
164 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
165 if (SipCallTable[i].comp_ref == client) {
166 return SipCallTable[i].call_id;
167 }
168 }
169 setverdict(fail, "SIP Call table not found by component ", client);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200170 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100171}
172
173private function f_expect_table_init()
174runs on SIP_Emulation_CT {
175 for (var integer i := 0; i < sizeof(SipExpectTable); i := i+1) {
176 SipExpectTable[i].sip_to := omit;
177 SipExpectTable[i].vc_conn := null;
178 }
179}
180
181private function f_call_table_init()
182runs on SIP_Emulation_CT {
183 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
184 SipCallTable[i].comp_ref := null;
185 SipCallTable[i].call_id := "";
186 }
187}
188
189private function f_call_table_add(SIP_ConnHdlr comp_ref, CallidString call_id)
190runs on SIP_Emulation_CT {
191 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
192 if (SipCallTable[i].call_id == "") {
193 SipCallTable[i].comp_ref := comp_ref;
194 SipCallTable[i].call_id := call_id;
195 log("Added SIP Call Table entry [", i, "] for ", call_id, " at ", comp_ref);
196 return;
197 }
198 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200199 testcase.stop("SIP Call table full");
Harald Welteafec4712018-03-19 22:52:17 +0100200}
201
202private function f_call_table_del(CallidString call_id)
203runs on SIP_Emulation_CT {
204 for (var integer i := 0; i < sizeof(SipCallTable); i := i+1) {
205 if (SipCallTable[i].call_id == call_id) {
206 SipCallTable[i].comp_ref := null;
207 SipCallTable[i].call_id := "";
208 log("Deleted SIP Call Table entry [", i, "] for ", call_id);
209 return;
210 }
211 }
212 setverdict(fail, "SIP Call table attempt to delete non-existant ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200213 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100214}
215
216/* call-back type, to be provided by specific implementation; called when new call connection
217 * arrives */
218type function SipCreateCallback(PDU_SIP_Request sip_req, charstring id)
219runs on SIP_Emulation_CT return SIP_ConnHdlr;
220
221type record SipOps {
222 SipCreateCallback create_cb
223};
224
225function f_init_sip(inout SIP_Emulation_CT ct, charstring id) {
226 id := id & "-SIP";
227
228 var SipOps ops := {
229 create_cb := refers(SIP_Emulation.ExpectedCreateCallback)
230 };
231
232 ct := SIP_Emulation_CT.create(id);
233 map(ct:SIP, system:SIP);
234 ct.start(SIP_Emulation.main(ops, id));
235}
236
237function main(SipOps ops, charstring id)
238runs on SIP_Emulation_CT {
239
240 f_sip_init();
241 f_expect_table_init();
242 f_call_table_init();
243
244 while (true) {
245 var SIP_ConnHdlr vc_hdlr, vc_conn;
246 var PDU_SIP_Request sip_req;
247 var PDU_SIP_Response sip_resp;
248 var SipUrl sip_to;
249
250 alt {
251 /* SIP INVITE was received on SIP socket/port */
252 [] SIP.receive(tr_SIP_INVITE) -> value sip_req {
253 var CallidString call_id := sip_req.msgHeader.callId.callid;
254 if (f_call_id_known(call_id)) {
255 /* re-invite? */
256 vc_conn := f_comp_by_call_id(call_id);
257 } else {
258 /* new INVITE: check expect */
259 vc_conn := ops.create_cb.apply(sip_req, id);
260 f_call_table_add(vc_conn, call_id);
261 }
262 CLIENT.send(sip_req) to vc_conn;
263 }
264 /* other SIP request was received on SIP socket/port */
265 [] SIP.receive(PDU_SIP_Request:?) -> value sip_req {
266 var CallidString call_id := sip_req.msgHeader.callId.callid;
267 if (f_call_id_known(call_id)) {
268 vc_conn := f_comp_by_call_id(call_id);
269 CLIENT.send(sip_req) to vc_conn;
270 } else {
271 setverdict(fail, "SIP Request for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200272 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100273 }
274 }
275 /* SIP response was received on SIP socket/port */
276 [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp {
277 var CallidString call_id := sip_resp.msgHeader.callId.callid;
278 if (f_call_id_known(call_id)) {
279 vc_conn := f_comp_by_call_id(call_id);
280 CLIENT.send(sip_resp) to vc_conn;
281 } else {
282 setverdict(fail, "SIP Response for unknown call ", call_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200283 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100284 }
285 }
286
287 /* a ConnHdlr is sending us a SIP INVITE: Forward to SIP port */
288 [] CLIENT.receive(tr_SIP_INVITE) -> value sip_req sender vc_conn {
289 var CallidString call_id := sip_req.msgHeader.callId.callid;
290 if (f_call_id_known(call_id)) {
291 /* re-invite? */
292 vc_conn := f_comp_by_call_id(call_id);
293 } else {
294 /* new INVITE: add to table */
295 f_call_table_add(vc_conn, call_id);
296 }
297 SIP.send(sip_req);
298 }
299 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
300 [] CLIENT.receive(PDU_SIP_Request:?) -> value sip_req sender vc_conn {
301 SIP.send(sip_req);
302 }
303 /* a ConnHdlr is sending us a SIP request: Forward to SIP port */
304 [] CLIENT.receive(PDU_SIP_Response:?) -> value sip_resp sender vc_conn {
305 SIP.send(sip_resp);
306 }
307
308 [] CLIENT_PROC.getcall(SIPEM_register:{?,?}) -> param(sip_to, vc_hdlr) {
309 f_create_expect(sip_to, vc_hdlr);
310 CLIENT_PROC.reply(SIPEM_register:{sip_to, vc_hdlr});
311 }
312
313 }
314 }
315}
316
317/***********************************************************************
318 * "Expect" Handling (mapping for expected incoming SIP callds from IUT)
319 ***********************************************************************/
320
321/* data about an expected future incoming connection */
322type record ExpectData {
323 /* SIP "To" (destination number) based on which we can match */
324 SipUrl sip_to optional,
325 /* component reference registered for the connection */
326 SIP_ConnHdlr vc_conn
327}
328
329/* procedure based port to register for incoming calls */
330signature SIPEM_register(SipUrl sip_to, SIP_ConnHdlr vc_conn);
331
332type port SIPEM_PROC_PT procedure {
333 inout SIPEM_register;
334} with { extension "internal" };
335
336
337/* CreateCallback that can be used as create_cb and will use the expect table */
338function ExpectedCreateCallback(PDU_SIP_Request sip_req, charstring id)
339runs on SIP_Emulation_CT return SIP_ConnHdlr {
340 var SIP_ConnHdlr ret := null;
341 var SipUrl sip_to;
342 var integer i;
343
344 if (sip_req.requestLine.method != INVITE_E) {
345 setverdict(fail, "SIP ExpectedCreateCallback needs INVITE");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200346 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100347 return ret;
348 }
349 sip_to := sip_req.msgHeader.toField.addressField.nameAddr.addrSpec;
350
351 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
352 if (not ispresent(SipExpectTable[i].sip_to)) {
353 continue;
354 }
355 /* build a template, use '*' for all 'omit' values */
356 var template SipUrl t_exp := SipExpectTable[i].sip_to;
357 if (not ispresent(t_exp.hostPort.host)) {
358 t_exp.hostPort.host := *;
359 }
360 if (not ispresent(t_exp.hostPort.portField)) {
361 t_exp.hostPort.portField := *;
362 }
363 if (not ispresent(t_exp.urlParameters)) {
364 t_exp.urlParameters := *;
365 }
366 if (not ispresent(t_exp.headers)) {
367 t_exp.headers := *;
368 }
369 /* match against the constructed template */
370 if (match(sip_to, t_exp)) {
371 ret := SipExpectTable[i].vc_conn;
372 /* release this entry to be used again */
373 SipExpectTable[i].sip_to := omit;
374 SipExpectTable[i].vc_conn := null;
375 log("Found SipExpect[", i, "] for ", sip_to, " handled at ", ret);
376 return ret;
377 }
378 }
379
380 setverdict(fail, "Couldn't find SipExpect for incoming call ", sip_to);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200381 mtc.stop
Harald Welteafec4712018-03-19 22:52:17 +0100382 return ret;
383}
384
385/* server/emulation side function to create expect */
386private function f_create_expect(SipUrl sip_to, SIP_ConnHdlr hdlr)
387runs on SIP_Emulation_CT {
388 var integer i;
389 for (i := 0; i < sizeof(SipExpectTable); i := i+1) {
390 if (not ispresent(SipExpectTable[i].sip_to)) {
391 SipExpectTable[i].sip_to := sip_to;
392 SipExpectTable[i].vc_conn := hdlr;
393 log("Created SipExpect[", i, "] for ", sip_to, " to be handled at ", hdlr);
394 return;
395 }
396 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200397 testcase.stop("No space left in SipExpectTable");
Harald Welteafec4712018-03-19 22:52:17 +0100398}
399
400/* client/conn_hdlr side function to use procedure port to create expect in emulation */
401function f_create_sip_expect(SipUrl sip_to) runs on SIP_ConnHdlr {
402 SIP_PROC.call(SIPEM_register:{sip_to, self}) {
403 [] SIP_PROC.getreply(SIPEM_register:{?,?}) {};
404 }
405}
406
407
408
409}