mqtt5_protocol/encoding/
mqtt_binary.rs

1//! MQTT binary data implementation using `BeBytes` 2.10.0 size expressions
2//!
3//! MQTT binary data is prefixed with a 2-byte length field in big-endian format.
4
5use crate::error::{MqttError, Result};
6use crate::prelude::{format, ToString, Vec};
7use bebytes::BeBytes;
8use bytes::Bytes;
9
10/// MQTT binary data with automatic size handling via `BeBytes` size expressions
11#[derive(Debug, Clone, PartialEq, Eq, BeBytes)]
12pub struct MqttBinary {
13    /// Length of the binary data in bytes (big-endian)
14    length: u16,
15
16    /// Binary data with size determined by length field
17    #[bebytes(size = "length")]
18    data: Vec<u8>,
19}
20
21impl MqttBinary {
22    /// Create new MQTT binary data
23    ///
24    /// # Errors
25    /// Returns an error if the data is longer than 65535 bytes
26    pub fn create(data: &[u8]) -> Result<Self> {
27        let len = data.len();
28        if len > u16::MAX as usize {
29            return Err(MqttError::MalformedPacket(format!(
30                "Binary data length {} exceeds maximum {}",
31                len,
32                u16::MAX
33            )));
34        }
35
36        Ok(Self {
37            #[allow(clippy::cast_possible_truncation)]
38            length: len as u16, // Safe: we checked len <= u16::MAX above
39            data: data.to_vec(),
40        })
41    }
42
43    /// Create from Bytes
44    ///
45    /// # Errors
46    /// Returns an error if the data is longer than 65535 bytes
47    pub fn from_bytes(bytes: &Bytes) -> Result<Self> {
48        Self::create(bytes)
49    }
50
51    /// Get the binary data as a slice
52    #[must_use]
53    pub fn as_slice(&self) -> &[u8] {
54        &self.data
55    }
56
57    /// Get the binary data as a Vec
58    #[must_use]
59    pub fn into_vec(self) -> Vec<u8> {
60        self.data
61    }
62
63    /// Convert to Bytes
64    #[must_use]
65    pub fn into_bytes(self) -> Bytes {
66        Bytes::from(self.data)
67    }
68
69    /// Get the total encoded size (length field + data)
70    #[must_use]
71    pub fn encoded_size(&self) -> usize {
72        2 + self.data.len()
73    }
74}
75
76impl TryFrom<&[u8]> for MqttBinary {
77    type Error = MqttError;
78
79    fn try_from(data: &[u8]) -> Result<Self> {
80        Self::create(data)
81    }
82}
83
84impl TryFrom<Vec<u8>> for MqttBinary {
85    type Error = MqttError;
86
87    fn try_from(data: Vec<u8>) -> Result<Self> {
88        Self::create(&data)
89    }
90}
91
92impl TryFrom<Bytes> for MqttBinary {
93    type Error = MqttError;
94
95    fn try_from(bytes: Bytes) -> Result<Self> {
96        Self::from_bytes(&bytes)
97    }
98}
99
100/// Encodes binary data with a 2-byte length prefix (compatibility function)
101///
102/// This function provides compatibility with the old binary module API.
103/// Prefer using `MqttBinary::create(data)?.to_be_bytes()` for new code.
104///
105/// # Errors
106///
107/// Returns an error if the data length exceeds maximum
108pub fn encode_binary<B: bytes::BufMut>(buf: &mut B, data: &[u8]) -> Result<()> {
109    let mqtt_binary = MqttBinary::create(data)?;
110    let encoded = mqtt_binary.to_be_bytes();
111    buf.put_slice(&encoded);
112    Ok(())
113}
114
115/// Decodes binary data with a 2-byte length prefix (compatibility function)
116///
117/// This function provides compatibility with the old binary module API.
118/// Prefer using `MqttBinary::try_from_be_bytes()` for new code.
119///
120/// # Errors
121///
122/// Returns an error if there are insufficient bytes in the buffer
123pub fn decode_binary<B: bytes::Buf>(buf: &mut B) -> Result<Bytes> {
124    if buf.remaining() < 2 {
125        return Err(MqttError::MalformedPacket(
126            "Insufficient bytes for binary data length".to_string(),
127        ));
128    }
129
130    let len = buf.get_u16() as usize;
131
132    if buf.remaining() < len {
133        return Err(MqttError::MalformedPacket(format!(
134            "Insufficient bytes for binary data: expected {}, got {}",
135            len,
136            buf.remaining()
137        )));
138    }
139
140    Ok(buf.copy_to_bytes(len))
141}
142
143/// Encodes optional binary data (compatibility function)
144///
145/// If data is None, nothing is written to the buffer
146///
147/// # Errors
148///
149/// Returns an error if the data length exceeds maximum
150pub fn encode_optional_binary<B: bytes::BufMut>(buf: &mut B, data: Option<&[u8]>) -> Result<()> {
151    if let Some(data) = data {
152        encode_binary(buf, data)?;
153    }
154    Ok(())
155}
156
157/// Calculates the encoded length of binary data (compatibility function)
158#[must_use]
159pub fn binary_len(data: &[u8]) -> usize {
160    2 + data.len()
161}
162
163/// Calculates the encoded length of optional binary data (compatibility function)
164#[must_use]
165pub fn optional_binary_len(data: Option<&[u8]>) -> usize {
166    data.map_or(0, binary_len)
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use crate::prelude::vec;
173    use bytes::BytesMut;
174
175    #[test]
176    fn test_mqtt_binary_encoding() {
177        let mqtt_bin = MqttBinary::create(&[0x01, 0x02, 0x03]).unwrap();
178        let bytes = mqtt_bin.to_be_bytes();
179
180        // Check encoding: 2-byte length (0x00, 0x03) + data
181        assert_eq!(bytes, vec![0x00, 0x03, 0x01, 0x02, 0x03]);
182    }
183
184    #[test]
185    fn test_mqtt_binary_decoding() {
186        let data = vec![0x00, 0x03, 0x01, 0x02, 0x03];
187        let (mqtt_bin, consumed) = MqttBinary::try_from_be_bytes(&data).unwrap();
188
189        assert_eq!(mqtt_bin.as_slice(), &[0x01, 0x02, 0x03]);
190        assert_eq!(consumed, 5);
191    }
192
193    #[test]
194    fn test_mqtt_binary_round_trip() {
195        let original = MqttBinary::create(&[0xFF, 0x00, 0xAB]).unwrap();
196        let bytes = original.to_be_bytes();
197        let (decoded, _) = MqttBinary::try_from_be_bytes(&bytes).unwrap();
198
199        assert_eq!(original, decoded);
200    }
201
202    #[test]
203    fn test_empty_binary() {
204        let mqtt_bin = MqttBinary::create(&[]).unwrap();
205        let bytes = mqtt_bin.to_be_bytes();
206
207        assert_eq!(bytes, vec![0x00, 0x00]);
208    }
209
210    #[test]
211    fn test_binary_too_long() {
212        let long_data = vec![0u8; 65536];
213        let result = MqttBinary::create(&long_data);
214
215        assert!(result.is_err());
216    }
217
218    #[test]
219    fn test_compatibility_functions() {
220        let mut buf = BytesMut::new();
221        let test_data = vec![0x01, 0x02, 0x03];
222
223        // Test encode/decode compatibility
224        encode_binary(&mut buf, &test_data).unwrap();
225        let decoded = decode_binary(&mut buf).unwrap();
226
227        assert_eq!(&decoded[..], &test_data[..]);
228    }
229}