use std::net::IpAddr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use smpp34::client::{SmppClient, SmppClientListener, BIND_TYPE, SMSC};
use smpp34::server::ESME;
use smpp34::{
bind_transceiver, bind_transceiver_resp, submit_sm, submit_sm_resp, SmppConnectionInformation,
SmppServer, SmppServerListener,
};
use tokio::sync::{oneshot, Mutex, Semaphore};
const REQUESTS: usize = 20_000;
const WINDOW: usize = 32;
const RESPONSE_TIMER_MS: u64 = 2_000;
const SESSION_INIT_TIMER_MS: u64 = 5_000;
const ENQUIRE_LINK_TIMER_MS: u64 = 30_000;
const INACTIVITY_TIMER_MS: u64 = 300_000;
const BUFFER_SIZE: usize = 1_500;
fn free_port() -> u16 {
std::net::TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap()
.port()
}
struct AckEverySubmit;
#[async_trait]
impl SmppServerListener for AckEverySubmit {
async fn on_bind_transceiver(
&self,
req: bind_transceiver,
_c: &SmppConnectionInformation,
_s: &String,
) -> bind_transceiver_resp {
req.accept("TEST-SMSC".to_string(), Some(0x34))
}
async fn on_submit_sm(
&self,
req: submit_sm,
_c: &SmppConnectionInformation,
_s: &String,
) -> submit_sm_resp {
req.accept("id".to_string())
}
}
struct CaptureSmsc {
tx: Mutex<Option<oneshot::Sender<Arc<SMSC>>>>,
}
#[async_trait]
impl SmppClientListener for CaptureSmsc {
async fn on_smsc_bound(&self, smsc: SMSC, _s: &String) {
if let Some(tx) = self.tx.lock().await.take() {
let _ = tx.send(Arc::new(smsc));
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn submit_sm_resp_is_never_dropped_under_load() {
let port = free_port();
let mut server = SmppServer::new_with_default_timers(
IpAddr::from([127, 0, 0, 1]),
port,
Arc::new(AckEverySubmit),
SESSION_INIT_TIMER_MS,
ENQUIRE_LINK_TIMER_MS,
INACTIVITY_TIMER_MS,
RESPONSE_TIMER_MS,
BUFFER_SIZE,
);
server.start().await;
tokio::time::sleep(Duration::from_millis(100)).await;
let (tx, rx) = oneshot::channel();
let mut client = SmppClient::new_with_default_timers(
"127.0.0.1".to_string(),
port,
false,
BIND_TYPE::TRX,
"load".to_string(),
"load".to_string(),
String::new(),
1,
1,
String::new(),
Arc::new(CaptureSmsc {
tx: Mutex::new(Some(tx)),
}),
SESSION_INIT_TIMER_MS,
ENQUIRE_LINK_TIMER_MS,
INACTIVITY_TIMER_MS,
RESPONSE_TIMER_MS,
BUFFER_SIZE,
WINDOW,
);
client.start().await;
let smsc = tokio::time::timeout(Duration::from_secs(5), rx)
.await
.expect("bind timed out")
.expect("bind channel dropped");
let sem = Arc::new(Semaphore::new(WINDOW));
let lost = Arc::new(AtomicU64::new(0));
let mut handles = Vec::with_capacity(REQUESTS);
for _ in 0..REQUESTS {
let permit = sem.clone().acquire_owned().await.expect("semaphore closed");
let smsc = smsc.clone();
let lost = lost.clone();
handles.push(tokio::spawn(async move {
let sent = smsc
.submit_sm()
.source_addr("15550100".to_string())
.destination_addr("15550199".to_string())
.short_message(b"correlation".to_vec())
.send()
.await;
if sent.is_err() {
lost.fetch_add(1, Ordering::Relaxed);
}
drop(permit);
}));
}
for h in handles {
h.await.expect("submit task panicked");
}
client.stop().await;
assert_eq!(
lost.load(Ordering::Relaxed),
0,
"{REQUESTS} submit_sm should all have been correlated with their \
submit_sm_resp; a non-zero count means the read loop dropped a \
response because its pending entry was not registered yet"
);
}
struct CaptureEsme {
tx: Mutex<Option<oneshot::Sender<Arc<ESME>>>>,
}
#[async_trait]
impl SmppServerListener for CaptureEsme {
async fn on_bind_transceiver(
&self,
req: bind_transceiver,
_c: &SmppConnectionInformation,
_s: &String,
) -> bind_transceiver_resp {
req.accept("TEST-SMSC".to_string(), Some(0x34))
}
async fn on_esme_bound(&self, esme: ESME, _s: &String) {
if let Some(tx) = self.tx.lock().await.take() {
let _ = tx.send(Arc::new(esme));
}
}
}
struct AckEveryDeliver;
#[async_trait]
impl SmppClientListener for AckEveryDeliver {
async fn on_smsc_bound(&self, _smsc: SMSC, _s: &String) {}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn deliver_sm_resp_is_never_dropped_under_load() {
let port = free_port();
let (tx, rx) = oneshot::channel();
let mut server = SmppServer::new_with_default_timers(
IpAddr::from([127, 0, 0, 1]),
port,
Arc::new(CaptureEsme {
tx: Mutex::new(Some(tx)),
}),
SESSION_INIT_TIMER_MS,
ENQUIRE_LINK_TIMER_MS,
INACTIVITY_TIMER_MS,
RESPONSE_TIMER_MS,
BUFFER_SIZE,
);
server.start().await;
tokio::time::sleep(Duration::from_millis(100)).await;
let mut client = SmppClient::new_with_default_timers(
"127.0.0.1".to_string(),
port,
false,
BIND_TYPE::TRX,
"load".to_string(),
"load".to_string(),
String::new(),
1,
1,
String::new(),
Arc::new(AckEveryDeliver),
SESSION_INIT_TIMER_MS,
ENQUIRE_LINK_TIMER_MS,
INACTIVITY_TIMER_MS,
RESPONSE_TIMER_MS,
BUFFER_SIZE,
WINDOW,
);
client.start().await;
let esme = tokio::time::timeout(Duration::from_secs(5), rx)
.await
.expect("esme bind timed out")
.expect("esme channel dropped");
assert!(esme.can_receive, "a TRX bind must be able to receive");
let sem = Arc::new(Semaphore::new(WINDOW));
let lost = Arc::new(AtomicU64::new(0));
let mut handles = Vec::with_capacity(REQUESTS);
for _ in 0..REQUESTS {
let permit = sem.clone().acquire_owned().await.expect("semaphore closed");
let esme = esme.clone();
let lost = lost.clone();
handles.push(tokio::spawn(async move {
let sent = esme
.deliver_sm()
.source_addr("15550199".to_string())
.destination_addr("15550100".to_string())
.short_message(b"correlation".to_vec())
.send()
.await;
if sent.is_err() {
lost.fetch_add(1, Ordering::Relaxed);
}
drop(permit);
}));
}
for h in handles {
h.await.expect("deliver task panicked");
}
client.stop().await;
assert_eq!(
lost.load(Ordering::Relaxed),
0,
"{REQUESTS} deliver_sm should all have been correlated with their \
deliver_sm_resp; a non-zero count means the read loop dropped a \
response because its pending entry was not registered yet"
);
}