1use anyhow::{bail, ensure, Context, Result};
6use std::time::Duration;
7use tokio::io::{AsyncRead, AsyncReadExt};
8
9pub const MAX_PDU: usize = 65536 + 512;
11pub const HEADER_LEN: usize = 16;
12pub const MAX_SHORT_MESSAGE: usize = 254;
14pub const MAX_PAYLOAD: usize = 65536;
15pub 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
49pub 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
114pub 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 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 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#[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 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()?; r.u8()?; r.cstring(17)?; r.cstring(17)?; let registered_delivery = r.u8()?;
268 r.u8()?; let data_coding = r.u8()?;
270 r.u8()?; 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
338pub 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
359pub 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
368pub 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
376pub 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
389pub 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}