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