rusmpp_extra/encoding/gsm7bit/
unpacked.rs

1use rusmpp_core::values::DataCoding;
2
3use crate::encoding::gsm7bit::alphabet::Gsm7BitAlphabet;
4
5/// GSM 7-bit unpacked codec.
6#[non_exhaustive]
7#[derive(Debug)]
8pub struct Gsm7BitUnpacked {
9    /// The GSM 7-bit alphabet to use for encoding.
10    alphabet: Gsm7BitAlphabet,
11    /// Whether to allow splitting extended characters across message parts.
12    allow_split_extended_character: bool,
13}
14
15impl Default for Gsm7BitUnpacked {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl Gsm7BitUnpacked {
22    /// Creates a new [`Gsm7BitUnpacked`] with [`Gsm7BitAlphabet::Default`].
23    ///
24    /// # Defaults
25    ///
26    /// - `alphabet`: [`Gsm7BitAlphabet::Default`]
27    /// - `allow_split_extended_character`: `false`
28    pub const fn new() -> Self {
29        Self {
30            alphabet: Gsm7BitAlphabet::default(),
31            allow_split_extended_character: false,
32        }
33    }
34
35    /// Sets the alphabet for the codec.
36    pub const fn with_alphabet(mut self, alphabet: Gsm7BitAlphabet) -> Self {
37        self.alphabet = alphabet;
38        self
39    }
40
41    /// Returns whether splitting extended characters is allowed.
42    pub const fn allow_split_extended_character(&self) -> bool {
43        self.allow_split_extended_character
44    }
45
46    /// Sets whether to allow splitting extended characters across message parts.
47    pub const fn with_allow_split_extended_character(mut self, allow: bool) -> Self {
48        self.allow_split_extended_character = allow;
49        self
50    }
51
52    /// Returns the associated [`Gsm7BitAlphabet`].
53    pub const fn alphabet(&self) -> &Gsm7BitAlphabet {
54        &self.alphabet
55    }
56
57    /// Returns the associated [`DataCoding`].
58    pub const fn data_coding(&self) -> DataCoding {
59        DataCoding::McSpecific
60    }
61}
62
63#[cfg(any(test, feature = "alloc"))]
64#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
65mod impl_owned {
66    use alloc::vec::Vec;
67
68    use crate::{
69        concatenation::{
70            MAX_PARTS,
71            owned::{Concatenation, Concatenator},
72        },
73        encoding::{
74            gsm7bit::{
75                alphabet::ESCAPE_CHARACTER,
76                errors::{Gsm7BitConcatenateError, Gsm7BitEncodeError},
77            },
78            owned::Encoder,
79        },
80    };
81
82    use super::*;
83
84    impl Gsm7BitUnpacked {
85        /// Encodes the given message into a vector of bytes.
86        pub fn encode_to_vec(&self, input: &str) -> Result<Vec<u8>, Gsm7BitEncodeError> {
87            self.alphabet
88                .encode_to_vec(input)
89                .map_err(Gsm7BitEncodeError::UnencodableCharacter)
90        }
91    }
92
93    impl Encoder for Gsm7BitUnpacked {
94        type Error = Gsm7BitEncodeError;
95
96        fn encode(&self, message: &str) -> Result<(Vec<u8>, DataCoding), Self::Error> {
97            self.encode_to_vec(message)
98                .map(|vec| (vec, self.data_coding()))
99        }
100    }
101
102    impl Concatenator for Gsm7BitUnpacked {
103        type Error = Gsm7BitConcatenateError;
104
105        fn concatenate(
106            &self,
107            message: &str,
108            max_message_size: usize,
109            part_header_size: usize,
110        ) -> Result<(Concatenation, DataCoding), Self::Error> {
111            let encoded = self.encode_to_vec(message)?;
112
113            let total = encoded.len();
114
115            if total <= max_message_size {
116                return Ok((Concatenation::single(encoded), self.data_coding()));
117            }
118
119            let part_payload_size = max_message_size.saturating_sub(part_header_size);
120
121            if part_payload_size == 0 {
122                return Err(Gsm7BitConcatenateError::PartCapacityExceeded);
123            }
124
125            let mut parts: Vec<Vec<u8>> = Vec::new();
126            let mut i = 0;
127
128            while i < total {
129                let mut end = (i + part_payload_size).min(total);
130
131                // avoid splitting extended characters unless allow_split_extended_character == true
132                if !self.allow_split_extended_character {
133                    // If not last part AND the last byte of this part is 0x1B,
134                    // we must shrink the part to avoid splitting ESC + next byte.
135                    if end < total && encoded[end - 1] == ESCAPE_CHARACTER {
136                        end -= 1;
137
138                        // If shrinking removed the entire part -> impossible
139                        if end == i {
140                            return Err(Gsm7BitConcatenateError::InvalidBoundary);
141                        }
142                    }
143                }
144
145                parts.push(encoded[i..end].to_vec());
146
147                i = end;
148            }
149
150            if parts.len() > MAX_PARTS {
151                return Err(Gsm7BitConcatenateError::parts_count_exceeded(parts.len()));
152            }
153
154            Ok((Concatenation::concatenated(parts), self.data_coding()))
155        }
156    }
157}