mqtt_protocol_core/mqtt/packet/
mqtt_binary.rs

1// MIT License
2//
3// Copyright (c) 2025 Takatoshi Kondo
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22use crate::mqtt::result_code::MqttError;
23use alloc::vec::Vec;
24use core::convert::TryFrom;
25use serde::{Serialize, Serializer};
26#[cfg(feature = "std")]
27use std::io::IoSlice;
28
29// SSO buffer size configuration - priority-based selection for maximum size
30#[cfg(feature = "sso-lv20")]
31const SSO_BUFFER_SIZE: usize = 48; // Highest priority: 48 bytes
32#[cfg(all(
33    not(feature = "sso-lv20"),
34    any(feature = "sso-lv10", feature = "sso-min-64bit")
35))]
36const SSO_BUFFER_SIZE: usize = 24; // Second priority: 24 bytes
37#[cfg(all(
38    not(any(feature = "sso-lv20", feature = "sso-lv10", feature = "sso-min-64bit")),
39    feature = "sso-min-32bit"
40))]
41const SSO_BUFFER_SIZE: usize = 12; // Third priority: 12 bytes
42#[cfg(not(any(
43    feature = "sso-min-32bit",
44    feature = "sso-min-64bit",
45    feature = "sso-lv10",
46    feature = "sso-lv20"
47)))]
48#[allow(dead_code)]
49const SSO_BUFFER_SIZE: usize = 0; // No SSO features enabled
50
51// Determine data threshold
52#[cfg(any(
53    feature = "sso-min-32bit",
54    feature = "sso-min-64bit",
55    feature = "sso-lv10",
56    feature = "sso-lv20"
57))]
58const SSO_DATA_THRESHOLD: usize = SSO_BUFFER_SIZE - 2;
59
60/// MQTT Binary Data representation with Small String Optimization (SSO)
61///
62/// This enum represents binary data as specified in the MQTT protocol specification.
63/// It uses SSO to store small binary data on the stack and larger data on the heap,
64/// following the MQTT wire format for binary data fields.
65///
66/// The binary data is stored with a 2-byte length prefix, which includes:
67/// - 2 bytes for the length prefix (big-endian u16)
68/// - The actual binary data bytes
69///
70/// This approach provides several benefits:
71/// - Zero-copy serialization to network buffers
72/// - Efficient memory usage (stack for small data, heap for large)
73/// - Guaranteed MQTT protocol compliance
74/// - Fast size calculations
75///
76/// # Size Limits
77///
78/// The maximum size of binary data is 65,535 bytes (2^16 - 1), as specified
79/// by the MQTT protocol. The SSO threshold is determined by feature flags.
80#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
81#[allow(clippy::large_enum_variant)]
82pub enum MqttBinary {
83    #[cfg(any(
84        feature = "sso-min-32bit",
85        feature = "sso-min-64bit",
86        feature = "sso-lv10",
87        feature = "sso-lv20"
88    ))]
89    Small([u8; SSO_BUFFER_SIZE]), // buffer with length prefix
90    Large(Vec<u8>),
91}
92
93impl MqttBinary {
94    /// Create a new MqttBinary from binary data
95    pub fn new(data: impl AsRef<[u8]>) -> Result<Self, MqttError> {
96        let data_ref = data.as_ref();
97        if data_ref.len() > 65535 {
98            return Err(MqttError::MalformedPacket);
99        }
100
101        // Try to fit in Small variant if SSO is enabled
102        #[cfg(any(
103            feature = "sso-min-32bit",
104            feature = "sso-min-64bit",
105            feature = "sso-lv10",
106            feature = "sso-lv20"
107        ))]
108        if data_ref.len() <= SSO_DATA_THRESHOLD {
109            let mut buffer = [0u8; SSO_BUFFER_SIZE];
110            let len = data_ref.len() as u16;
111            buffer[0] = (len >> 8) as u8;
112            buffer[1] = len as u8;
113            buffer[2..2 + data_ref.len()].copy_from_slice(data_ref);
114            return Ok(MqttBinary::Small(buffer));
115        }
116
117        // Fallback to Large variant
118        let len = data_ref.len() as u16;
119        let mut encoded = Vec::with_capacity(2 + data_ref.len());
120        encoded.push((len >> 8) as u8);
121        encoded.push(len as u8);
122        encoded.extend_from_slice(data_ref);
123        Ok(MqttBinary::Large(encoded))
124    }
125
126    /// Get the complete encoded byte sequence including length prefix
127    pub fn as_bytes(&self) -> &[u8] {
128        match self {
129            #[cfg(any(
130                feature = "sso-min-32bit",
131                feature = "sso-min-64bit",
132                feature = "sso-lv10",
133                feature = "sso-lv20"
134            ))]
135            MqttBinary::Small(buffer) => {
136                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
137                &buffer[..2 + len]
138            }
139            MqttBinary::Large(encoded) => encoded,
140        }
141    }
142
143    /// Get only the binary data without the length prefix
144    pub fn as_slice(&self) -> &[u8] {
145        match self {
146            #[cfg(any(
147                feature = "sso-min-32bit",
148                feature = "sso-min-64bit",
149                feature = "sso-lv10",
150                feature = "sso-lv20"
151            ))]
152            MqttBinary::Small(buffer) => {
153                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
154                &buffer[2..2 + len]
155            }
156            MqttBinary::Large(encoded) => &encoded[2..],
157        }
158    }
159
160    /// Get the length of the binary data in bytes
161    pub fn len(&self) -> usize {
162        match self {
163            #[cfg(any(
164                feature = "sso-min-32bit",
165                feature = "sso-min-64bit",
166                feature = "sso-lv10",
167                feature = "sso-lv20"
168            ))]
169            MqttBinary::Small(buffer) => ((buffer[0] as usize) << 8) | (buffer[1] as usize),
170            MqttBinary::Large(encoded) => encoded.len() - 2,
171        }
172    }
173
174    /// Check if the binary data is empty
175    pub fn is_empty(&self) -> bool {
176        self.len() == 0
177    }
178
179    /// Get the total encoded size including the length field
180    pub fn size(&self) -> usize {
181        match self {
182            #[cfg(any(
183                feature = "sso-min-32bit",
184                feature = "sso-min-64bit",
185                feature = "sso-lv10",
186                feature = "sso-lv20"
187            ))]
188            MqttBinary::Small(buffer) => {
189                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
190                2 + len
191            }
192            MqttBinary::Large(encoded) => encoded.len(),
193        }
194    }
195
196    /// Create IoSlice buffers for efficient network I/O
197    #[cfg(feature = "std")]
198    pub fn to_buffers(&self) -> Vec<IoSlice<'_>> {
199        match self {
200            MqttBinary::Large(encoded) => vec![IoSlice::new(encoded)],
201            #[cfg(any(
202                feature = "sso-min-32bit",
203                feature = "sso-min-64bit",
204                feature = "sso-lv10",
205                feature = "sso-lv20"
206            ))]
207            MqttBinary::Small(buffer) => {
208                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
209                vec![IoSlice::new(&buffer[..2 + len])]
210            }
211        }
212    }
213
214    /// Create a continuous buffer containing the complete packet data
215    pub fn to_continuous_buffer(&self) -> Vec<u8> {
216        self.as_bytes().to_vec()
217    }
218
219    /// Parse binary data from a byte sequence
220    pub fn decode(data: &[u8]) -> Result<(Self, usize), MqttError> {
221        if data.len() < 2 {
222            return Err(MqttError::MalformedPacket);
223        }
224
225        let data_len = ((data[0] as usize) << 8) | (data[1] as usize);
226        if data.len() < 2 + data_len {
227            return Err(MqttError::MalformedPacket);
228        }
229
230        // Try to fit in Small variant if SSO is enabled
231        #[cfg(any(
232            feature = "sso-min-32bit",
233            feature = "sso-min-64bit",
234            feature = "sso-lv10",
235            feature = "sso-lv20"
236        ))]
237        if data_len <= SSO_DATA_THRESHOLD {
238            let payload = &data[2..2 + data_len];
239            let mut buffer = [0u8; SSO_BUFFER_SIZE];
240            buffer[0] = data[0];
241            buffer[1] = data[1];
242            buffer[2..2 + data_len].copy_from_slice(payload);
243            return Ok((MqttBinary::Small(buffer), 2 + data_len));
244        }
245
246        // Fallback to Large variant
247        let mut encoded = Vec::with_capacity(2 + data_len);
248        encoded.extend_from_slice(&data[0..2 + data_len]);
249        Ok((MqttBinary::Large(encoded), 2 + data_len))
250    }
251}
252
253/// Implementation of `AsRef<[u8]>` for `MqttBinary`
254impl AsRef<[u8]> for MqttBinary {
255    fn as_ref(&self) -> &[u8] {
256        self.as_slice()
257    }
258}
259
260/// Implementation of `Deref` for `MqttBinary`
261impl core::ops::Deref for MqttBinary {
262    type Target = [u8];
263
264    fn deref(&self) -> &Self::Target {
265        self.as_slice()
266    }
267}
268
269/// Implementation of `Serialize` for `MqttBinary`
270impl Serialize for MqttBinary {
271    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
272    where
273        S: Serializer,
274    {
275        serializer.serialize_bytes(self.as_slice())
276    }
277}
278
279/// Implementation of `TryFrom<&str>` for `MqttBinary`
280impl TryFrom<&str> for MqttBinary {
281    type Error = MqttError;
282
283    fn try_from(s: &str) -> Result<Self, Self::Error> {
284        Self::new(s.as_bytes())
285    }
286}
287
288/// Implementation of `Default` for `MqttBinary`
289impl Default for MqttBinary {
290    fn default() -> Self {
291        Self::new(b"").unwrap()
292    }
293}
294
295impl core::fmt::Debug for MqttBinary {
296    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
297        f.debug_struct("MqttBinary")
298            .field("data", &self.as_slice())
299            .finish()
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn test_empty_binary() {
309        let binary = MqttBinary::new(b"").unwrap();
310        assert_eq!(binary.len(), 0);
311        assert!(binary.is_empty());
312        assert_eq!(binary.size(), 2);
313        assert_eq!(binary.as_slice(), b"");
314        assert_eq!(binary.as_bytes(), vec![0x00, 0x00]);
315    }
316
317    #[test]
318    fn test_small_binary() {
319        let data = b"hello";
320        let binary = MqttBinary::new(data).unwrap();
321        assert_eq!(binary.len(), 5);
322        assert!(!binary.is_empty());
323        assert_eq!(binary.size(), 7);
324        assert_eq!(binary.as_slice(), data);
325        assert_eq!(
326            binary.as_bytes(),
327            vec![0x00, 0x05, b'h', b'e', b'l', b'l', b'o']
328        );
329    }
330
331    #[test]
332    fn test_decode_roundtrip() {
333        let original_data = b"test data";
334        let binary = MqttBinary::new(original_data).unwrap();
335        let encoded = binary.as_bytes();
336
337        let (decoded_binary, consumed) = MqttBinary::decode(&encoded).unwrap();
338        assert_eq!(consumed, encoded.len());
339        assert_eq!(decoded_binary.as_slice(), original_data);
340        assert_eq!(decoded_binary.len(), original_data.len());
341    }
342
343    #[test]
344    fn test_max_size() {
345        let data = vec![0u8; 65535];
346        let binary = MqttBinary::new(&data).unwrap();
347        assert_eq!(binary.len(), 65535);
348        assert_eq!(binary.size(), 65537);
349    }
350
351    #[test]
352    fn test_oversized_data() {
353        let data = vec![0u8; 65536];
354        let result = MqttBinary::new(&data);
355        assert!(result.is_err());
356        assert_eq!(result.unwrap_err(), MqttError::MalformedPacket);
357    }
358
359    #[test]
360    fn test_decode_malformed() {
361        // Too short buffer
362        assert!(MqttBinary::decode(&[0x00]).is_err());
363
364        // Length mismatch
365        assert!(MqttBinary::decode(&[0x00, 0x05, b'h', b'i']).is_err());
366    }
367
368    #[test]
369    fn test_continuous_buffer() {
370        let data = b"continuous";
371        let binary = MqttBinary::new(data).unwrap();
372        let buffer = binary.to_continuous_buffer();
373        assert_eq!(buffer[0], 0x00);
374        assert_eq!(buffer[1], 0x0A); // length = 10
375        assert_eq!(&buffer[2..], data);
376    }
377
378    #[cfg(feature = "std")]
379    #[test]
380    fn test_to_buffers() {
381        let data = b"buffer test";
382        let binary = MqttBinary::new(data).unwrap();
383        let buffers = binary.to_buffers();
384
385        // Both variants should return 1 buffer containing the encoded data
386        assert_eq!(buffers.len(), 1);
387
388        // Verify the buffer contains the complete encoded data
389        let buffer_data: &[u8] = &buffers[0];
390        assert_eq!(buffer_data, binary.as_bytes());
391    }
392
393    #[test]
394    fn test_trait_implementations() {
395        let binary = MqttBinary::new(b"trait test").unwrap();
396
397        // AsRef<[u8]>
398        let slice: &[u8] = binary.as_ref();
399        assert_eq!(slice, b"trait test");
400
401        // Deref
402        assert_eq!(&*binary, b"trait test");
403
404        // PartialEq
405        let binary2 = MqttBinary::new(b"trait test").unwrap();
406        assert_eq!(binary, binary2);
407
408        // Clone
409        let cloned = binary.clone();
410        assert_eq!(binary, cloned);
411
412        // Default
413        let default = MqttBinary::default();
414        assert!(default.is_empty());
415    }
416
417    #[test]
418    fn test_from_conversions() {
419        // TryFrom<&str>
420        let binary = MqttBinary::try_from("string test").unwrap();
421        assert_eq!(binary.as_slice(), b"string test");
422
423        // TryFrom with oversized data should fail
424        let long_str = "x".repeat(65536);
425        assert!(MqttBinary::try_from(long_str.as_str()).is_err());
426    }
427
428    // Feature-specific tests
429    #[cfg(any(
430        feature = "sso-min-32bit",
431        feature = "sso-min-64bit",
432        feature = "sso-lv10",
433        feature = "sso-lv20"
434    ))]
435    #[test]
436    fn test_sso_features() {
437        // Test small data (Small variant if SSO is enabled)
438        let small_data = b"small";
439        let binary = MqttBinary::new(small_data).unwrap();
440
441        #[cfg(any(
442            feature = "sso-min-32bit",
443            feature = "sso-min-64bit",
444            feature = "sso-lv10",
445            feature = "sso-lv20"
446        ))]
447        {
448            if let MqttBinary::Small(buffer) = binary {
449                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
450                assert_eq!(len, 5); // 5 bytes data
451            } else {
452                panic!("Expected Small variant for small data with SSO enabled");
453            }
454        }
455
456        #[cfg(not(any(
457            feature = "sso-min-32bit",
458            feature = "sso-min-64bit",
459            feature = "sso-lv10",
460            feature = "sso-lv20"
461        )))]
462        assert!(matches!(binary, MqttBinary::Large(_)));
463
464        // Test data that should always be Large variant (larger than largest SSO buffer)
465        let very_large_data = b"This is a very long binary data that exceeds even the largest SSO buffer size to ensure it's always stored in the Large variant for consistent testing";
466        let binary = MqttBinary::new(very_large_data).unwrap();
467        assert!(matches!(binary, MqttBinary::Large(_)));
468    }
469}