| module SMPP_Emulation { |
| |
| /* SMPP Emulation layer, sitting on top of SMPP_CodecPort. |
| * |
| * (C) 2018 by Harald Welte <laforge@gnumonks.org> |
| * All rights reserved. |
| * |
| * Released under the terms of GNU General Public License, Version 2 or |
| * (at your option) any later version. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| |
| import from Osmocom_Types all; |
| import from General_Types all; |
| import from SMPP_Types all; |
| import from SMPP_Templates all; |
| import from SMPP_CodecPort all; |
| import from SMPP_CodecPort_CtrlFunct all; |
| import from IPL4asp_Types all; |
| import from IPL4asp_PortType all; |
| import from Socket_API_Definitions all; |
| |
| /* general "base class" component definition, of which specific implementations |
| * derive themselves by menas of the "extends" feature */ |
| type component SMPP_ConnHdlr { |
| /* port towards SPPP_Emulation_CT */ |
| port SMPP_Conn_PT SMPP; |
| port SMPPEM_PROC_PT SMPP_PROC; |
| } |
| |
| |
| type component SMPP_Emulation_CT { |
| /* down-facing port to SMPP Codec port */ |
| port SMPP_CODEC_PT SMPP_PORT; |
| var IPL4asp_Types.ConnectionId g_smpp_conn_id := -1; |
| |
| var integer g_seq := 1; |
| |
| /* up-facing port to Clients */ |
| port SMPP_Conn_PT SMPP_CLIENT; |
| port SMPPEM_PROC_PT SMPP_PROC; |
| |
| var TransactionData TransactionTable[32]; |
| var ExpectData ExpectTable[32]; |
| } |
| |
| type port SMPP_Conn_PT message { |
| inout SMPP_PDU; |
| } with { extension "internal" }; |
| |
| type record TransactionData { |
| uint32_t tid optional, |
| SMPP_ConnHdlr vc_conn |
| } |
| |
| type record ExpectData { |
| SMPP_TON dst_ton optional, |
| SMPP_NPI dst_npi optional, |
| charstring dst_addr, |
| SMPP_ConnHdlr vc_conn |
| } |
| |
| private function f_trans_id_known(uint32_t tid) |
| runs on SMPP_Emulation_CT return boolean { |
| for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) { |
| if (TransactionTable[i].tid == tid) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private function f_comp_known(SMPP_ConnHdlr client) |
| runs on SMPP_Emulation_CT return boolean { |
| for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) { |
| if (TransactionTable[i].vc_conn == client) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private function f_comp_by_trans_id(uint32_t tid) |
| runs on SMPP_Emulation_CT return SMPP_ConnHdlr { |
| for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) { |
| if (TransactionTable[i].tid == tid) { |
| return TransactionTable[i].vc_conn; |
| } |
| } |
| setverdict(fail, "No componten for SMPP TID ", tid); |
| mtc.stop; |
| } |
| |
| |
| private function f_trans_table_init() |
| runs on SMPP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) { |
| TransactionTable[i].vc_conn := null; |
| TransactionTable[i].tid := omit; |
| } |
| } |
| |
| private function f_trans_table_add(SMPP_ConnHdlr vc_conn, uint32_t trans_id) |
| runs on SMPP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) { |
| if (TransactionTable[i].vc_conn == null) { |
| TransactionTable[i].vc_conn := vc_conn; |
| TransactionTable[i].tid := trans_id; |
| return; |
| } |
| } |
| testcase.stop("SMPP Trans table full!"); |
| } |
| |
| private function f_trans_table_del(uint32_t trans_id) |
| runs on SMPP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(TransactionTable); i := i+1) { |
| if (TransactionTable[i].tid == trans_id) { |
| TransactionTable[i].vc_conn := null; |
| TransactionTable[i].tid := omit; |
| return; |
| } |
| } |
| setverdict(fail, "SMPP Trans table attempt to delete non-existant ", trans_id); |
| mtc.stop; |
| } |
| |
| |
| |
| function f_connect(charstring remote_host, IPL4asp_Types.PortNumber remote_port, |
| charstring local_host, IPL4asp_Types.PortNumber local_port) |
| runs on SMPP_Emulation_CT { |
| var IPL4asp_Types.Result res; |
| res := SMPP_CodecPort_CtrlFunct.f_IPL4_connect(SMPP_PORT, remote_host, remote_port, |
| local_host, local_port, 0, { tcp :={} }); |
| if (not ispresent(res.connId)) { |
| setverdict(fail, "Could not connect to SMPP port, check your configuration"); |
| mtc.stop; |
| } |
| g_smpp_conn_id := res.connId; |
| } |
| |
| /* Function to use to bind to a local port as IPA server, accepting remote clients */ |
| function f_bind(charstring local_host, IPL4asp_Types.PortNumber local_port) |
| runs on SMPP_Emulation_CT { |
| var IPL4asp_Types.Result res; |
| res := SMPP_CodecPort_CtrlFunct.f_IPL4_listen(SMPP_PORT, local_host, local_port, { tcp:={} }); |
| g_smpp_conn_id := res.connId; |
| } |
| |
| |
| function main_server(EsmePars pars, charstring local_host, integer local_port) |
| runs on SMPP_Emulation_CT { |
| f_bind(local_host, local_port); |
| f_mainloop(pars); |
| } |
| |
| function main_client(EsmePars pars, charstring remote_host, integer remote_port, |
| charstring local_host, integer local_port) |
| runs on SMPP_Emulation_CT { |
| f_connect(remote_host, remote_port, local_host, local_port); |
| f_mainloop(pars); |
| } |
| |
| type enumerated EsmeMode { |
| MODE_TRANSMITTER, |
| MODE_RECEIVER, |
| MODE_TRANSCEIVER |
| } |
| type record EsmePars { |
| EsmeMode mode, |
| SMPP_Bind bind, |
| boolean esme_role |
| } |
| |
| private function f_tx_smpp(template (value) SMPP_PDU pdu) runs on SMPP_Emulation_CT { |
| pdu.header.seq_num := g_seq; |
| SMPP_PORT.send(ts_SMPP_Send(g_smpp_conn_id, pdu)); |
| g_seq := g_seq+1; |
| } |
| |
| private function f_rx_smpp(template SMPP_PDU pdu) runs on SMPP_Emulation_CT { |
| timer T_wait := 3.0; |
| T_wait.start; |
| alt { |
| [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id, pdu)) { } |
| [] T_wait.timeout { |
| setverdict(fail, "Timeout waiting for ", pdu); |
| mtc.stop; |
| } |
| } |
| } |
| |
| /* default altstep which we use throughout */ |
| private altstep as_smpp() runs on SMPP_Emulation_CT { |
| var SMPP_ConnHdlr vc_conn; |
| var SMPP_RecvFrom smpp_rf; |
| /* Answer to ENQUIRE LINK */ |
| [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id, |
| tr_SMPP(c_SMPP_command_id_enquire_link, ESME_ROK))) { |
| f_tx_smpp(ts_SMPP_ENQ_LINK_resp); |
| } |
| [] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id, |
| tr_SMPP(c_SMPP_command_id_alert_notification, ESME_ROK))) -> value smpp_rf { |
| /* TODO: dispatch to ConnHdlr based on some kind of expect mechanism? */ |
| vc_conn := f_exp_lookup(smpp_rf.msg.body.alert_notif.source_addr_ton, |
| smpp_rf.msg.body.alert_notif.source_addr_npi, |
| smpp_rf.msg.body.alert_notif.source_addr); |
| SMPP_CLIENT.send(smpp_rf.msg) to vc_conn; |
| } |
| [] SMPP_PORT.receive { |
| setverdict(fail, "Unexpected SMPP from peer"); |
| mtc.stop; |
| } |
| } |
| |
| function f_mainloop(EsmePars pars) |
| runs on SMPP_Emulation_CT { |
| |
| /* Set function for dissecting the binary stream into packets */ |
| var f_IPL4_getMsgLen vl_f := refers(f_IPL4_fixedMsgLen); |
| /* Offset: 0, size of length: 4, delta: 0, multiplier: 1, big-endian */ |
| SMPP_CodecPort_CtrlFunct.f_IPL4_setGetMsgLen(SMPP_PORT, g_smpp_conn_id, vl_f, {0, 4, 0, 1, 0}); |
| |
| f_trans_table_init(); |
| f_expect_table_init(); |
| |
| /* activate default altstep */ |
| var default d := activate(as_smpp()); |
| |
| if (pars.esme_role) { |
| /* BIND to SMSC */ |
| select (pars.mode) { |
| case (MODE_TRANSMITTER) { |
| f_tx_smpp(ts_SMPP_BIND_TX(pars.bind)); |
| /* FIXME: do we have to check for SEQ? */ |
| f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_transmitter_resp, ESME_ROK, g_seq)); |
| } |
| case (MODE_RECEIVER) { |
| f_tx_smpp(ts_SMPP_BIND_RX(pars.bind)); |
| /* FIXME: do we have to check for SEQ? */ |
| f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_receiver_resp, ESME_ROK, g_seq)); |
| } |
| case (MODE_TRANSCEIVER) { |
| f_tx_smpp(ts_SMPP_BIND_TRX(pars.bind)); |
| /* FIXME: do we have to check for SEQ? */ |
| f_rx_smpp(tr_SMPP(c_SMPP_command_id_bind_transceiver_resp, ESME_ROK)); |
| } |
| } |
| } else { |
| var SMPP_Bind_resp bresp := { |
| system_id := pars.bind.system_id, |
| opt_pars := {} |
| } |
| /* Expect bind from ESME */ |
| select (pars.mode) { |
| case (MODE_TRANSMITTER) { |
| f_rx_smpp(tr_SMPP_BIND_TX(pars.bind)); |
| /* FIXME: do we have to check for SEQ? */ |
| f_tx_smpp(ts_SMPP_BIND_TX_resp(ESME_ROK, bresp)); |
| } |
| case (MODE_RECEIVER) { |
| f_rx_smpp(tr_SMPP_BIND_RX(pars.bind)); |
| /* FIXME: do we have to check for SEQ? */ |
| f_tx_smpp(ts_SMPP_BIND_RX_resp(ESME_ROK, bresp)); |
| } |
| case (MODE_TRANSCEIVER) { |
| f_rx_smpp(tr_SMPP_BIND_TRX(pars.bind)); |
| /* FIXME: do we have to check for SEQ? */ |
| f_tx_smpp(ts_SMPP_BIND_TRX_resp(ESME_ROK, bresp)); |
| } |
| } |
| } |
| |
| while (true) { |
| var SMPP_ConnHdlr vc_conn; |
| var SMPP_RecvFrom smpp_rf; |
| var SMPP_PDU smpp; |
| var charstring dest_addr; |
| alt { |
| /* SMSC -> CLIENT: response, map by seq_nr */ |
| [pars.esme_role] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id, |
| tr_SMPP_esme_resp)) -> value smpp_rf { |
| var uint32_t trans_id := smpp_rf.msg.header.seq_num; |
| if (f_trans_id_known(trans_id)) { |
| vc_conn := f_comp_by_trans_id(trans_id); |
| SMPP_CLIENT.send(smpp_rf.msg) to vc_conn; |
| f_trans_table_del(trans_id); |
| } else { |
| log("Received SMPP response for unknown trans_id ", smpp_rf); |
| /* FIXME */ |
| } |
| } |
| /* SMSC -> CLIENT: DELIVER-SM.req */ |
| [pars.esme_role] SMPP_PORT.receive(tr_SMPP_Recv(g_smpp_conn_id, |
| tr_SMPP(c_SMPP_command_id_deliver_sm, ESME_ROK))) -> value smpp_rf { |
| vc_conn := f_exp_lookup(smpp_rf.msg.body.deliver_sm.dest_addr_ton, |
| smpp_rf.msg.body.deliver_sm.dest_addr_npi, |
| smpp_rf.msg.body.deliver_sm.destination_addr); |
| SMPP_CLIENT.send(smpp_rf.msg) to vc_conn; |
| } |
| |
| /* record seq_nr for commands from CLIENT -> SMSC */ |
| [pars.esme_role] SMPP_CLIENT.receive(tr_SMPP_esme_req) -> value smpp sender vc_conn { |
| /* register current seq_nr/trans_id */ |
| f_trans_table_add(vc_conn, g_seq); |
| f_tx_smpp(smpp); |
| } |
| /* pass responses 1:1 through from CLIENT -> SMSC */ |
| [pars.esme_role] SMPP_CLIENT.receive(tr_SMPP_smsc_resp) -> value smpp sender vc_conn { |
| SMPP_PORT.send(ts_SMPP_Send(g_smpp_conn_id, smpp)); |
| } |
| |
| [] SMPP_PROC.getcall(SMPPEM_register:{?,?}) -> param(dest_addr, vc_conn) { |
| f_create_expect(dest_addr, vc_conn); |
| SMPP_PROC.reply(SMPPEM_register:{dest_addr, vc_conn}) to vc_conn; |
| } |
| } |
| } |
| } |
| |
| /* Requests from ESME -> SMSC */ |
| template OCT4 SMPP_esme_req := ( |
| c_SMPP_command_id_submit_sm, |
| c_SMPP_command_id_replace_sm, |
| c_SMPP_command_id_cancel_sm, |
| c_SMPP_command_id_submit_multi |
| ); |
| template SMPP_PDU tr_SMPP_esme_req := tr_SMPP(SMPP_esme_req, ?); |
| |
| /* Responses from ESME -> SMSC */ |
| template OCT4 SMPP_esme_resp := ( |
| c_SMPP_command_id_submit_sm_resp, |
| c_SMPP_command_id_replace_sm_resp, |
| c_SMPP_command_id_cancel_sm_resp, |
| c_SMPP_command_id_submit_multi_resp |
| ); |
| template SMPP_PDU tr_SMPP_esme_resp := tr_SMPP(SMPP_esme_resp, ?); |
| |
| /* Requests from SMSC -> ESME */ |
| template OCT4 SMPP_smsc_req := ( |
| c_SMPP_command_id_deliver_sm |
| ); |
| template SMPP_PDU tr_SMPP_smsc_req := tr_SMPP(SMPP_smsc_req, ?); |
| |
| /* Responses from SMSC -> ESME */ |
| template OCT4 SMPP_smsc_resp := ( |
| c_SMPP_command_id_deliver_sm_resp |
| ); |
| template SMPP_PDU tr_SMPP_smsc_resp := tr_SMPP(SMPP_smsc_resp, ?); |
| |
| |
| |
| signature SMPPEM_register(charstring dst_addr, SMPP_ConnHdlr hdlr); |
| |
| type port SMPPEM_PROC_PT procedure { |
| inout SMPPEM_register; |
| } with { extension "internal" }; |
| |
| private function f_create_expect(charstring dest_number, SMPP_ConnHdlr hdlr) |
| runs on SMPP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) { |
| if (ExpectTable[i].vc_conn == null) { |
| ExpectTable[i] := { |
| dst_ton := omit, |
| dst_npi := omit, |
| dst_addr := dest_number, |
| vc_conn := hdlr |
| } |
| return; |
| } |
| } |
| testcase.stop("No space left in SmppExpectTable"); |
| } |
| |
| private function f_exp_lookup(SMPP_TON ton, SMPP_NPI npi, charstring dst) |
| runs on SMPP_Emulation_CT return SMPP_ConnHdlr { |
| for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) { |
| if (ExpectTable[i].vc_conn != null and ExpectTable[i].dst_addr == dst) { |
| if (ispresent(ExpectTable[i].dst_ton) and ExpectTable[i].dst_ton != ton) { |
| continue; |
| } |
| if (ispresent(ExpectTable[i].dst_npi) and ExpectTable[i].dst_npi != npi) { |
| continue; |
| } |
| return ExpectTable[i].vc_conn; |
| } |
| } |
| return null; |
| } |
| private function f_expect_table_init() |
| runs on SMPP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(ExpectTable); i := i+1) { |
| ExpectTable[i] := { |
| dst_ton := omit, |
| dst_npi := omit, |
| dst_addr := "", |
| vc_conn := null |
| }; |
| } |
| } |
| |
| |
| |
| |
| |
| /* client/conn_hdlr side function to use procedure port to create expect in emulation */ |
| function f_create_smpp_expect(charstring dest_number) runs on SMPP_ConnHdlr { |
| SMPP_PROC.call(SMPPEM_register:{dest_number, self}) { |
| [] SMPP_PROC.getreply(SMPPEM_register:{?,?}) {}; |
| } |
| } |
| |
| |
| } |