blob: 5bd4e508b20a45d218b1511d1bcc7e64919a6042 [file] [log] [blame]
Daniel Willmannfa870f52018-01-17 12:37:14 +01001module MGCP_Emulation {
2
Harald Weltea02515f2018-01-26 10:33:23 +01003/* MGCP Emulation, runs on top of MGCP_CodecPort. It multiplexes/demultiplexes
4 * the individual connections, so there can be separate TTCN-3 components handling
5 * each of the connections.
6 *
7 * The MGCP_Emulation.main() function processes MGCP primitives from the MGCP
8 * socket via the MGCP_CodecPort, and dispatches them to the per-connection components.
9 *
10 * For each new inbound connection, the MgcpOps.create_cb() is called. It can create
11 * or resolve a TTCN-3 component, and returns a component reference to which that inbound
12 * connection is routed/dispatched.
13 *
14 * If a pre-existing component wants to register to handle a future inbound call, it can
15 * do so by registering an "expect" with the expected destination phone number. This is e.g. useful
16 * if you are simulating BSC + MGCP, and first trigger a connection from BSC side in a
17 * component which then subsequently should also handle the MGCP emulation.
18 *
19 * Inbound Unit Data messages (such as are dispatched to the MgcpOps.unitdata_cb() callback,
20 * which is registered with an argument to the main() function below.
21 *
22 * (C) 2017-2018 by Harald Welte <laforge@gnumonks.org>
23 * (C) 2018 by sysmocom - s.f.m.c. GmbH, Author: Daniel Willmann
24 * All rights reserved.
25 *
26 * Released under the terms of GNU General Public License, Version 2 or
27 * (at your option) any later version.
28 */
29
Daniel Willmannfa870f52018-01-17 12:37:14 +010030import from MGCP_CodecPort all;
31import from MGCP_CodecPort_CtrlFunct all;
32import from MGCP_Types all;
33import from MGCP_Templates all;
34import from Osmocom_Types all;
35import from IPL4asp_Types all;
36
37type component MGCP_ConnHdlr {
38 port MGCP_Conn_PT MGCP;
Harald Weltebb5a1212018-01-26 10:34:44 +010039 /* procedure based port to register for incoming connections */
40 port MGCPEM_PROC_PT MGCP_PROC;
Harald Weltec27eaae2018-01-25 18:43:23 +010041 var MgcpConnectionId mgcp_conn_id;
Daniel Willmannfa870f52018-01-17 12:37:14 +010042}
43
44/* port between individual per-connection components and this dispatcher */
45type port MGCP_Conn_PT message {
46 inout MgcpCommand, MgcpResponse;
47} with { extension "internal" };
48
Harald Weltebb5a1212018-01-26 10:34:44 +010049/* represents a single MGCP Connection */
50type record ConnectionData {
51 MGCP_ConnHdlr comp_ref,
52 MgcpConnectionId conn_id optional
53};
Daniel Willmannfa870f52018-01-17 12:37:14 +010054
55type component MGCP_Emulation_CT {
56 /* Port facing to the UDP SUT */
57 port MGCP_CODEC_PT MGCP;
58 /* All MGCP_ConnHdlr MGCP ports connect here
59 * MGCP_Emulation_CT.main needs to figure out what messages
60 * to send where with CLIENT.send() to vc_conn */
Harald Weltebb5a1212018-01-26 10:34:44 +010061 port MGCP_Conn_PT MGCP_CLIENT;
Daniel Willmannfa870f52018-01-17 12:37:14 +010062 /* currently tracked connections */
Harald Weltebb5a1212018-01-26 10:34:44 +010063 var ConnectionData MgcpConnectionTable[16];
Daniel Willmannfa870f52018-01-17 12:37:14 +010064 /* pending expected CRCX */
Harald Weltebb5a1212018-01-26 10:34:44 +010065 var ExpectData MgcpExpectTable[8];
Daniel Willmannfa870f52018-01-17 12:37:14 +010066 /* procedure based port to register for incoming connections */
Harald Weltebb5a1212018-01-26 10:34:44 +010067 port MGCPEM_PROC_PT MGCP_PROC;
Daniel Willmannfa870f52018-01-17 12:37:14 +010068
69 var charstring g_mgcp_id;
Daniel Willmann166bbb32018-01-17 15:28:04 +010070 var integer g_mgcp_conn_id := -1;
Daniel Willmannfa870f52018-01-17 12:37:14 +010071}
72
73type function MGCPCreateCallback(MgcpCommand cmd, charstring id)
74runs on MGCP_Emulation_CT return MGCP_ConnHdlr;
75
Harald Weltebb5a1212018-01-26 10:34:44 +010076type function MGCPUnitdataCallback(MgcpMessage msg)
77runs on MGCP_Emulation_CT return template MgcpMessage;
78
Daniel Willmannfa870f52018-01-17 12:37:14 +010079type record MGCPOps {
Harald Weltebb5a1212018-01-26 10:34:44 +010080 MGCPCreateCallback create_cb,
81 MGCPUnitdataCallback unitdata_cb
Daniel Willmannfa870f52018-01-17 12:37:14 +010082}
83
84type record MGCP_conn_parameters {
Harald Weltebb5a1212018-01-26 10:34:44 +010085 HostName callagent_ip,
86 PortNumber callagent_udp_port,
87 HostName mgw_ip,
88 PortNumber mgw_udp_port
Daniel Willmannfa870f52018-01-17 12:37:14 +010089}
90
Daniel Willmann166bbb32018-01-17 15:28:04 +010091function tr_MGCP_RecvFrom_R(template MgcpMessage msg)
92runs on MGCP_Emulation_CT return template MGCP_RecvFrom {
93 var template MGCP_RecvFrom mrf := {
94 connId := g_mgcp_conn_id,
95 remName := ?,
96 remPort := ?,
97 locName := ?,
98 locPort := ?,
99 msg := msg
100 }
101 return mrf;
102}
103
Harald Weltebb5a1212018-01-26 10:34:44 +0100104private function f_conn_id_known(MgcpConnectionId conn_id)
105runs on MGCP_Emulation_CT return boolean {
106 var integer i;
107 for (i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
108 if (MgcpConnectionTable[i].conn_id == conn_id) {
109 return true;
110 }
111 }
112 return false;
113}
114
115private function f_comp_known(MGCP_ConnHdlr client)
116runs on MGCP_Emulation_CT return boolean {
117 var integer i;
118 for (i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
119 if (MgcpConnectionTable[i].comp_ref == client) {
120 return true;
121 }
122 }
123 return false;
124}
125
126private function f_comp_by_conn_id(MgcpConnectionId conn_id)
127runs on MGCP_Emulation_CT return MGCP_ConnHdlr {
128 var integer i;
129 for (i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
130 if (MgcpConnectionTable[i].conn_id == conn_id) {
131 return MgcpConnectionTable[i].comp_ref;
132 }
133 }
134 log("MGCP Connection Table not found by Connection Id", conn_id);
135 setverdict(fail);
136 self.stop;
137}
138
139private function f_conn_id_by_comp(MGCP_ConnHdlr client)
140runs on MGCP_Emulation_CT return MgcpConnectionId {
141 var integer i;
142 for (i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
143 if (MgcpConnectionTable[i].comp_ref == client) {
144 return MgcpConnectionTable[i].conn_id;
145 }
146 }
147 log("MGCP Connection Table not found by component ", client);
148 setverdict(fail);
149 self.stop;
150}
151
152/* TODO: move this to MGCP_Types? */
153function f_mgcp_conn_id(MgcpMessage msg) return hexstring {
154 var MgcpParameterList params;
155 var integer i;
156 if (ischosen(msg.command)) {
157 params := msg.command.params;
158 } else {
159 params := msg.response.params;
160 }
161 for (i := 0; i < lengthof(params); i := i+1) {
162 if (params[i].code == "I") {
163 return str2hex(params[i].val);
164 }
165 }
166 return ''H;
167}
168
169private function f_conn_table_init()
170runs on MGCP_Emulation_CT {
171 for (var integer i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
172 MgcpConnectionTable[i].comp_ref := null;
173 MgcpConnectionTable[i].conn_id := omit;
174 }
175}
176
Daniel Willmann955627a2018-01-17 15:22:32 +0100177function main(MGCPOps ops, MGCP_conn_parameters p, charstring id) runs on MGCP_Emulation_CT {
Daniel Willmannfa870f52018-01-17 12:37:14 +0100178 var Result res;
179 g_mgcp_id := id;
Harald Weltebb5a1212018-01-26 10:34:44 +0100180 f_conn_table_init();
Daniel Willmann955627a2018-01-17 15:22:32 +0100181 f_expect_table_init();
Daniel Willmannfa870f52018-01-17 12:37:14 +0100182
183 map(self:MGCP, system:MGCP_CODEC_PT);
Harald Weltebb5a1212018-01-26 10:34:44 +0100184 if (p.callagent_udp_port == -1) {
185 res := MGCP_CodecPort_CtrlFunct.f_IPL4_listen(MGCP, p.mgw_ip, p.mgw_udp_port, { udp:={} });
186 } else {
187 res := MGCP_CodecPort_CtrlFunct.f_IPL4_connect(MGCP, p.callagent_ip, p.callagent_udp_port, p.mgw_ip, p.mgw_udp_port, -1, { udp:={} });
188 }
189
Daniel Willmann166bbb32018-01-17 15:28:04 +0100190 g_mgcp_conn_id := res.connId;
Harald Weltebb5a1212018-01-26 10:34:44 +0100191
Daniel Willmannfa870f52018-01-17 12:37:14 +0100192 while (true) {
Daniel Willmann166bbb32018-01-17 15:28:04 +0100193 var MGCP_ConnHdlr vc_conn;
194 var ExpectCriteria crit;
195 var MGCP_RecvFrom mrf;
196 var MgcpMessage msg;
197 var MgcpCommand cmd;
198 var MgcpResponse resp;
199
Daniel Willmannfa870f52018-01-17 12:37:14 +0100200 alt {
Daniel Willmann166bbb32018-01-17 15:28:04 +0100201 /* MGCP from client */
Harald Weltebb5a1212018-01-26 10:34:44 +0100202 [] MGCP_CLIENT.receive(MgcpResponse:?) -> value resp sender vc_conn {
Daniel Willmann166bbb32018-01-17 15:28:04 +0100203 /* Pass message through */
204 msg.response := resp;
Harald Weltebb5a1212018-01-26 10:34:44 +0100205 /* TODO: check which ConnectionID client has allocated + store in table? */
Daniel Willmann166bbb32018-01-17 15:28:04 +0100206 MGCP.send(t_MGCP_Send(g_mgcp_conn_id, msg));
Daniel Willmannfa870f52018-01-17 12:37:14 +0100207 }
Daniel Willmann166bbb32018-01-17 15:28:04 +0100208 [] MGCP.receive(tr_MGCP_RecvFrom_R(?)) -> value mrf {
Harald Weltebb5a1212018-01-26 10:34:44 +0100209 if (p.callagent_udp_port == -1) {
210 /* we aren't yet connected to the remote side port, let's fix this */
211 p.callagent_udp_port := mrf.remPort;
212 MGCP_CodecPort_CtrlFunct.f_IPL4_connect(MGCP, p.callagent_ip, p.callagent_udp_port, p.mgw_ip, p.mgw_udp_port, g_mgcp_conn_id, { udp:={} });
213 }
Daniel Willmann166bbb32018-01-17 15:28:04 +0100214 if (ischosen(mrf.msg.command)) {
215 cmd := mrf.msg.command;
Harald Weltebb5a1212018-01-26 10:34:44 +0100216 if (match(cmd, tr_MgcpCommand_CO)) {
217 /* connection-oriented MGCP */
218 if (cmd.line.verb == "CRCX") {
219 /* TODO: allocate ConnectionID here + store in Table? */
220 vc_conn := ops.create_cb.apply(cmd, id);
221 } else {
222 var MgcpConnectionId conn_id := f_mgcp_conn_id(mrf.msg);
223 vc_conn := f_comp_by_conn_id(conn_id);
224 }
225 MGCP_CLIENT.send(cmd) to vc_conn;
226 } else {
227 /* connectionless MGCP, i.e. messages without ConnectionId */
228 var template MgcpMessage r := ops.unitdata_cb.apply(mrf.msg);
229 if (isvalue(r)) {
230 MGCP.send(t_MGCP_Send(g_mgcp_conn_id, r));
231 }
232 }
Daniel Willmann166bbb32018-01-17 15:28:04 +0100233 } else {
234 setverdict(fail, "Received unexpected MGCP response: ", mrf.msg.response);
235 self.stop;
236 }
Daniel Willmannfa870f52018-01-17 12:37:14 +0100237 }
Harald Weltebb5a1212018-01-26 10:34:44 +0100238 [] MGCP_PROC.getcall(MGCPEM_register:{?,?}) -> param(crit, vc_conn) {
Daniel Willmann955627a2018-01-17 15:22:32 +0100239 f_create_expect(crit, vc_conn);
Harald Weltebb5a1212018-01-26 10:34:44 +0100240 MGCP_PROC.reply(MGCPEM_register:{crit, vc_conn});
Daniel Willmann955627a2018-01-17 15:22:32 +0100241 }
Daniel Willmannfa870f52018-01-17 12:37:14 +0100242 }
243 }
244}
245
246/* "Expect" Handling */
247
248/* */
Daniel Willmann955627a2018-01-17 15:22:32 +0100249type record ExpectCriteria {
250 MgcpConnectionId connid optional,
251 MgcpEndpoint endpoint optional,
252 MgcpTransId transid optional
Daniel Willmannfa870f52018-01-17 12:37:14 +0100253}
254
255type record ExpectData {
256 ExpectCriteria crit optional,
Daniel Willmann955627a2018-01-17 15:22:32 +0100257 MGCP_ConnHdlr vc_conn
Daniel Willmannfa870f52018-01-17 12:37:14 +0100258}
259
Daniel Willmann955627a2018-01-17 15:22:32 +0100260signature MGCPEM_register(in ExpectCriteria cmd, in MGCP_ConnHdlr hdlr);
Daniel Willmannfa870f52018-01-17 12:37:14 +0100261
262type port MGCPEM_PROC_PT procedure {
263 inout MGCPEM_register;
264} with { extension "internal" };
265
266function f_get_mgcp_by_crit(ExpectCriteria crit)
267return template MgcpCommand {
Harald Weltebb5a1212018-01-26 10:34:44 +0100268 var template MgcpCommand ret := {
269 line := {
270 verb := ?,
271 trans_id := ?,
272 ep := ?,
273 ver := ?
274 },
275 params := *,
276 sdp := *
277 }
278 if (ispresent(crit.connid)) {
279 ret.params := { *, ts_MgcpParConnectionId(crit.connid), * };
280 }
281 if (ispresent(crit.endpoint)) {
282 ret.line.ep := crit.endpoint;
283 }
284 if (ispresent(crit.transid)) {
285 ret.line.trans_id := crit.transid;
286 }
Daniel Willmannfa870f52018-01-17 12:37:14 +0100287
288 return ret;
289}
290
291/* Function that can be used as create_cb and will usse the expect table */
292function ExpectedCreateCallback(MgcpCommand cmd, charstring id)
293runs on MGCP_Emulation_CT return MGCP_ConnHdlr {
294 var MGCP_ConnHdlr ret := null;
295 var template MgcpCommand mgcpcmd;
296 var integer i;
297
298 /* Ensure cmd is a CRCX? */
299
Harald Weltebb5a1212018-01-26 10:34:44 +0100300 for (i := 0; i < sizeof(MgcpExpectTable); i := i+1) {
301 if (not ispresent(MgcpExpectTable[i].crit)) {
Daniel Willmannfa870f52018-01-17 12:37:14 +0100302 continue;
303 }
Daniel Willmann955627a2018-01-17 15:22:32 +0100304 /* FIXME: Ignore criteria for now */
Harald Weltebb5a1212018-01-26 10:34:44 +0100305 mgcpcmd := f_get_mgcp_by_crit(MgcpExpectTable[i].crit);
306 if (match(cmd, mgcpcmd)) {
307 ret := MgcpExpectTable[i].vc_conn;
Daniel Willmannfa870f52018-01-17 12:37:14 +0100308 /* Release this entry */
Harald Weltebb5a1212018-01-26 10:34:44 +0100309 MgcpExpectTable[i].crit := omit;
310 MgcpExpectTable[i].vc_conn := null;
Daniel Willmannfa870f52018-01-17 12:37:14 +0100311 log("Found Expect[", i, "] for ", cmd, " handled at ", ret);
312 return ret;
Harald Weltebb5a1212018-01-26 10:34:44 +0100313 }
Daniel Willmannfa870f52018-01-17 12:37:14 +0100314 }
315 setverdict(fail, "Couldn't find Expect for CRCX", cmd);
316 return ret;
317}
318
319private function f_create_expect(ExpectCriteria crit, MGCP_ConnHdlr hdlr)
320runs on MGCP_Emulation_CT {
321 var integer i;
322
323 /* Check an entry like this is not already presnt */
Harald Weltebb5a1212018-01-26 10:34:44 +0100324 for (i := 0; i < sizeof(MgcpExpectTable); i := i+1) {
325 if (crit == MgcpExpectTable[i].crit) {
Daniel Willmannfa870f52018-01-17 12:37:14 +0100326 setverdict(fail, "Crit already present", crit);
327 self.stop;
328 }
329 }
Harald Weltebb5a1212018-01-26 10:34:44 +0100330 for (i := 0; i < sizeof(MgcpExpectTable); i := i+1) {
331 if (not ispresent(MgcpExpectTable[i].crit)) {
332 MgcpExpectTable[i].crit := crit;
333 MgcpExpectTable[i].vc_conn := hdlr;
Daniel Willmannfa870f52018-01-17 12:37:14 +0100334 log("Created Expect[", i, "] for ", crit, " to be handled at ", hdlr);
335 return;
336 }
337 }
Harald Weltebb5a1212018-01-26 10:34:44 +0100338 setverdict(fail, "No space left in MgcpExpectTable")
339}
340
341/* client/conn_hdlr side function to use procedure port to create expect in emulation */
342function f_create_mgcp_expect(ExpectCriteria dest_number) runs on MGCP_ConnHdlr {
343 MGCP_PROC.call(MGCPEM_register:{dest_number, self}) {
344 [] MGCP_PROC.getreply(MGCPEM_register:{?,?}) {};
345 }
Daniel Willmannfa870f52018-01-17 12:37:14 +0100346}
347
Daniel Willmann955627a2018-01-17 15:22:32 +0100348private function f_expect_table_init()
349runs on MGCP_Emulation_CT {
350 var integer i;
Harald Weltebb5a1212018-01-26 10:34:44 +0100351 for (i := 0; i < sizeof(MgcpExpectTable); i := i + 1) {
352 MgcpExpectTable[i].crit := omit;
Daniel Willmann955627a2018-01-17 15:22:32 +0100353 }
354}
355
Harald Weltebb5a1212018-01-26 10:34:44 +0100356function DummyUnitdataCallback(MgcpMessage msg)
357runs on MGCP_Emulation_CT return template MgcpMessage {
358 log("Ignoring MGCP ", msg);
359 return omit;
360}
361
362
Daniel Willmannfa870f52018-01-17 12:37:14 +0100363}