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