Skip to main content

netget/server/smpp/
wire.rs

1//! SMPP 3.4 PDUs (the SMS Peer-to-Peer protocol between an ESME and an SMSC): the 16-byte
2//! header, C-Octet strings, bind, submit_sm/deliver_sm with their TLVs, enquire_link, unbind
3//! and generic_nack. Shared by the server (SMSC) and the client (ESME); every length is
4//! checked before anything is allocated for it.
5use anyhow::{bail, ensure, Context, Result};
6use std::time::Duration;
7use tokio::io::{AsyncRead, AsyncReadExt};
8
9/// Largest PDU accepted: room for a 64 KiB message_payload and the fixed fields.
10pub const MAX_PDU: usize = 65536 + 512;
11pub const HEADER_LEN: usize = 16;
12/// short_message holds at most 254 octets; longer text rides in message_payload.
13pub const MAX_SHORT_MESSAGE: usize = 254;
14pub const MAX_PAYLOAD: usize = 65536;
15/// Deadline for the rest of a PDU once its first byte has arrived, and for each write.
16pub const IO_TIMEOUT: Duration = Duration::from_secs(30);
17
18pub const GENERIC_NACK: u32 = 0x8000_0000;
19pub const BIND_RECEIVER: u32 = 0x0000_0001;
20pub const BIND_TRANSMITTER: u32 = 0x0000_0002;
21pub const SUBMIT_SM: u32 = 0x0000_0004;
22pub const DELIVER_SM: u32 = 0x0000_0005;
23pub const UNBIND: u32 = 0x0000_0006;
24pub const BIND_TRANSCEIVER: u32 = 0x0000_0009;
25pub const ENQUIRE_LINK: u32 = 0x0000_0015;
26pub const RESP: u32 = 0x8000_0000;
27
28pub const ESME_ROK: u32 = 0x00;
29pub const ESME_RINVMSGLEN: u32 = 0x01;
30pub const ESME_RINVCMDLEN: u32 = 0x02;
31pub const ESME_RINVCMDID: u32 = 0x03;
32pub const ESME_RINVBNDSTS: u32 = 0x04;
33pub const ESME_RALYBND: u32 = 0x05;
34pub const ESME_RSYSERR: u32 = 0x08;
35pub const ESME_RINVSRCADR: u32 = 0x0A;
36pub const ESME_RINVDSTADR: u32 = 0x0B;
37pub const ESME_RBINDFAIL: u32 = 0x0D;
38pub const ESME_RINVPASWD: u32 = 0x0E;
39pub const ESME_RINVSYSID: u32 = 0x0F;
40pub const ESME_RSUBMITFAIL: u32 = 0x45;
41pub const ESME_RTHROTTLED: u32 = 0x58;
42pub const ESME_RX_T_APPN: u32 = 0x64;
43pub const ESME_RX_P_APPN: u32 = 0x65;
44
45pub const TLV_MESSAGE_PAYLOAD: u16 = 0x0424;
46pub const TLV_RECEIPTED_MESSAGE_ID: u16 = 0x001E;
47pub const TLV_MESSAGE_STATE: u16 = 0x0427;
48
49/// Status names the handler may use, and their codes.
50pub const STATUS_NAMES: &[(&str, u32)] = &[
51    ("ESME_RINVMSGLEN", ESME_RINVMSGLEN),
52    ("ESME_RSYSERR", ESME_RSYSERR),
53    ("ESME_RINVSRCADR", ESME_RINVSRCADR),
54    ("ESME_RINVDSTADR", ESME_RINVDSTADR),
55    ("ESME_RBINDFAIL", ESME_RBINDFAIL),
56    ("ESME_RINVPASWD", ESME_RINVPASWD),
57    ("ESME_RINVSYSID", ESME_RINVSYSID),
58    ("ESME_RSUBMITFAIL", ESME_RSUBMITFAIL),
59    ("ESME_RTHROTTLED", ESME_RTHROTTLED),
60    ("ESME_RX_T_APPN", ESME_RX_T_APPN),
61    ("ESME_RX_P_APPN", ESME_RX_P_APPN),
62];
63
64pub fn status_code(name: &str) -> Option<u32> {
65    STATUS_NAMES
66        .iter()
67        .find(|(n, _)| *n == name)
68        .map(|(_, c)| *c)
69}
70
71pub fn status_name(code: u32) -> String {
72    match code {
73        ESME_ROK => "ESME_ROK".into(),
74        ESME_RINVCMDLEN => "ESME_RINVCMDLEN".into(),
75        ESME_RINVCMDID => "ESME_RINVCMDID".into(),
76        ESME_RINVBNDSTS => "ESME_RINVBNDSTS".into(),
77        ESME_RALYBND => "ESME_RALYBND".into(),
78        other => STATUS_NAMES
79            .iter()
80            .find(|(_, c)| *c == other)
81            .map(|(n, _)| n.to_string())
82            .unwrap_or_else(|| format!("0x{other:08X}")),
83    }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Pdu {
88    pub command_id: u32,
89    pub status: u32,
90    pub sequence: u32,
91    pub body: Vec<u8>,
92}
93
94impl Pdu {
95    pub fn new(command_id: u32, status: u32, sequence: u32, body: Vec<u8>) -> Self {
96        Self {
97            command_id,
98            status,
99            sequence,
100            body,
101        }
102    }
103    pub fn encode(&self) -> Vec<u8> {
104        let mut out = Vec::with_capacity(HEADER_LEN + self.body.len());
105        out.extend_from_slice(&((HEADER_LEN + self.body.len()) as u32).to_be_bytes());
106        out.extend_from_slice(&self.command_id.to_be_bytes());
107        out.extend_from_slice(&self.status.to_be_bytes());
108        out.extend_from_slice(&self.sequence.to_be_bytes());
109        out.extend_from_slice(&self.body);
110        out
111    }
112}
113
114/// Read one PDU. `None` on a clean EOF between PDUs; the first byte may wait `idle`.
115pub async fn read_pdu<R: AsyncRead + Unpin>(r: &mut R, idle: Duration) -> Result<Option<Pdu>> {
116    let mut len = [0u8; 4];
117    match tokio::time::timeout(idle, r.read(&mut len[..1])).await {
118        Ok(Ok(0)) => return Ok(None),
119        Ok(Ok(_)) => {}
120        Ok(Err(e)) => return Err(e.into()),
121        Err(_) => bail!("SMPP peer idle for {}s", idle.as_secs()),
122    }
123    tokio::time::timeout(IO_TIMEOUT, async {
124        r.read_exact(&mut len[1..]).await?;
125        let len = u32::from_be_bytes(len) as usize;
126        ensure!(
127            (HEADER_LEN..=MAX_PDU).contains(&len),
128            "SMPP command_length {len} outside {HEADER_LEN}..={MAX_PDU}"
129        );
130        let mut rest = vec![0u8; len - 4];
131        r.read_exact(&mut rest).await?;
132        Ok(Some(Pdu {
133            command_id: u32::from_be_bytes(rest[0..4].try_into()?),
134            status: u32::from_be_bytes(rest[4..8].try_into()?),
135            sequence: u32::from_be_bytes(rest[8..12].try_into()?),
136            body: rest[12..].to_vec(),
137        }))
138    })
139    .await
140    .context("SMPP read deadline")?
141}
142
143pub struct Reader<'a> {
144    buf: &'a [u8],
145}
146
147impl<'a> Reader<'a> {
148    pub fn new(buf: &'a [u8]) -> Self {
149        Self { buf }
150    }
151    pub fn rest(&self) -> &'a [u8] {
152        self.buf
153    }
154    pub fn u8(&mut self) -> Result<u8> {
155        ensure!(!self.buf.is_empty(), "SMPP PDU ends inside a field");
156        let b = self.buf[0];
157        self.buf = &self.buf[1..];
158        Ok(b)
159    }
160    pub fn bytes(&mut self, n: usize) -> Result<&'a [u8]> {
161        ensure!(self.buf.len() >= n, "SMPP PDU ends inside a field");
162        let (head, tail) = self.buf.split_at(n);
163        self.buf = tail;
164        Ok(head)
165    }
166    /// A C-Octet string of at most `max` octets including its NUL.
167    pub fn cstring(&mut self, max: usize) -> Result<String> {
168        let end = self
169            .buf
170            .iter()
171            .take(max)
172            .position(|b| *b == 0)
173            .context("SMPP C-Octet string unterminated or too long")?;
174        let s = String::from_utf8_lossy(&self.buf[..end]).to_string();
175        self.buf = &self.buf[end + 1..];
176        Ok(s)
177    }
178    /// TLVs to the end of the body: (tag, value).
179    pub fn tlvs(&mut self) -> Result<Vec<(u16, Vec<u8>)>> {
180        let mut out = Vec::new();
181        while !self.buf.is_empty() {
182            let tag = u16::from_be_bytes(self.bytes(2)?.try_into()?);
183            let len = u16::from_be_bytes(self.bytes(2)?.try_into()?) as usize;
184            out.push((tag, self.bytes(len)?.to_vec()));
185        }
186        Ok(out)
187    }
188}
189
190pub fn put_cstring(out: &mut Vec<u8>, s: &str, max: usize) {
191    let bytes = s.as_bytes();
192    out.extend_from_slice(&bytes[..bytes.len().min(max - 1)]);
193    out.push(0);
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct Bind {
198    pub system_id: String,
199    pub password: String,
200    pub system_type: String,
201    pub interface_version: u8,
202    pub address_range: String,
203}
204
205pub fn parse_bind(body: &[u8]) -> Result<Bind> {
206    let mut r = Reader::new(body);
207    let system_id = r.cstring(16)?;
208    let password = r.cstring(9)?;
209    let system_type = r.cstring(13)?;
210    let interface_version = r.u8()?;
211    r.u8()?;
212    r.u8()?;
213    let address_range = r.cstring(41)?;
214    Ok(Bind {
215        system_id,
216        password,
217        system_type,
218        interface_version,
219        address_range,
220    })
221}
222
223pub fn encode_bind(b: &Bind) -> Vec<u8> {
224    let mut out = Vec::new();
225    put_cstring(&mut out, &b.system_id, 16);
226    put_cstring(&mut out, &b.password, 9);
227    put_cstring(&mut out, &b.system_type, 13);
228    out.push(b.interface_version);
229    out.push(0);
230    out.push(0);
231    put_cstring(&mut out, &b.address_range, 41);
232    out
233}
234
235/// submit_sm and deliver_sm share one layout.
236#[derive(Debug, Clone, PartialEq, Eq, Default)]
237pub struct Message {
238    pub service_type: String,
239    pub source_ton: u8,
240    pub source_npi: u8,
241    pub source_addr: String,
242    pub dest_ton: u8,
243    pub dest_npi: u8,
244    pub destination_addr: String,
245    pub esm_class: u8,
246    pub registered_delivery: u8,
247    pub data_coding: u8,
248    /// short_message, or the message_payload TLV when short_message is empty.
249    pub payload: Vec<u8>,
250    pub tlvs: Vec<(u16, Vec<u8>)>,
251}
252
253pub fn parse_message(body: &[u8]) -> Result<Message> {
254    let mut r = Reader::new(body);
255    let service_type = r.cstring(6)?;
256    let source_ton = r.u8()?;
257    let source_npi = r.u8()?;
258    let source_addr = r.cstring(21)?;
259    let dest_ton = r.u8()?;
260    let dest_npi = r.u8()?;
261    let destination_addr = r.cstring(21)?;
262    let esm_class = r.u8()?;
263    r.u8()?; // protocol_id
264    r.u8()?; // priority_flag
265    r.cstring(17)?; // schedule_delivery_time
266    r.cstring(17)?; // validity_period
267    let registered_delivery = r.u8()?;
268    r.u8()?; // replace_if_present_flag
269    let data_coding = r.u8()?;
270    r.u8()?; // sm_default_msg_id
271    let sm_length = r.u8()? as usize;
272    ensure!(
273        sm_length <= MAX_SHORT_MESSAGE,
274        "sm_length {sm_length} exceeds 254"
275    );
276    let mut payload = r.bytes(sm_length)?.to_vec();
277    let tlvs = r.tlvs()?;
278    if let Some((_, p)) = tlvs.iter().find(|(t, _)| *t == TLV_MESSAGE_PAYLOAD) {
279        ensure!(
280            payload.is_empty(),
281            "both short_message and message_payload are set"
282        );
283        payload = p.clone();
284    }
285    Ok(Message {
286        service_type,
287        source_ton,
288        source_npi,
289        source_addr,
290        dest_ton,
291        dest_npi,
292        destination_addr,
293        esm_class,
294        registered_delivery,
295        data_coding,
296        payload,
297        tlvs,
298    })
299}
300
301pub fn encode_message(m: &Message) -> Vec<u8> {
302    let mut out = Vec::new();
303    put_cstring(&mut out, &m.service_type, 6);
304    out.push(m.source_ton);
305    out.push(m.source_npi);
306    put_cstring(&mut out, &m.source_addr, 21);
307    out.push(m.dest_ton);
308    out.push(m.dest_npi);
309    put_cstring(&mut out, &m.destination_addr, 21);
310    out.push(m.esm_class);
311    out.push(0);
312    out.push(0);
313    out.push(0);
314    out.push(0);
315    out.push(m.registered_delivery);
316    out.push(0);
317    out.push(m.data_coding);
318    out.push(0);
319    let long = m.payload.len() > MAX_SHORT_MESSAGE;
320    if long {
321        out.push(0);
322    } else {
323        out.push(m.payload.len() as u8);
324        out.extend_from_slice(&m.payload);
325    }
326    let mut tlvs = m.tlvs.clone();
327    if long {
328        tlvs.push((TLV_MESSAGE_PAYLOAD, m.payload.clone()));
329    }
330    for (tag, value) in tlvs {
331        out.extend_from_slice(&tag.to_be_bytes());
332        out.extend_from_slice(&(value.len() as u16).to_be_bytes());
333        out.extend_from_slice(&value);
334    }
335    out
336}
337
338/// Decode message text by data_coding: UCS-2 (8) as UTF-16BE, Latin-1 (3) as Latin-1, and the
339/// SMSC default (0) and IA5 (1) as their ASCII-compatible octets. `None` when the octets do
340/// not decode (binary data_coding 2 or 4, or an odd UCS-2 length).
341pub fn decode_text(data_coding: u8, payload: &[u8]) -> Option<String> {
342    match data_coding {
343        8 => {
344            if !payload.len().is_multiple_of(2) {
345                return None;
346            }
347            let units: Vec<u16> = payload
348                .chunks(2)
349                .map(|c| u16::from_be_bytes([c[0], c[1]]))
350                .collect();
351            String::from_utf16(&units).ok()
352        }
353        3 => Some(payload.iter().map(|b| *b as char).collect()),
354        0 | 1 => std::str::from_utf8(payload).ok().map(str::to_string),
355        _ => None,
356    }
357}
358
359/// Encode text: ASCII as the SMSC default alphabet (0), anything else as UCS-2 (8).
360pub fn encode_text(text: &str) -> (u8, Vec<u8>) {
361    if text.is_ascii() {
362        (0, text.as_bytes().to_vec())
363    } else {
364        (8, text.encode_utf16().flat_map(u16::to_be_bytes).collect())
365    }
366}
367
368/// The SMPP 3.4 Appendix B delivery receipt text.
369pub fn receipt_text(id: &str, stat: &str, submitted: &str, done: &str, text: &str) -> String {
370    let err = if stat == "DELIVRD" { "000" } else { "001" };
371    let dlvrd = if stat == "DELIVRD" { "001" } else { "000" };
372    let excerpt: String = text.chars().take(20).collect();
373    format!("id:{id} sub:001 dlvrd:{dlvrd} submit date:{submitted} done date:{done} stat:{stat} err:{err} text:{excerpt}")
374}
375
376/// message_state for a receipt's stat, as the message_state TLV carries it.
377pub fn message_state(stat: &str) -> u8 {
378    match stat {
379        "DELIVRD" => 2,
380        "EXPIRED" => 3,
381        "DELETED" => 4,
382        "UNDELIV" => 5,
383        "ACCEPTD" => 6,
384        "REJECTD" => 8,
385        _ => 7,
386    }
387}
388
389/// Fields of a delivery receipt, when `text` is one.
390pub fn parse_receipt(text: &str) -> Option<serde_json::Map<String, serde_json::Value>> {
391    if !text.starts_with("id:") {
392        return None;
393    }
394    let mut out = serde_json::Map::new();
395    let (head, body) = match text.split_once(" text:") {
396        Some((h, b)) => (h, Some(b)),
397        None => (text, None),
398    };
399    let mut key: Option<String> = None;
400    for token in head.split(' ') {
401        if let Some((k, v)) = token.split_once(':') {
402            let k = match key.take() {
403                Some(prefix) => format!("{prefix}_{k}"),
404                None => k.to_string(),
405            };
406            out.insert(k, serde_json::Value::String(v.to_string()));
407        } else {
408            key = Some(token.to_string());
409        }
410    }
411    if let Some(b) = body {
412        out.insert("text".into(), serde_json::Value::String(b.to_string()));
413    }
414    Some(out)
415}