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