blob: 83e7801f94ea5b529c1c6aa64beaa8fa2d99e244 [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);
91 self.stop;
92}
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 }
112 setverdict(fail, "SMPP Trans table full!");
113 self.stop;
114}
115
116private function f_trans_table_del(uint32_t trans_id)
117runs on SMPP_Emulation_CT {
118 for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) {
119 if (TransactionTable[i].tid == trans_id) {
120 TransactionTable[i].vc_conn := null;
121 TransactionTable[i].tid := omit;
122 return;
123 }
124 }
125 setverdict(fail, "SMPP Trans table attempt to delete non-existant ", trans_id);
126 self.stop;
127}
128
129
130
131function f_connect(charstring remote_host, IPL4asp_Types.PortNumber remote_port,
132 charstring local_host, IPL4asp_Types.PortNumber local_port)
133runs on SMPP_Emulation_CT {
134 var IPL4asp_Types.Result res;
135 res := SMPP_CodecPort_CtrlFunct.f_IPL4_connect(SMPP_PORT, remote_host, remote_port,
136 local_host, local_port, 0, { tcp :={} });
137 g_smpp_conn_id := res.connId;
138}
139
140/* Function to use to bind to a local port as IPA server, accepting remote clients */
141function f_bind(charstring local_host, IPL4asp_Types.PortNumber local_port)
142runs on SMPP_Emulation_CT {
143 var IPL4asp_Types.Result res;
144 res := SMPP_CodecPort_CtrlFunct.f_IPL4_listen(SMPP_PORT, local_host, local_port, { tcp:={} });
145 g_smpp_conn_id := res.connId;
146}
147
148
149function main_server(EsmePars pars, charstring local_host, integer local_port)
150runs on SMPP_Emulation_CT {
151 f_bind(local_host, local_port);
152 f_mainloop(pars);
153}
154
155function main_client(EsmePars pars, charstring remote_host, integer remote_port,
156 charstring local_host, integer local_port)
157runs on SMPP_Emulation_CT {
158 f_connect(remote_host, remote_port, local_host, local_port);
159 f_mainloop(pars);
160}
161
162type enumerated EsmeMode {
163 MODE_TRANSMITTER,
164 MODE_RECEIVER,
165 MODE_TRANSCEIVER
166}
167type record EsmePars {
168 EsmeMode mode,
169 SMPP_Bind bind,
170 boolean esme_role
171}
172
173private function f_tx_smpp(template (value) SMPP_PDU pdu) runs on SMPP_Emulation_CT {
174 pdu.header.seq_num := g_seq;
175 SMPP_PORT.send(ts_SMPP_Send(g_smpp_conn_id, pdu));
176 g_seq := g_seq+1;
177}
178
179private function f_rx_smpp(template SMPP_PDU pdu) runs on SMPP_Emulation_CT {
180 timer T_wait := 3.0;
181 T_wait.start;
182 alt {
183 [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id, pdu)) { }
184 [] T_wait.timeout {
185 setverdict(fail, "Timeout waiting for ", pdu);
186 self.stop;
187 }
188 }
189}
190
191/* default altstep which we use throughout */
192private altstep as_smpp() runs on SMPP_Emulation_CT {
193 var SMPP_ConnHdlr vc_conn;
194 var SMPP_RecvFrom smpp_rf;
195 /* Answer to ENQUIRE LINK */
196 [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
197 tr_SMPP(c_SMPP_command_id_enquire_link, ESME_ROK))) {
198 f_tx_smpp(ts_SMPP_ENQ_LINK_resp);
199 }
200 [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
201 tr_SMPP(c_SMPP_command_id_alert_notification, ESME_ROK))) -> value smpp_rf {
202 /* TODO: dispatch to ConnHdlr based on some kind of expect mechanism? */
203 vc_conn := f_exp_lookup(smpp_rf.msg.body.alert_notif.source_addr_ton,
204 smpp_rf.msg.body.alert_notif.source_addr_npi,
205 smpp_rf.msg.body.alert_notif.source_addr);
206 SMPP_CLIENT.send(smpp_rf.msg) to vc_conn;
207 }
208 [] SMPP_PORT.receive {
209 setverdict(fail, "Unexpected SMPP from peer");
210 self.stop;
211 }
212}
213
214function f_mainloop(EsmePars pars)
215runs on SMPP_Emulation_CT {
216
217 /* Set function for dissecting the binary stream into packets */
218 var f_IPL4_getMsgLen vl_f := refers(f_IPL4_fixedMsgLen);
219 /* Offset: 0, size of length: 4, delta: 0, multiplier: 1, big-endian */
220 SMPP_CodecPort_CtrlFunct.f_IPL4_setGetMsgLen(SMPP_PORT, g_smpp_conn_id, vl_f, {0, 4, 0, 1, 0});
221
222 f_trans_table_init();
223 f_expect_table_init();
224
225 /* activate default altstep */
226 var default d := activate(as_smpp());
227
228 if (pars.esme_role) {
229 /* BIND to SMSC */
230 select (pars.mode) {
231 case (MODE_TRANSMITTER) {
232 f_tx_smpp(ts_SMPP_BIND_TX(pars.bind));
233 /* FIXME: do we have to check for SEQ? */
234 f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_transmitter_resp, ESME_ROK, g_seq));
235 }
236 case (MODE_RECEIVER) {
237 f_tx_smpp(ts_SMPP_BIND_RX(pars.bind));
238 /* FIXME: do we have to check for SEQ? */
239 f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_receiver_resp, ESME_ROK, g_seq));
240 }
241 case (MODE_TRANSCEIVER) {
242 f_tx_smpp(ts_SMPP_BIND_TRX(pars.bind));
243 /* FIXME: do we have to check for SEQ? */
244 f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_transceiver_resp, ESME_ROK));
245 }
246 }
247 } else {
248 var SMPP_Bind_resp bresp := {
249 system_id := pars.bind.system_id,
250 opt_pars := {}
251 }
252 /* Expect bind from ESME */
253 select (pars.mode) {
254 case (MODE_TRANSMITTER) {
255 f_rx_smpp(tr_SMPP_BIND_TX(pars.bind));
256 /* FIXME: do we have to check for SEQ? */
257 f_tx_smpp(ts_SMPP_BIND_TX_resp(ESME_ROK, bresp));
258 }
259 case (MODE_RECEIVER) {
260 f_rx_smpp(tr_SMPP_BIND_RX(pars.bind));
261 /* FIXME: do we have to check for SEQ? */
262 f_tx_smpp(ts_SMPP_BIND_RX_resp(ESME_ROK, bresp));
263 }
264 case (MODE_TRANSCEIVER) {
265 f_rx_smpp(tr_SMPP_BIND_TRX(pars.bind));
266 /* FIXME: do we have to check for SEQ? */
267 f_tx_smpp(ts_SMPP_BIND_TRX_resp(ESME_ROK, bresp));
268 }
269 }
270 }
271
272 while (true) {
273 var SMPP_ConnHdlr vc_conn;
274 var SMPP_RecvFrom smpp_rf;
275 var SMPP_PDU smpp;
276 var charstring dest_addr;
277 alt {
278 /* SMSC -> CLIENT: response, map by seq_nr */
279 [pars.esme_role] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
280 tr_SMPP_esme_resp)) -> value smpp_rf {
281 var uint32_t trans_id := smpp_rf.msg.header.seq_num;
282 if (f_trans_id_known(trans_id)) {
283 vc_conn := f_comp_by_trans_id(trans_id);
284 SMPP_CLIENT.send(smpp_rf.msg) to vc_conn;
285 f_trans_table_del(trans_id);
286 } else {
287 log("Received SMPP response for unknown trans_id ", smpp_rf);
288 /* FIXME */
289 }
290 }
291 /* SMSC -> CLIENT: DELIVER-SM.req */
292 [pars.esme_role] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id,
293 tr_SMPP(c_SMPP_command_id_deliver_sm, ESME_ROK))) -> value smpp_rf {
294 vc_conn := f_exp_lookup(smpp_rf.msg.body.deliver_sm.dest_addr_ton,
295 smpp_rf.msg.body.deliver_sm.dest_addr_npi,
296 smpp_rf.msg.body.deliver_sm.destination_addr);
297 SMPP_CLIENT.send(smpp_rf.msg) to vc_conn;
298 }
299
300 /* record seq_nr for commands from CLIENT -> SMSC */
301 [pars.esme_role] SMPP_CLIENT.receive(tr_SMPP_esme_req) -> value smpp sender vc_conn {
302 /* register current seq_nr/trans_id */
303 f_trans_table_add(vc_conn, g_seq);
304 f_tx_smpp(smpp);
305 }
306 /* pass responses 1:1 through from CLIENT -> SMSC */
307 [pars.esme_role] SMPP_CLIENT.receive(tr_SMPP_smsc_resp) -> value smpp sender vc_conn {
308 SMPP_PORT.send(ts_SMPP_Send(g_smpp_conn_id, smpp));
309 }
310
311 [] SMPP_PROC.getcall(SMPPEM_register:{?,?}) -> param(dest_addr, vc_conn) {
312 f_create_expect(dest_addr, vc_conn);
313 SMPP_PROC.reply(SMPPEM_register:{dest_addr, vc_conn});
314 }
315 }
316 }
317}
318
319/* Requests from ESME -> SMSC */
320template OCT4 SMPP_esme_req := (
321 c_SMPP_command_id_submit_sm,
322 c_SMPP_command_id_replace_sm,
323 c_SMPP_command_id_cancel_sm,
324 c_SMPP_command_id_submit_multi
325);
326template SMPP_PDU tr_SMPP_esme_req := tr_SMPP(SMPP_esme_req, ?);
327
328/* Responses from ESME -> SMSC */
329template OCT4 SMPP_esme_resp := (
330 c_SMPP_command_id_submit_sm_resp,
331 c_SMPP_command_id_replace_sm_resp,
332 c_SMPP_command_id_cancel_sm_resp,
333 c_SMPP_command_id_submit_multi_resp
334);
335template SMPP_PDU tr_SMPP_esme_resp := tr_SMPP(SMPP_esme_resp, ?);
336
337/* Requests from SMSC -> ESME */
338template OCT4 SMPP_smsc_req := (
339 c_SMPP_command_id_deliver_sm
340);
341template SMPP_PDU tr_SMPP_smsc_req := tr_SMPP(SMPP_smsc_req, ?);
342
343/* Responses from SMSC -> ESME */
344template OCT4 SMPP_smsc_resp := (
345 c_SMPP_command_id_deliver_sm_resp
346);
347template SMPP_PDU tr_SMPP_smsc_resp := tr_SMPP(SMPP_smsc_resp, ?);
348
349
350
351signature SMPPEM_register(charstring dst_addr, SMPP_ConnHdlr hdlr);
352
353type port SMPPEM_PROC_PT procedure {
354 inout SMPPEM_register;
355} with { extension "internal" };
356
357private function f_create_expect(charstring dest_number, SMPP_ConnHdlr hdlr)
358runs on SMPP_Emulation_CT {
359 for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
360 if (ExpectTable[i].vc_conn == null) {
361 ExpectTable[i] := {
362 dst_ton := omit,
363 dst_npi := omit,
364 dst_addr := dest_number,
365 vc_conn := hdlr
366 }
367 return;
368 }
369 }
370 setverdict(fail, "No space left in SmppExpectTable");
371 self.stop;
372}
373
374private function f_exp_lookup(SMPP_TON ton, SMPP_NPI npi, charstring dst)
375runs on SMPP_Emulation_CT return SMPP_ConnHdlr {
376 for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
377 if (ExpectTable[i].vc_conn != null and ExpectTable[i].dst_addr == dst) {
378 if (ispresent(ExpectTable[i].dst_ton) and ExpectTable[i].dst_ton != ton) {
379 continue;
380 }
381 if (ispresent(ExpectTable[i].dst_npi) and ExpectTable[i].dst_npi != npi) {
382 continue;
383 }
384 return ExpectTable[i].vc_conn;
385 }
386 }
387 return null;
388}
389private function f_expect_table_init()
390runs on SMPP_Emulation_CT {
391 for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) {
392 ExpectTable[i] := {
393 dst_ton := omit,
394 dst_npi := omit,
395 dst_addr := "",
396 vc_conn := null
397 };
398 }
399}
400
401
402
403
404
405/* client/conn_hdlr side function to use procedure port to create expect in emulation */
406function f_create_smpp_expect(charstring dest_number) runs on SMPP_ConnHdlr {
407 SMPP_PROC.call(SMPPEM_register:{dest_number, self}) {
408 [] SMPP_PROC.getreply(SMPPEM_register:{?,?}) {};
409 }
410}
411
412
413}