Skip to main content

smpp_codec/
splitter.rs

1//! # Message Content Splitter
2//!
3//! This module contains logic for splitting large message content into pre-defined length
4
5use crate::encoding;
6// use rand::Rng; // Deprecated/Unused
7
8/// Supported text encodings for splitting.
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum EncodingType {
11    /// GSM 7-bit default alphabet
12    Gsm7Bit,
13    /// Latin1 (ISO-8859-1)
14    Latin1,
15    /// UCS2 (UTF-16BE like)
16    Ucs2,
17}
18
19/// Mode of message splitting.
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum SplitMode {
22    /// Use User Data Header (UDH) for concatenation
23    Udh,
24    /// Use Segmentation and Reassembly (SAR) TLVs
25    Sar,
26    /// Do not split, treat as single large payload
27    Payload,
28}
29
30/// Helper struct for splitting messages.
31pub struct MessageSplitter;
32
33impl MessageSplitter {
34    /// Split a long message into multiple chunks.
35    ///
36    /// Returns a tuple of (Chunks, DataCoding).
37    /// * For `SplitMode::Udh`, chunks include the User Data Header.
38    /// * For `SplitMode::Sar`, chunks are raw payload; caller must add SAR TLVs.
39    /// * For `SplitMode::Payload`, returns a single chunk (no splitting).
40    pub fn split(
41        text: String,
42        encoding: EncodingType,
43        mode: SplitMode,
44    ) -> Result<(Vec<Vec<u8>>, u8), String> {
45        // 1. Encode Text
46        let (encoded_bytes, data_coding) = match encoding {
47            EncodingType::Gsm7Bit => (encoding::gsm_7bit_encode(&text)?, 0x00),
48            EncodingType::Latin1 => (encoding::encode_8bit(&text), 0x03),
49            EncodingType::Ucs2 => (encoding::encode_16bit(&text), 0x08),
50        };
51
52        // 2. Determine Limits
53        let (single_max, multipart_max) = match mode {
54            SplitMode::Udh => match encoding {
55                EncodingType::Gsm7Bit => (160, 153),
56                _ => (140, 134),
57            },
58            SplitMode::Sar => (254, 254),
59            SplitMode::Payload => (65535, 65535),
60        };
61
62        // 3. Simple Case: Fits in one message?
63        if encoded_bytes.len() <= single_max || matches!(mode, SplitMode::Payload) {
64            return Ok((vec![encoded_bytes], data_coding));
65        }
66
67        // 4. Calculate Chunks (Payload Only First)
68        let mut temp_chunks = Vec::new();
69        let mut offset = 0;
70
71        while offset < encoded_bytes.len() {
72            let remaining = encoded_bytes.len() - offset;
73            let mut chunk_len = std::cmp::min(multipart_max, remaining);
74
75            // Handle GSM 7-bit Escape Splitting
76            if encoding == EncodingType::Gsm7Bit && chunk_len < remaining {
77                let last_byte_index = offset + chunk_len - 1;
78                if encoded_bytes[last_byte_index] == 0x1B {
79                    chunk_len -= 1; // Back off to avoid splitting escape sequence
80                }
81            }
82
83            temp_chunks.push(encoded_bytes[offset..offset + chunk_len].to_vec());
84            offset += chunk_len;
85        }
86
87        // 5. Finalize Chunks (Add UDH if needed)
88        let mut final_chunks = Vec::new();
89        let total_segments = temp_chunks.len() as u8;
90        let ref_num = rand::random::<u8>();
91
92        for (i, chunk_payload) in temp_chunks.iter().enumerate() {
93            let mut chunk = Vec::new();
94
95            if mode == SplitMode::Udh {
96                // UDH: Len(05) + ID(00) + Len(03) + Ref + Total + Seq
97                let seq_num = (i + 1) as u8;
98                let udh = vec![0x05, 0x00, 0x03, ref_num, total_segments, seq_num];
99                chunk.extend(udh);
100            }
101
102            chunk.extend_from_slice(chunk_payload);
103            final_chunks.push(chunk);
104        }
105
106        Ok((final_chunks, data_coding))
107    }
108}