418 lines
12 KiB
Plaintext
418 lines
12 KiB
Plaintext
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:{?,?}) {};
|
|
}
|
|
}
|
|
|
|
|
|
}
|