Skip to main content

smpp_codec/
splitter.rs

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