serial_codec/sms/mod.rs
1//! SMS codecs: 3GPP TS 23.038 alphabets and TS 23.040 TPDUs as used by
2//! TS 27.005 PDU mode (`AT+CMGF=0`).
3//!
4//! - [`gsm7`]: default alphabet, extension table, national language tables, septet packing.
5//! - [`ucs2`]: UTF-16BE user data, surrogate pairs included.
6//! - [`pdu`]: decode SMS-DELIVER / SMS-SUBMIT / SMS-STATUS-REPORT, encode SMS-SUBMIT
7//! (with automatic concatenation).
8//! - [`concat`]: multipart reassembly keyed by (type, address, reference, total).
9//!
10//! All decoders are byte based and return errors for malformed input; none panic.
11
12pub mod concat;
13pub mod gsm7;
14pub mod pdu;
15pub mod ucs2;
16
17/// Hex string → bytes. Accepts either case and surrounding whitespace; `None` for odd
18/// length or a non-hex character.
19pub fn hex_decode(s: &str) -> Option<Vec<u8>> {
20 let s = s.trim().as_bytes();
21 if s.len() % 2 != 0 {
22 return None;
23 }
24 s.chunks_exact(2)
25 .map(|p| Some((hex_val(p[0])? << 4) | hex_val(p[1])?))
26 .collect()
27}
28
29/// Bytes → uppercase hex.
30pub fn hex_encode(bytes: &[u8]) -> String {
31 const D: &[u8; 16] = b"0123456789ABCDEF";
32 let mut s = String::with_capacity(bytes.len() * 2);
33 for b in bytes {
34 s.push(char::from(D[usize::from(b >> 4)]));
35 s.push(char::from(D[usize::from(b & 15)]));
36 }
37 s
38}
39
40fn hex_val(c: u8) -> Option<u8> {
41 match c {
42 b'0'..=b'9' => Some(c - b'0'),
43 b'a'..=b'f' => Some(c - b'a' + 10),
44 b'A'..=b'F' => Some(c - b'A' + 10),
45 _ => None,
46 }
47}