mqtt_protocol_core/mqtt/packet/
mqtt_string.rs

1/**
2 * MIT License
3 *
4 * Copyright (c) 2025 Takatoshi Kondo
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24use crate::mqtt::result_code::MqttError;
25use alloc::{string::String, vec, vec::Vec};
26use serde::{Serialize, Serializer};
27#[cfg(feature = "std")]
28use std::io::IoSlice;
29
30/// MQTT String representation with pre-encoded byte buffer
31///
32/// This struct represents UTF-8 strings as specified in the MQTT protocol specification.
33/// It efficiently stores string data with a 2-byte length prefix, following the MQTT
34/// wire format for string fields.
35///
36/// The string data is stored in a pre-encoded format internally, which includes:
37/// - 2 bytes for the length prefix (big-endian u16)
38/// - The UTF-8 encoded string bytes
39///
40/// This approach provides several benefits:
41/// - Zero-copy serialization to network buffers
42/// - Efficient memory usage with single allocation
43/// - Guaranteed MQTT protocol compliance
44/// - Fast size calculations
45/// - UTF-8 validation at construction time
46///
47/// # Size Limits
48///
49/// The maximum size of string data is 65,535 bytes (2^16 - 1), as specified
50/// by the MQTT protocol. Attempting to create an `MqttString` with larger
51/// data will result in an error.
52///
53/// # UTF-8 Validation
54///
55/// All string data is validated to be valid UTF-8 at construction time.
56/// Once created, the string is guaranteed to be valid UTF-8, allowing
57/// for safe conversion to `&str` without runtime checks.
58///
59/// # Examples
60///
61/// ```ignore
62/// use mqtt_protocol_core::mqtt;
63///
64/// // Create from string literal
65/// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
66///
67/// // Access the string content
68/// assert_eq!(mqtt_str.as_str(), "hello world");
69/// assert_eq!(mqtt_str.len(), 11);
70///
71/// // Get the complete encoded buffer (length prefix + UTF-8 bytes)
72/// let encoded = mqtt_str.as_bytes();
73/// assert_eq!(encoded.len(), 13); // 2 bytes length + 11 bytes data
74///
75/// // String operations
76/// assert!(mqtt_str.contains('w'));
77/// assert!(mqtt_str.starts_with("hello"));
78/// ```
79#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
80pub struct MqttString {
81    /// Complete buffer including length prefix (2 bytes) + UTF-8 byte sequence
82    encoded: Vec<u8>,
83}
84
85impl MqttString {
86    /// Create a new MqttString from a string
87    ///
88    /// Creates an `MqttString` instance from the provided string data.
89    /// The string is converted to UTF-8 bytes and stored with a 2-byte length prefix.
90    ///
91    /// # Parameters
92    ///
93    /// * `s` - String data to store. Can be any type that implements `AsRef<str>`
94    ///   such as `&str`, `String`, or `Cow<str>`
95    ///
96    /// # Returns
97    ///
98    /// * `Ok(MqttString)` - Successfully created string
99    /// * `Err(MqttError::MalformedPacket)` - If string length exceeds 65,535 bytes
100    ///
101    /// # Examples
102    ///
103    /// ```ignore
104    /// use mqtt_protocol_core::mqtt;
105    ///
106    /// // From string literal
107    /// let mqtt_str = mqtt::packet::MqttString::new("hello").unwrap();
108    ///
109    /// // From String
110    /// let owned = String::from("world");
111    /// let mqtt_str = mqtt::packet::MqttString::new(owned).unwrap();
112    ///
113    /// // UTF-8 strings are supported
114    /// let mqtt_str = mqtt::packet::MqttString::new("hello").unwrap();
115    /// ```
116    pub fn new(s: impl AsRef<str>) -> Result<Self, MqttError> {
117        let s_ref = s.as_ref();
118        let len = s_ref.len();
119
120        if len > 65535 {
121            return Err(MqttError::MalformedPacket);
122        }
123
124        let mut encoded = Vec::with_capacity(2 + len);
125        encoded.push((len >> 8) as u8);
126        encoded.push(len as u8);
127        encoded.extend_from_slice(s_ref.as_bytes());
128
129        Ok(Self { encoded })
130    }
131
132    /// Get the complete encoded byte sequence including length prefix
133    ///
134    /// Returns the complete internal buffer, which includes the 2-byte length prefix
135    /// followed by the UTF-8 encoded string bytes. This is the exact format used
136    /// in MQTT wire protocol.
137    ///
138    /// # Returns
139    ///
140    /// A byte slice containing [length_high, length_low, utf8_bytes...]
141    ///
142    /// # Examples
143    ///
144    /// ```ignore
145    /// use mqtt_protocol_core::mqtt;
146    ///
147    /// let mqtt_str = mqtt::packet::MqttString::new("hi").unwrap();
148    /// let bytes = mqtt_str.as_bytes();
149    /// assert_eq!(bytes, &[0x00, 0x02, b'h', b'i']);
150    /// ```
151    pub fn as_bytes(&self) -> &[u8] {
152        &self.encoded
153    }
154
155    /// Get the string content as a string slice
156    ///
157    /// Returns a string slice containing the UTF-8 string data,
158    /// excluding the 2-byte length prefix. This operation is zero-cost
159    /// since UTF-8 validity was verified at construction time.
160    ///
161    /// # Returns
162    ///
163    /// A string slice containing the UTF-8 string data
164    ///
165    /// # Examples
166    ///
167    /// ```ignore
168    /// use mqtt_protocol_core::mqtt;
169    ///
170    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
171    /// assert_eq!(mqtt_str.as_str(), "hello world");
172    /// ```
173    pub fn as_str(&self) -> &str {
174        // SAFETY: UTF-8 validity verified during MqttString creation or decode
175        // Also, no direct modification of encoded field is provided,
176        // ensuring buffer immutability
177        unsafe { core::str::from_utf8_unchecked(&self.encoded[2..]) }
178    }
179
180    /// Get the length of the string data in bytes
181    ///
182    /// Returns the number of bytes in the UTF-8 string data,
183    /// excluding the 2-byte length prefix. Note that this is
184    /// the byte length, not the character count.
185    ///
186    /// # Returns
187    ///
188    /// The length of the string data in bytes
189    ///
190    /// # Examples
191    ///
192    /// ```ignore
193    /// use mqtt_protocol_core::mqtt;
194    ///
195    /// let ascii = mqtt::packet::MqttString::new("hello").unwrap();
196    /// assert_eq!(ascii.len(), 5);
197    ///
198    /// // UTF-8 strings: byte length != character count
199    /// let utf8 = mqtt::packet::MqttString::new("hello").unwrap();
200    /// assert_eq!(utf8.len(), 5); // 5 characters
201    /// ```
202    pub fn len(&self) -> usize {
203        self.encoded.len() - 2
204    }
205
206    /// Check if the string is empty
207    ///
208    /// Returns `true` if the string contains no characters,
209    /// `false` otherwise.
210    ///
211    /// # Returns
212    ///
213    /// `true` if the string is empty, `false` otherwise
214    ///
215    /// # Examples
216    ///
217    /// ```ignore
218    /// use mqtt_protocol_core::mqtt;
219    ///
220    /// let empty = mqtt::packet::MqttString::new("").unwrap();
221    /// assert!(empty.is_empty());
222    ///
223    /// let text = mqtt::packet::MqttString::new("x").unwrap();
224    /// assert!(!text.is_empty());
225    /// ```
226    pub fn is_empty(&self) -> bool {
227        self.encoded.len() <= 2
228    }
229
230    /// Get the total encoded size including the length field
231    ///
232    /// Returns the total number of bytes in the encoded representation,
233    /// including the 2-byte length prefix and the UTF-8 string data.
234    ///
235    /// # Returns
236    ///
237    /// The total size in bytes (length prefix + string data)
238    ///
239    /// # Examples
240    ///
241    /// ```ignore
242    /// use mqtt_protocol_core::mqtt;
243    ///
244    /// let mqtt_str = mqtt::packet::MqttString::new("hello").unwrap();
245    /// assert_eq!(mqtt_str.size(), 7); // 2 bytes prefix + 5 bytes data
246    /// assert_eq!(mqtt_str.len(), 5);  // Only string length
247    /// ```
248    pub fn size(&self) -> usize {
249        self.encoded.len()
250    }
251
252    /// Create IoSlice buffers for efficient network I/O
253    ///
254    /// Returns a vector of `IoSlice` objects that can be used for vectored I/O
255    /// operations, allowing zero-copy writes to network sockets.
256    ///
257    /// # Returns
258    ///
259    /// A vector containing a single `IoSlice` referencing the complete encoded buffer
260    ///
261    /// # Examples
262    ///
263    /// ```ignore
264    /// use mqtt_protocol_core::mqtt;
265    /// use std::io::IoSlice;
266    ///
267    /// let mqtt_str = mqtt::packet::MqttString::new("data").unwrap();
268    /// let buffers = mqtt_str.to_buffers();
269    /// // Can be used with vectored write operations
270    /// // socket.write_vectored(&buffers)?;
271    /// ```
272    #[cfg(feature = "std")]
273    pub fn to_buffers(&self) -> Vec<IoSlice<'_>> {
274        vec![IoSlice::new(&self.encoded)]
275    }
276
277    /// Create a continuous buffer containing the complete packet data
278    ///
279    /// Returns a vector containing all packet bytes in a single continuous buffer.
280    /// This method is compatible with no-std environments and provides an alternative
281    /// to [`to_buffers()`] when vectored I/O is not needed.
282    ///
283    /// # Returns
284    ///
285    /// A vector containing the complete encoded buffer
286    ///
287    /// # Examples
288    ///
289    /// ```ignore
290    /// use mqtt_protocol_core::mqtt;
291    ///
292    /// let mqtt_str = mqtt::packet::MqttString::new("data").unwrap();
293    /// let buffer = mqtt_str.to_continuous_buffer();
294    /// // buffer contains all packet bytes
295    /// ```
296    ///
297    /// [`to_buffers()`]: #method.to_buffers
298    pub fn to_continuous_buffer(&self) -> Vec<u8> {
299        self.encoded.clone()
300    }
301
302    /// Parse string data from a byte sequence
303    ///
304    /// Decodes MQTT string data from a byte buffer according to the MQTT protocol.
305    /// The buffer must start with a 2-byte length prefix followed by valid UTF-8 bytes.
306    ///
307    /// # Parameters
308    ///
309    /// * `data` - Byte buffer containing the encoded string data
310    ///
311    /// # Returns
312    ///
313    /// * `Ok((MqttString, bytes_consumed))` - Successfully parsed string and number of bytes consumed
314    /// * `Err(MqttError::MalformedPacket)` - If the buffer is too short, malformed, or contains invalid UTF-8
315    ///
316    /// # Examples
317    ///
318    /// ```ignore
319    /// use mqtt_protocol_core::mqtt;
320    ///
321    /// // Buffer: [length_high, length_low, utf8_bytes...]
322    /// let buffer = &[0x00, 0x05, b'h', b'e', b'l', b'l', b'o'];
323    /// let (mqtt_str, consumed) = mqtt::packet::MqttString::decode(buffer).unwrap();
324    ///
325    /// assert_eq!(mqtt_str.as_str(), "hello");
326    /// assert_eq!(consumed, 7);
327    /// ```
328    pub fn decode(data: &[u8]) -> Result<(Self, usize), MqttError> {
329        if data.len() < 2 {
330            return Err(MqttError::MalformedPacket);
331        }
332
333        let string_len = ((data[0] as usize) << 8) | (data[1] as usize);
334        if data.len() < 2 + string_len {
335            return Err(MqttError::MalformedPacket);
336        }
337
338        // Verify UTF-8 validity - return MQTT error on parse failure
339        if core::str::from_utf8(&data[2..2 + string_len]).is_err() {
340            return Err(MqttError::MalformedPacket);
341        }
342
343        // Create encoded buffer
344        let mut encoded = Vec::with_capacity(2 + string_len);
345        encoded.extend_from_slice(&data[0..2 + string_len]);
346
347        Ok((Self { encoded }, 2 + string_len))
348    }
349
350    /// Check if the string contains a specific character
351    ///
352    /// Returns `true` if the string contains the specified character,
353    /// `false` otherwise.
354    ///
355    /// # Parameters
356    ///
357    /// * `c` - The character to search for
358    ///
359    /// # Returns
360    ///
361    /// `true` if the character is found, `false` otherwise
362    ///
363    /// # Examples
364    ///
365    /// ```ignore
366    /// use mqtt_protocol_core::mqtt;
367    ///
368    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
369    /// assert!(mqtt_str.contains('w'));
370    /// assert!(!mqtt_str.contains('x'));
371    /// ```
372    pub fn contains(&self, c: char) -> bool {
373        self.as_str().contains(c)
374    }
375
376    /// Check if the string starts with the specified prefix
377    ///
378    /// Returns `true` if the string starts with the given prefix,
379    /// `false` otherwise.
380    ///
381    /// # Parameters
382    ///
383    /// * `prefix` - The prefix string to check for
384    ///
385    /// # Returns
386    ///
387    /// `true` if the string starts with the prefix, `false` otherwise
388    ///
389    /// # Examples
390    ///
391    /// ```ignore
392    /// use mqtt_protocol_core::mqtt;
393    ///
394    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
395    /// assert!(mqtt_str.starts_with("hello"));
396    /// assert!(!mqtt_str.starts_with("world"));
397    /// ```
398    pub fn starts_with(&self, prefix: &str) -> bool {
399        self.as_str().starts_with(prefix)
400    }
401
402    /// Check if the string ends with the specified suffix
403    ///
404    /// Returns `true` if the string ends with the given suffix,
405    /// `false` otherwise.
406    ///
407    /// # Parameters
408    ///
409    /// * `suffix` - The suffix string to check for
410    ///
411    /// # Returns
412    ///
413    /// `true` if the string ends with the suffix, `false` otherwise
414    ///
415    /// # Examples
416    ///
417    /// ```ignore
418    /// use mqtt_protocol_core::mqtt;
419    ///
420    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
421    /// assert!(mqtt_str.ends_with("world"));
422    /// assert!(!mqtt_str.ends_with("hello"));
423    /// ```
424    pub fn ends_with(&self, suffix: &str) -> bool {
425        self.as_str().ends_with(suffix)
426    }
427}
428
429/// Implementation of `AsRef<str>` for `MqttString`
430///
431/// Returns the string content when the `MqttString` is used in contexts
432/// expecting a string slice reference.
433impl AsRef<str> for MqttString {
434    fn as_ref(&self) -> &str {
435        self.as_str()
436    }
437}
438
439/// Implementation of `Display` for `MqttString`
440///
441/// Formats the string content for display purposes.
442/// This allows `MqttString` to be used with `println!`, `format!`, etc.
443impl core::fmt::Display for MqttString {
444    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
445        write!(f, "{}", self.as_str())
446    }
447}
448
449/// Implementation of `Deref` for `MqttString`
450///
451/// Allows `MqttString` to be used directly as a string slice in many contexts
452/// through automatic dereferencing. This enables method calls like `mqtt_str.len()`
453/// to work directly on the string content.
454impl core::ops::Deref for MqttString {
455    type Target = str;
456
457    fn deref(&self) -> &Self::Target {
458        self.as_str()
459    }
460}
461
462/// Implementation of `Serialize` for `MqttString`
463///
464/// Serializes the string content as a string value.
465/// This is useful for JSON serialization and other serialization formats.
466impl Serialize for MqttString {
467    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
468    where
469        S: Serializer,
470    {
471        self.as_str().serialize(serializer)
472    }
473}
474
475/// Implementation of `PartialEq<str>` for `MqttString`
476///
477/// Allows direct comparison between `MqttString` and `str`.
478impl core::cmp::PartialEq<str> for MqttString {
479    fn eq(&self, other: &str) -> bool {
480        self.as_str() == other
481    }
482}
483
484/// Implementation of `PartialEq<&str>` for `MqttString`
485///
486/// Allows direct comparison between `MqttString` and `&str`.
487impl core::cmp::PartialEq<&str> for MqttString {
488    fn eq(&self, other: &&str) -> bool {
489        self.as_str() == *other
490    }
491}
492
493/// Implementation of `PartialEq<String>` for `MqttString`
494///
495/// Allows direct comparison between `MqttString` and `String`.
496impl core::cmp::PartialEq<String> for MqttString {
497    fn eq(&self, other: &String) -> bool {
498        self.as_str() == other.as_str()
499    }
500}
501
502/// Implementation of `Hash` for `MqttString`
503///
504/// Hashes the string content, allowing `MqttString` to be used in hash-based
505/// collections like `HashMap` and `HashSet`.
506impl core::hash::Hash for MqttString {
507    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
508        self.as_str().hash(state);
509    }
510}
511
512/// Implementation of `Default` for `MqttString`
513///
514/// Creates an empty `MqttString` with zero-length string content.
515/// The internal buffer contains only the 2-byte length prefix (0x00, 0x00).
516impl Default for MqttString {
517    fn default() -> Self {
518        MqttString {
519            encoded: vec![0x00, 0x00],
520        }
521    }
522}
523
524/// Implementation of `TryFrom<&str>` for `MqttString`
525///
526/// Converts a string slice to `MqttString`. This is a convenient way to
527/// create `MqttString` instances from string literals.
528impl TryFrom<&str> for MqttString {
529    type Error = MqttError;
530
531    fn try_from(s: &str) -> Result<Self, Self::Error> {
532        MqttString::new(s)
533    }
534}
535
536/// Implementation of `TryFrom<String>` for `MqttString`
537///
538/// Converts an owned `String` to `MqttString`. This is a convenient way to
539/// create `MqttString` instances from owned strings.
540impl TryFrom<String> for MqttString {
541    type Error = MqttError;
542
543    fn try_from(s: String) -> Result<Self, Self::Error> {
544        MqttString::new(s)
545    }
546}