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