blob: 6c808ebee9ab8cec83c449b21a3ec6dc3dd5d662 [file] [log] [blame]
Harald Welte32ff8b92018-04-14 10:57:41 +02001module SMPP_Emulation {
2
3/* SMPP Emulation layer, sitting on top of SMPP_CodecPort.
4 *
5 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
6 * All rights reserved.
7 *
8 * Released under the terms of GNU General Public License, Version 2 or
9 * (at your option) any later version.
Harald Welte34b5a952019-05-27 11:54:11 +020010 *
11 * SPDX-License-Identifier: GPL-2.0-or-later
Harald Welte32ff8b92018-04-14 10:57:41 +020012 */
13
14
15import from Osmocom_Types all;
16import from General_Types all;
17import from SMPP_Types all;
18import from SMPP_Templates all;
19import from SMPP_CodecPort all;
20import from SMPP_CodecPort_CtrlFunct all;
21import from IPL4asp_Types all;
22import from IPL4asp_PortType all;
23import from Socket_API_Definitions all;
24
25/* general "base class" component definition, of which specific implementations
26 * derive themselves by menas of the "extends" feature */
27type component SMPP_ConnHdlr {
28 /* port towards SPPP_Emulation_CT */
29 port SMPP_Conn_PT SMPP;
30 port SMPPEM_PROC_PT SMPP_PROC;
31}
32
33
34type component SMPP_Emulation_CT {
35 /* down-facing port to SMPP Codec port */
36 port SMPP_CODEC_PT SMPP_PORT;
37 var IPL4asp_Types.ConnectionId g_smpp_conn_id := -1;
38
39 var integer g_seq := 1;
40
41 /* up-facing port to Clients */
42 port SMPP_Conn_PT SMPP_CLIENT;
43 port SMPPEM_PROC_PT SMPP_PROC;
44
45 var TransactionData TransactionTable[32];
46 var ExpectData ExpectTable[32];
47}
48
49type port SMPP_Conn_PT message {
50 inout SMPP_PDU;
51} with { extension "internal" };
52
53type record TransactionData {
54 uint32_t tid optional,
55 SMPP_ConnHdlr vc_conn
56}
57
58type record ExpectData {
59 SMPP_TON dst_ton optional,
60 SMPP_NPI dst_npi optional,
61 charstring dst_addr,
62 SMPP_ConnHdlr vc_conn
63}
64
65private function f_trans_id_known(uint32_t tid)
66runs on SMPP_Emulation_CT return boolean {
67 for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) {
68 if (TransactionTable[i].tid == tid) {
69 return true;
70 }
71 }
72 return false;
73}
74
75private function f_comp_known(SMPP_ConnHdlr client)
76runs on SMPP_Emulation_CT return boolean {
77 for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) {
78 if (TransactionTable[i].vc_conn == client) {
79 return true;
80 }
81 }
82 return false;
83}
84
85private function f_comp_by_trans_id(uint32_t tid)
86runs on SMPP_Emulation_CT return SMPP_ConnHdlr {
87 for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) {
88 if (TransactionTable[i].tid == tid) {
89 return TransactionTable[i].vc_conn;
90 }
91 }
92 setverdict(fail, "No componten for SMPP TID ", tid);
Daniel Willmanne4ff5372018-07-05 17:35:03 +020093 mtc.stop;
Harald Welte32ff8b92018-04-14 10:57:41 +020094}
95
96
97private function f_trans_table_init()
98runs on SMPP_Emulation_CT {
99 for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) {
100 TransactionTable[i].vc_conn := null;
101 TransactionTable[i].tid := omit;
102 }
103}
104
105private function f_trans_table_add(SMPP_ConnHdlr vc_conn, uint32_t trans_id)
106runs on SMPP_Emulation_CT {
107 for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) {
108 if (TransactionTable[i].vc_conn == null) {
109 TransactionTable[i].vc_conn := vc_conn;
110 TransactionTable[i].tid := trans_id;
111 return;
112 }
113 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200114 testcase.stop("SMPP Trans table full!");
Harald Welte32ff8b92018-04-14 10:57:41 +0200115}
116
117private function f_trans_table_del(uint32_t trans_id)
118runs on SMPP_Emulation_CT {
119 for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) {
120 if (TransactionTable[i].tid == trans_id) {
121 TransactionTable[i].vc_conn := null;
122 TransactionTable[i].tid := omit;
123 return;
124 }
125 }
126 setverdict(fail, "SMPP Trans table attempt to delete non-existant ", trans_id);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200127 mtc.stop;
Harald Welte32ff8b92018-04-14 10:57:41 +0200128}
129
130
131
132function f_connect(charstring remote_host, IPL4asp_Types.PortNumber remote_port,
133 charstring local_host, IPL4asp_Types.PortNumber local_port)
134runs on SMPP_Emulation_CT {
135 var IPL4asp_Types.Result res;
136 res := SMPP_CodecPort_CtrlFunct.f_IPL4_connect(SMPP_PORT, remote_host, remote_port,
137 local_host, local_port, 0, { tcp :={} });
Harald Welte9220f632018-05-23 20:27:02 +0200138 if (not ispresent(res.connId)) {
139 setverdict(fail, "Could not connect to SMPP port, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200140 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200141 }
Harald Welte32ff8b92018-04-14 10:57:41 +0200142 g_smpp_conn_id := res.connId;
143}
144
145/* Function to use to bind to a local port as IPA server, accepting remote clients */
146function f_bind(charstring local_host, IPL4asp_Types.PortNumber local_port)
147runs on SMPP_Emulation_CT {
148 var IPL4asp_Types.Result res;
149 res := SMPP_CodecPort_CtrlFunct.f_IPL4_listen(SMPP_PORT, local_host, local_port, { tcp:={} });
150 g_smpp_conn_id := res.connId;
151}
152
153
154function main_server(EsmePars pars, charstring local_host, integer local_port)
155runs on SMPP_Emulation_CT {
156 f_bind(local_host, local_port);
157 f_mainloop(pars);
158}
159
160function main_client(EsmePars pars, charstring remote_host, integer remote_port,
161 charstring local_host, integer local_port)
162runs on SMPP_Emulation_CT {
163 f_connect(remote_host, remote_port, local_host, local_port);
164 f_mainloop(pars);
165}
166
167type enumerated EsmeMode {
168 MODE_TRANSMITTER,
169 MODE_RECEIVER,
170 MODE_TRANSCEIVER
171}
172type record EsmePars {
173 EsmeMode mode,
174 SMPP_Bind bind,
175 boolean esme_role
176}
177
178private function f_tx_smpp(template (value) SMPP_PDU pdu) runs on SMPP_Emulation_CT {
179 pdu.header.seq_num := g_seq;
180 SMPP_PORT.send(ts_SMPP_Send(g_smpp_conn_id, pdu));
181 g_seq := g_seq+1;
182}
183
184private function f_rx_smpp(template SMPP_PDU pdu) runs on SMPP_Emulation_CT {
185 timer T_wait := 3.0;
186 T_wait.start;
187 alt {
188 [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id, pdu)) { }
189 [] T_wait.timeout {
190 setverdict(fail, "Timeout waiting for ", pdu);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200191 mtc.stop;
Harald Welte32ff8b92018-04-14 10:57:41 +0200192 }
193 }
194}
195
196/* default altstep which we use throughout */
197private altstep as_smpp() runs on SMPP_Emulation_CT {
198 var SMPP_ConnHdlr vc_conn;
199 var SMPP_RecvFrom smpp_rf;
200 /* Answer to ENQUIRE LINK */
201 [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
202 tr_SMPP(c_SMPP_command_id_enquire_link, ESME_ROK))) {
203 f_tx_smpp(ts_SMPP_ENQ_LINK_resp);
204 }
205 [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
206 tr_SMPP(c_SMPP_command_id_alert_notification, ESME_ROK))) -> value smpp_rf {
207 /* TODO: dispatch to ConnHdlr based on some kind of expect mechanism? */
208 vc_conn := f_exp_lookup(smpp_rf.msg.body.alert_notif.source_addr_ton,
209 smpp_rf.msg.body.alert_notif.source_addr_npi,
210 smpp_rf.msg.body.alert_notif.source_addr);
211 SMPP_CLIENT.send(smpp_rf.msg) to vc_conn;
212 }
213 [] SMPP_PORT.receive {
214 setverdict(fail, "Unexpected SMPP from peer");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200215 mtc.stop;
Harald Welte32ff8b92018-04-14 10:57:41 +0200216 }
217}
218
219function f_mainloop(EsmePars pars)
220runs on SMPP_Emulation_CT {
221
222 /* Set function for dissecting the binary stream into packets */
223 var f_IPL4_getMsgLen vl_f := refers(f_IPL4_fixedMsgLen);
224 /* Offset: 0, size of length: 4, delta: 0, multiplier: 1, big-endian */
225 SMPP_CodecPort_CtrlFunct.f_IPL4_setGetMsgLen(SMPP_PORT, g_smpp_conn_id, vl_f, {0, 4, 0, 1, 0});
226
227 f_trans_table_init();
228 f_expect_table_init();
229
230 /* activate default altstep */
231 var default d := activate(as_smpp());
232
233 if (pars.esme_role) {
234 /* BIND to SMSC */
235 select (pars.mode) {
236 case (MODE_TRANSMITTER) {
237 f_tx_smpp(ts_SMPP_BIND_TX(pars.bind));
238 /* FIXME: do we have to check for SEQ? */
239 f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_transmitter_resp, ESME_ROK, g_seq));
240 }
241 case (MODE_RECEIVER) {
242 f_tx_smpp(ts_SMPP_BIND_RX(pars.bind));
243 /* FIXME: do we have to check for SEQ? */
244 f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_receiver_resp, ESME_ROK, g_seq));
245 }
246 case (MODE_TRANSCEIVER) {
247 f_tx_smpp(ts_SMPP_BIND_TRX(pars.bind));
248 /* FIXME: do we have to check for SEQ? */
249 f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_transceiver_resp, ESME_ROK));
250 }
251 }
252 } else {
253 var SMPP_Bind_resp bresp := {
254 system_id := pars.bind.system_id,
255 opt_pars := {}
256 }
257 /* Expect bind from ESME */
258 select (pars.mode) {
259 case (MODE_TRANSMITTER) {
260 f_rx_smpp(tr_SMPP_BIND_TX(pars.bind));
261 /* FIXME: do we have to check for SEQ? */
262 f_tx_smpp(ts_SMPP_BIND_TX_resp(ESME_ROK, bresp));
263 }
264 case (MODE_RECEIVER) {
265 f_rx_smpp(tr_SMPP_BIND_RX(pars.bind));
266 /* FIXME: do we have to check for SEQ? */
267 f_tx_smpp(ts_SMPP_BIND_RX_resp(ESME_ROK, bresp));
268 }
269 case (MODE_TRANSCEIVER) {
270 f_rx_smpp(tr_SMPP_BIND_TRX(pars.bind));
271 /* FIXME: do we have to check for SEQ? */
272 f_tx_smpp(ts_SMPP_BIND_TRX_resp(ESME_ROK, bresp));
273 }
274 }
275 }
276
277 while (true) {
278 var SMPP_ConnHdlr vc_conn;
279 var SMPP_RecvFrom smpp_rf;
280 var SMPP_PDU smpp;
281 var charstring dest_addr;
282 alt {
283 /* SMSC -> CLIENT: response, map by seq_nr */
284 [pars.esme_role] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
285 tr_SMPP_esme_resp)) -> value smpp_rf {
286 var uint32_t trans_id := smpp_rf.msg.header.seq_num;
287 if (f_trans_id_known(trans_id)) {
288 vc_conn := f_comp_by_trans_id(trans_id);
289 SMPP_CLIENT.send(smpp_rf.msg) to vc_conn;
290 f_trans_table_del(trans_id);
291 } else {
292 log("Received SMPP response for unknown trans_id ", smpp_rf);
293 /* FIXME */
294 }
295 }
296 /* SMSC -> CLIENT: DELIVER-SM.req */
297 [pars.esme_role] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
298 tr_SMPP(c_SMPP_command_id_deliver_sm, ESME_ROK))) -> value smpp_rf {
299 vc_conn := f_exp_lookup(smpp_rf.msg.body.deliver_sm.dest_addr_ton,
300 smpp_rf.msg.body.deliver_sm.dest_addr_npi,
301 smpp_rf.msg.body.deliver_sm.destination_addr);
302 SMPP_CLIENT.send(smpp_rf.msg) to vc_conn;
303 }
304
305 /* record seq_nr for commands from CLIENT -> SMSC */
306 [pars.esme_role] SMPP_CLIENT.receive(tr_SMPP_esme_req) -> value smpp sender vc_conn {
307 /* register current seq_nr/trans_id */
308 f_trans_table_add(vc_conn, g_seq);
309 f_tx_smpp(smpp);
310 }
311 /* pass responses 1:1 through from CLIENT -> SMSC */
312 [pars.esme_role] SMPP_CLIENT.receive(tr_SMPP_smsc_resp) -> value smpp sender vc_conn {
313 SMPP_PORT.send(ts_SMPP_Send(g_smpp_conn_id, smpp));
314 }
315
316 [] SMPP_PROC.getcall(SMPPEM_register:{?,?}) -> param(dest_addr, vc_conn) {
317 f_create_expect(dest_addr, vc_conn);
Harald Weltee32ad992018-05-31 22:17:46 +0200318 SMPP_PROC.reply(SMPPEM_register:{dest_addr, vc_conn}) to vc_conn;
Harald Welte32ff8b92018-04-14 10:57:41 +0200319 }
320 }
321 }
322}
323
324/* Requests from ESME -> SMSC */
325template OCT4 SMPP_esme_req := (
326 c_SMPP_command_id_submit_sm,
327 c_SMPP_command_id_replace_sm,
328 c_SMPP_command_id_cancel_sm,
329 c_SMPP_command_id_submit_multi
330);
331template SMPP_PDU tr_SMPP_esme_req := tr_SMPP(SMPP_esme_req, ?);
332
333/* Responses from ESME -> SMSC */
334template OCT4 SMPP_esme_resp := (
335 c_SMPP_command_id_submit_sm_resp,
336 c_SMPP_command_id_replace_sm_resp,
337 c_SMPP_command_id_cancel_sm_resp,
338 c_SMPP_command_id_submit_multi_resp
339);
340template SMPP_PDU tr_SMPP_esme_resp := tr_SMPP(SMPP_esme_resp, ?);
341
342/* Requests from SMSC -> ESME */
343template OCT4 SMPP_smsc_req := (
344 c_SMPP_command_id_deliver_sm
345);
346template SMPP_PDU tr_SMPP_smsc_req := tr_SMPP(SMPP_smsc_req, ?);
347
348/* Responses from SMSC -> ESME */
349template OCT4 SMPP_smsc_resp := (
350 c_SMPP_command_id_deliver_sm_resp
351);
352template SMPP_PDU tr_SMPP_smsc_resp := tr_SMPP(SMPP_smsc_resp, ?);
353
354
355
356signature SMPPEM_register(charstring dst_addr, SMPP_ConnHdlr hdlr);
357
358type port SMPPEM_PROC_PT procedure {
359 inout SMPPEM_register;
360} with { extension "internal" };
361
362private function f_create_expect(charstring dest_number, SMPP_ConnHdlr hdlr)
363runs on SMPP_Emulation_CT {
364 for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
Neels Hofmeyrf8aa6c12021-07-28 00:57:09 +0200365 if (ExpectTable[i].dst_addr == dest_number) {
366 ExpectTable[i] := {
367 dst_ton := omit,
368 dst_npi := omit,
369 dst_addr := dest_number,
370 vc_conn := hdlr
371 }
372 return;
373 }
374 }
375 for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
Harald Welte32ff8b92018-04-14 10:57:41 +0200376 if (ExpectTable[i].vc_conn == null) {
377 ExpectTable[i] := {
378 dst_ton := omit,
379 dst_npi := omit,
380 dst_addr := dest_number,
381 vc_conn := hdlr
382 }
383 return;
384 }
385 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200386 testcase.stop("No space left in SmppExpectTable");
Harald Welte32ff8b92018-04-14 10:57:41 +0200387}
388
389private function f_exp_lookup(SMPP_TON ton, SMPP_NPI npi, charstring dst)
390runs on SMPP_Emulation_CT return SMPP_ConnHdlr {
391 for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
392 if (ExpectTable[i].vc_conn != null and ExpectTable[i].dst_addr == dst) {
393 if (ispresent(ExpectTable[i].dst_ton) and ExpectTable[i].dst_ton != ton) {
394 continue;
395 }
396 if (ispresent(ExpectTable[i].dst_npi) and ExpectTable[i].dst_npi != npi) {
397 continue;
398 }
399 return ExpectTable[i].vc_conn;
400 }
401 }
402 return null;
403}
404private function f_expect_table_init()
405runs on SMPP_Emulation_CT {
406 for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
407 ExpectTable[i] := {
408 dst_ton := omit,
409 dst_npi := omit,
410 dst_addr := "",
411 vc_conn := null
412 };
413 }
414}
415
416
417
418
419
420/* client/conn_hdlr side function to use procedure port to create expect in emulation */
421function f_create_smpp_expect(charstring dest_number) runs on SMPP_ConnHdlr {
422 SMPP_PROC.call(SMPPEM_register:{dest_number, self}) {
423 [] SMPP_PROC.getreply(SMPPEM_register:{?,?}) {};
424 }
425}
426
427
428}