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].dst_addr == dest_number) { ExpectTable[i] := { dst_ton := omit, dst_npi := omit, dst_addr := dest_number, vc_conn := hdlr } return; } } 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:{?,?}) {}; } } }