1#![allow(private_interfaces)]
2
3use crate::codecs::{GSM, UCS2};
4use crate::elements::{Date, Number, Toa, TypeOfAddress};
5use crate::{PDUError, Result};
6
7use std::io::{Cursor, Read, Seek, SeekFrom};
8
9struct PDUReader {
11 cursor: Cursor<Vec<u8>>,
12}
13
14impl PDUReader {
15 fn new(data: &str) -> Result<Self> {
16 if !data.len().is_multiple_of(2) {
17 return Err(PDUError::OddLength);
18 }
19 let bytes = hex::decode(data)?;
20 Ok(PDUReader {
21 cursor: Cursor::new(bytes),
22 })
23 }
24
25 fn read_hex(&mut self, len: usize) -> Result<String> {
27 let mut buffer = vec![0; len];
28 if self.cursor.read_exact(&mut buffer).is_err() {
29 return Err(PDUError::EndOfPdu);
30 }
31 Ok(hex::encode_upper(buffer))
32 }
33
34 fn read_hex_available(&mut self, len: usize) -> Result<String> {
36 let mut buffer = vec![0; len];
37 let bytes_read = self
38 .cursor
39 .read(&mut buffer)
40 .map_err(|_| PDUError::EndOfPdu)?;
41 buffer.truncate(bytes_read);
42 Ok(hex::encode_upper(buffer))
43 }
44
45 fn read_octet(&mut self) -> Result<u8> {
47 let hex_str = self.read_hex(1)?;
48 Ok(u8::from_str_radix(&hex_str, 16).unwrap_or(0))
49 }
50
51 fn position(&self) -> u64 {
53 self.cursor.position()
54 }
55
56 fn seek(&mut self, pos: u64) -> Result<u64> {
58 self.cursor
59 .seek(SeekFrom::Start(pos))
60 .map_err(|_| PDUError::EndOfPdu)
61 }
62}
63
64#[derive(Debug, Default)]
66pub struct DecodeContext {
67 pub header: Option<PDUHeaderDecoded>,
68 pub dcs: Option<DcsDecoded>,
69}
70
71#[derive(Debug, PartialEq)]
73pub struct AddressDecoded {
74 pub length: u8,
75 pub toa: Toa,
76 pub number: String,
77}
78
79pub(crate) struct Address;
80
81impl Address {
82 pub(crate) fn decode(reader: &mut PDUReader) -> Result<AddressDecoded> {
84 let length = reader.read_octet()?;
85 let toa = TypeOfAddress::decode(&reader.read_hex(1)?)?;
86
87 let octets_to_read = (length as usize).div_ceil(2);
89 let encoded_number = reader.read_hex(octets_to_read)?;
90
91 let number = if toa.ton == "alphanumeric" {
92 GSM::decode(&encoded_number, false)?
93 } else {
94 Number::decode(&encoded_number)?
95 };
96
97 Ok(AddressDecoded {
98 length,
99 toa,
100 number,
101 })
102 }
103}
104
105#[derive(Debug, PartialEq)]
107pub struct SmscDecoded {
108 pub length: u8,
109 pub toa: Option<Toa>,
110 pub number: Option<String>,
111}
112
113pub(crate) struct SMSC;
114
115impl SMSC {
116 pub(crate) fn decode(reader: &mut PDUReader) -> Result<SmscDecoded> {
118 let length = reader.read_octet()?;
119 if length == 0 {
120 return Ok(SmscDecoded {
121 length: 0,
122 toa: None,
123 number: None,
124 });
125 }
126
127 let toa = TypeOfAddress::decode(&reader.read_hex(1)?)?;
128
129 let octets_to_read = length as usize - 1;
130 let encoded_number = reader.read_hex(octets_to_read)?;
131
132 let number = if toa.ton == "alphanumeric" {
133 GSM::decode(&encoded_number, false)?
134 } else {
135 Number::decode(&encoded_number)?
136 };
137
138 Ok(SmscDecoded {
139 length,
140 toa: Some(toa),
141 number: Some(number),
142 })
143 }
144}
145
146#[derive(Debug, PartialEq, Default, Clone)]
148pub struct PDUHeaderDecoded {
149 pub rp: bool,
150 pub udhi: bool,
151 pub sri: bool,
152 pub lp: bool,
153 pub mms: bool,
154 pub mti: String,
155}
156
157pub(crate) struct PDUHeader;
158
159impl PDUHeader {
160 const MTI: [(u8, &'static str); 3] = [
161 (0b00, "deliver"),
162 (0b01, "submit-report"),
163 (0b10, "status-report"),
164 ];
165
166 pub(crate) fn decode(reader: &mut PDUReader) -> Result<PDUHeaderDecoded> {
168 let octet = reader.read_octet()?;
169
170 let mti_bits = octet & 0x03;
171 let mti = Self::MTI
172 .iter()
173 .find(|(k, _)| *k == mti_bits)
174 .map(|(_, v)| v.to_string())
175 .ok_or(PDUError::InvalidMti)?;
176
177 Ok(PDUHeaderDecoded {
178 rp: octet & 0x80 != 0, udhi: octet & 0x40 != 0, sri: octet & 0x20 != 0, lp: octet & 0x08 != 0, mms: octet & 0x04 != 0, mti,
184 })
185 }
186}
187
188#[derive(Debug, PartialEq, Default, Clone)]
190pub struct OutgoingPDUHeaderDecoded {
191 pub rp: bool,
192 pub udhi: bool,
193 pub srr: bool,
194 pub vpf: u8,
195 pub rd: bool,
196 pub mti: String,
197}
198
199pub(crate) struct OutgoingPDUHeader;
200
201impl OutgoingPDUHeader {
202 const MTI: [(u8, &'static str); 3] = [(0b00, "deliver"), (0b01, "submit"), (0b10, "status")];
203
204 pub(crate) fn decode(reader: &mut PDUReader) -> Result<OutgoingPDUHeaderDecoded> {
206 let octet = reader.read_octet()?;
207
208 let mti_bits = octet & 0x03;
209 let mti = Self::MTI
210 .iter()
211 .find(|(k, _)| *k == mti_bits)
212 .map(|(_, v)| v.to_string())
213 .ok_or(PDUError::InvalidMti)?;
214
215 Ok(OutgoingPDUHeaderDecoded {
216 rp: octet & 0x80 != 0, udhi: octet & 0x40 != 0, srr: octet & 0x20 != 0, vpf: (octet & 0x18) >> 3, rd: octet & 0x04 != 0, mti,
222 })
223 }
224}
225
226#[derive(Debug, PartialEq, Default, Clone)]
228pub struct DcsDecoded {
229 pub encoding: String,
230}
231
232pub(crate) struct DCS;
233
234impl DCS {
235 pub(crate) fn decode(reader: &mut PDUReader) -> Result<DcsDecoded> {
236 let dcs = reader.read_octet()?;
237 let coding = (dcs & 0b1100) >> 2;
238 let encoding = match coding {
239 1 => "binary".to_string(),
240 2 => "ucs2".to_string(),
241 _ => "gsm".to_string(),
242 };
243 Ok(DcsDecoded { encoding })
244 }
245}
246
247#[derive(Debug, PartialEq)]
249pub struct InformationElementDecoded {
250 pub iei: u8,
251 pub length: u8,
252 pub data: serde_json::Value,
253}
254
255pub(crate) struct InformationElement;
256
257impl InformationElement {
258 fn concatenated_sms(data: &str, length_bits: u8) -> serde_json::Value {
259 if data.len() < 6 {
261 return serde_json::Value::String(data.to_string());
262 }
263
264 let bytes = hex::decode(data).unwrap_or_default();
265 if bytes.len() != 3 {
266 return serde_json::Value::String(data.to_string());
267 }
268
269 let reference = if length_bits == 16 {
270 (bytes[0] as u16) << 8 | (bytes[1] as u16)
271 } else {
272 bytes[0] as u16
273 };
274
275 let (parts_count, part_number) = if length_bits == 16 {
276 (bytes[2], bytes[3]) } else {
278 (bytes[1], bytes[2])
279 };
280
281 serde_json::json!({
282 "reference": reference,
283 "parts_count": parts_count,
284 "part_number": part_number,
285 })
286 }
287
288 pub(crate) fn decode(reader: &mut PDUReader) -> Result<InformationElementDecoded> {
289 let iei = reader.read_octet()?;
290 let length = reader.read_octet()?;
291 let data_hex = reader.read_hex(length as usize)?;
292
293 let processed_data = match iei {
294 0x00 => Self::concatenated_sms(&data_hex, 8),
295 0x08 => Self::concatenated_sms(&data_hex, 16),
296 _ => serde_json::Value::String(data_hex),
297 };
298
299 Ok(InformationElementDecoded {
300 iei,
301 length,
302 data: processed_data,
303 })
304 }
305}
306
307#[derive(Debug, PartialEq, Default)]
309pub struct UserDataHeaderDecoded {
310 pub length: u8,
311 pub elements: Vec<InformationElementDecoded>,
312}
313
314pub(crate) struct UserDataHeader;
315
316impl UserDataHeader {
317 pub(crate) fn decode(reader: &mut PDUReader) -> Result<UserDataHeaderDecoded> {
318 let length = reader.read_octet()?;
319 let final_position = reader.position() + length as u64;
320 let mut elements = Vec::new();
321 while reader.position() < final_position {
322 elements.push(InformationElement::decode(reader)?);
323 }
324 Ok(UserDataHeaderDecoded { length, elements })
325 }
326}
327
328#[derive(Debug, PartialEq, Default)]
330pub struct UserDataDecoded {
331 pub header: Option<UserDataHeaderDecoded>,
332 pub data: String,
333 pub warning: Option<String>,
334}
335
336pub(crate) struct UserData;
337
338impl UserData {
339 pub(crate) fn decode(reader: &mut PDUReader, ctx: &DecodeContext) -> Result<UserDataDecoded> {
340 let length_octets = reader.read_octet()?;
341 let pdu_start = reader.position();
342 let mut header: Option<UserDataHeaderDecoded> = None;
343 let mut header_length_octets: u8 = 0;
344
345 if ctx.header.as_ref().map(|h| h.udhi).unwrap_or(false) {
346 header = Some(UserDataHeader::decode(reader)?);
347 header_length_octets = header.as_ref().unwrap().length + 1;
348 }
349
350 let data_length_octets = length_octets.saturating_sub(header_length_octets);
351 let encoding = ctx
352 .dcs
353 .as_ref()
354 .map(|d| d.encoding.as_str())
355 .unwrap_or("gsm");
356 let mut warning: Option<String> = None;
357
358 let user_data = match encoding {
359 "binary" => {
360 let hex_data = reader.read_hex(data_length_octets as usize)?;
361 hex::decode(hex_data)
362 .map_err(|_| PDUError::InvalidHex(hex::FromHexError::InvalidStringLength))?
363 .iter()
364 .map(|b| format!("{:02X}", b))
365 .collect::<String>()
366 }
367 "gsm" => {
368 reader.seek(pdu_start)?;
369 let header_length_bits = header_length_octets as usize * 8;
370 let data_length_septets = length_octets as usize;
371
372 let data_length_bytes = (data_length_septets * 7).div_ceil(8);
374 let data_hex = reader.read_hex(data_length_bytes)?;
375
376 let decoded_msg = GSM::decode(&data_hex, false)?;
377
378 let header_length_septets = header_length_bits.div_ceil(7);
380
381 if decoded_msg.len() > header_length_septets {
382 decoded_msg[header_length_septets..].to_string()
383 } else {
384 decoded_msg.to_string() }
386 }
387 "ucs2" => {
388 let expected_hex_len = 2 * data_length_octets as usize;
389 let hex_data = reader.read_hex_available(data_length_octets as usize)?;
390
391 let is_truncated = hex_data.len() < expected_hex_len;
392
393 if is_truncated {
394 warning = Some(PDUError::UserDataTruncated.to_string());
395 let trunc_len = hex_data.len() - (hex_data.len() % 4);
396 UCS2::decode(&hex_data[..trunc_len])? + "…"
397 } else {
398 UCS2::decode(&hex_data)?
399 }
400 }
401 _ => return Err(PDUError::NonRecognizedEncoding(encoding.to_string())),
402 };
403
404 Ok(UserDataDecoded {
405 header,
406 data: user_data,
407 warning,
408 })
409 }
410}
411
412#[derive(Debug, PartialEq)]
414pub struct SmsDeliverDecoded {
415 pub smsc: SmscDecoded,
416 pub header: PDUHeaderDecoded,
417 pub sender: AddressDecoded,
418 pub pid: u8,
419 pub dcs: DcsDecoded,
420 pub scts: chrono::DateTime<chrono::Utc>,
421 pub user_data: UserDataDecoded,
422}
423
424pub struct SMSDeliver;
425
426impl SMSDeliver {
427 pub fn decode(data: &str) -> Result<SmsDeliverDecoded> {
429 let mut reader = PDUReader::new(data)?;
430 let smsc = SMSC::decode(&mut reader)?;
431 let header = PDUHeader::decode(&mut reader)?;
432 let sender = Address::decode(&mut reader)?;
433 let pid = reader.read_octet()?;
434 let dcs = DCS::decode(&mut reader)?;
435 let scts_hex = reader.read_hex(7)?;
436 let scts = Date::decode(&scts_hex)?;
437
438 let ctx = DecodeContext {
439 header: Some(header.clone()),
440 dcs: Some(dcs.clone()),
441 };
442 let user_data = UserData::decode(&mut reader, &ctx)?;
443
444 Ok(SmsDeliverDecoded {
445 smsc,
446 header,
447 sender,
448 pid,
449 dcs,
450 scts,
451 user_data,
452 })
453 }
454}
455
456#[derive(Debug, PartialEq)]
458pub struct SmsSubmitDecoded {
459 pub smsc: SmscDecoded,
460 pub header: OutgoingPDUHeaderDecoded,
461 pub message_ref: u8,
462 pub recipient: AddressDecoded,
463 pub pid: u8,
464 pub dcs: DcsDecoded,
465 pub vp: Option<u8>,
466 pub validity_minutes: Option<u16>,
467 pub validity_hours: Option<u16>,
468 pub validity_days: Option<u8>,
469 pub validity_weeks: Option<u8>,
470 pub vp_date: Option<chrono::DateTime<chrono::Utc>>,
471 pub user_data: UserDataDecoded,
472}
473
474pub struct SMSSubmit;
475
476impl SMSSubmit {
477 pub fn decode(data: &str) -> Result<SmsSubmitDecoded> {
479 let mut reader = PDUReader::new(data)?;
480 let smsc = SMSC::decode(&mut reader)?;
481 let header = OutgoingPDUHeader::decode(&mut reader)?;
482 let message_ref = reader.read_octet()?;
483 let recipient = Address::decode(&mut reader)?;
484 let pid = reader.read_octet()?;
485 let dcs = DCS::decode(&mut reader)?;
486
487 let mut decoded = SmsSubmitDecoded {
488 smsc,
489 header: header.clone(),
490 message_ref,
491 recipient,
492 pid,
493 dcs: dcs.clone(),
494 vp: None,
495 validity_minutes: None,
496 validity_hours: None,
497 validity_days: None,
498 validity_weeks: None,
499 vp_date: None,
500 user_data: UserDataDecoded::default(),
501 };
502
503 if header.vpf == 0 {
504 } else if header.vpf == 2 {
506 let vp = reader.read_octet()?;
508 decoded.vp = Some(vp);
509 if vp <= 143 {
510 decoded.validity_minutes = Some(vp as u16 * 5);
511 } else if vp <= 167 {
512 decoded.validity_hours = Some(12 + (vp - 143) as u16 / 2);
513 } else if vp <= 196 {
514 decoded.validity_days = Some(vp - 166);
515 } else {
516 decoded.validity_weeks = Some(vp - 192);
517 }
518 } else if header.vpf == 3 {
519 let vp_hex = reader.read_hex(7)?;
521 decoded.vp_date = Some(Date::decode(&vp_hex)?);
522 } else {
523 reader.read_hex(7)?;
525 }
526
527 let user_data_ctx = DecodeContext {
528 header: Some(PDUHeaderDecoded {
529 udhi: decoded.header.udhi,
530 ..Default::default()
531 }),
532 dcs: Some(decoded.dcs.clone()),
533 };
534 decoded.user_data = UserData::decode(&mut reader, &user_data_ctx)?;
535
536 Ok(decoded)
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn test_decode_truncated_ucs2() -> Result<()> {
546 let pdu = "0891683110304105F1240D91683167414052F700081270115183942344597D70E6597D70E651CF80A551CF80A55C";
547
548 let decoded_data = SMSDeliver::decode(pdu)?;
549
550 assert_eq!(decoded_data.user_data.data, "好烦好烦减肥减肥…");
554
555 assert!(decoded_data.user_data.warning.is_some());
557
558 Ok(())
559 }
560}