mqtt_protocol_core/mqtt/packet/
mqtt_string.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::string::String;
24use alloc::vec::Vec;
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 String representation with pre-encoded byte buffer
61///
62/// This struct represents UTF-8 strings as specified in the MQTT protocol specification.
63/// It efficiently stores string data with a 2-byte length prefix, following the MQTT
64/// wire format for string fields.
65///
66/// The string data is stored in a pre-encoded format internally, which includes:
67/// - 2 bytes for the length prefix (big-endian u16)
68/// - The UTF-8 encoded string bytes
69///
70/// This approach provides several benefits:
71/// - Zero-copy serialization to network buffers
72/// - Efficient memory usage with single allocation
73/// - Guaranteed MQTT protocol compliance
74/// - Fast size calculations
75/// - UTF-8 validation at construction time
76///
77/// # Size Limits
78///
79/// The maximum size of string data is 65,535 bytes (2^16 - 1), as specified
80/// by the MQTT protocol. Attempting to create an `MqttString` with larger
81/// data will result in an error.
82///
83/// # UTF-8 Validation
84///
85/// All string data is validated to be valid UTF-8 at construction time.
86/// Once created, the string is guaranteed to be valid UTF-8, allowing
87/// for safe conversion to `&str` without runtime checks.
88///
89/// # Examples
90///
91/// ```ignore
92/// use mqtt_protocol_core::mqtt;
93///
94/// // Create from string literal
95/// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
96///
97/// // Access the string content
98/// assert_eq!(mqtt_str.as_str(), "hello world");
99/// assert_eq!(mqtt_str.len(), 11);
100///
101/// // Get the complete encoded buffer (length prefix + UTF-8 bytes)
102/// let encoded = mqtt_str.as_bytes();
103/// assert_eq!(encoded.len(), 13); // 2 bytes length + 11 bytes data
104///
105/// // String operations
106/// assert!(mqtt_str.contains('w'));
107/// assert!(mqtt_str.starts_with("hello"));
108/// ```
109#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
110#[allow(clippy::large_enum_variant)]
111pub enum MqttString {
112    #[cfg(any(
113        feature = "sso-min-32bit",
114        feature = "sso-min-64bit",
115        feature = "sso-lv10",
116        feature = "sso-lv20"
117    ))]
118    Small([u8; SSO_BUFFER_SIZE]),
119    Large(Vec<u8>),
120}
121
122impl MqttString {
123    /// Create a new MqttString from a string
124    ///
125    /// Creates an `MqttString` instance from the provided string data.
126    /// The string is converted to UTF-8 bytes and stored with a 2-byte length prefix.
127    ///
128    /// # Parameters
129    ///
130    /// * `s` - String data to store. Can be any type that implements `AsRef<str>`
131    ///   such as `&str`, `String`, or `Cow<str>`
132    ///
133    /// # Returns
134    ///
135    /// * `Ok(MqttString)` - Successfully created string
136    /// * `Err(MqttError::MalformedPacket)` - If string length exceeds 65,535 bytes
137    ///
138    /// # Examples
139    ///
140    /// ```ignore
141    /// use mqtt_protocol_core::mqtt;
142    ///
143    /// // From string literal
144    /// let mqtt_str = mqtt::packet::MqttString::new("hello").unwrap();
145    ///
146    /// // From String
147    /// let owned = String::from("world");
148    /// let mqtt_str = mqtt::packet::MqttString::new(owned).unwrap();
149    ///
150    /// // UTF-8 strings are supported
151    /// let mqtt_str = mqtt::packet::MqttString::new("hello").unwrap();
152    /// ```
153    pub fn new(s: impl AsRef<str>) -> Result<Self, MqttError> {
154        let s_ref = s.as_ref();
155        let len = s_ref.len();
156
157        if len > 65535 {
158            return Err(MqttError::MalformedPacket);
159        }
160
161        let total_encoded_len = 2 + len;
162
163        // Try to fit in Small variant if SSO is enabled
164        #[cfg(any(
165            feature = "sso-min-32bit",
166            feature = "sso-min-64bit",
167            feature = "sso-lv10",
168            feature = "sso-lv20"
169        ))]
170        if len <= SSO_DATA_THRESHOLD {
171            let mut buffer = [0u8; SSO_BUFFER_SIZE];
172            buffer[0] = (len >> 8) as u8;
173            buffer[1] = len as u8;
174            buffer[2..2 + len].copy_from_slice(s_ref.as_bytes());
175            return Ok(Self::Small(buffer));
176        }
177
178        // Fallback to Large variant
179        let mut encoded = Vec::with_capacity(total_encoded_len);
180        encoded.push((len >> 8) as u8);
181        encoded.push(len as u8);
182        encoded.extend_from_slice(s_ref.as_bytes());
183
184        Ok(Self::Large(encoded))
185    }
186
187    /// Get the complete encoded byte sequence including length prefix
188    ///
189    /// Returns the complete internal buffer, which includes the 2-byte length prefix
190    /// followed by the UTF-8 encoded string bytes. This is the exact format used
191    /// in MQTT wire protocol.
192    ///
193    /// # Returns
194    ///
195    /// A byte slice containing [length_high, length_low, utf8_bytes...]
196    ///
197    /// # Examples
198    ///
199    /// ```ignore
200    /// use mqtt_protocol_core::mqtt;
201    ///
202    /// let mqtt_str = mqtt::packet::MqttString::new("hi").unwrap();
203    /// let bytes = mqtt_str.as_bytes();
204    /// assert_eq!(bytes, &[0x00, 0x02, b'h', b'i']);
205    /// ```
206    pub fn as_bytes(&self) -> &[u8] {
207        match self {
208            MqttString::Large(encoded) => encoded,
209            #[cfg(any(
210                feature = "sso-min-32bit",
211                feature = "sso-min-64bit",
212                feature = "sso-lv10",
213                feature = "sso-lv20"
214            ))]
215            MqttString::Small(buffer) => {
216                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
217                &buffer[..2 + len]
218            }
219        }
220    }
221
222    /// Get the string content as a string slice
223    ///
224    /// Returns a string slice containing the UTF-8 string data,
225    /// excluding the 2-byte length prefix. This operation is zero-cost
226    /// since UTF-8 validity was verified at construction time.
227    ///
228    /// # Returns
229    ///
230    /// A string slice containing the UTF-8 string data
231    ///
232    /// # Examples
233    ///
234    /// ```ignore
235    /// use mqtt_protocol_core::mqtt;
236    ///
237    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
238    /// assert_eq!(mqtt_str.as_str(), "hello world");
239    /// ```
240    pub fn as_str(&self) -> &str {
241        // SAFETY: UTF-8 validity verified during MqttString creation or decode
242        // Also, no direct modification of encoded field is provided,
243        // ensuring buffer immutability
244        let data = match self {
245            MqttString::Large(encoded) => &encoded[2..],
246            #[cfg(any(
247                feature = "sso-min-32bit",
248                feature = "sso-min-64bit",
249                feature = "sso-lv10",
250                feature = "sso-lv20"
251            ))]
252            MqttString::Small(buffer) => {
253                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
254                &buffer[2..2 + len]
255            }
256        };
257        unsafe { core::str::from_utf8_unchecked(data) }
258    }
259
260    /// Get the length of the string data in bytes
261    ///
262    /// Returns the number of bytes in the UTF-8 string data,
263    /// excluding the 2-byte length prefix. Note that this is
264    /// the byte length, not the character count.
265    ///
266    /// # Returns
267    ///
268    /// The length of the string data in bytes
269    ///
270    /// # Examples
271    ///
272    /// ```ignore
273    /// use mqtt_protocol_core::mqtt;
274    ///
275    /// let ascii = mqtt::packet::MqttString::new("hello").unwrap();
276    /// assert_eq!(ascii.len(), 5);
277    ///
278    /// // UTF-8 strings: byte length != character count
279    /// let utf8 = mqtt::packet::MqttString::new("hello").unwrap();
280    /// assert_eq!(utf8.len(), 5); // 5 characters
281    /// ```
282    pub fn len(&self) -> usize {
283        match self {
284            MqttString::Large(encoded) => encoded.len() - 2,
285            #[cfg(any(
286                feature = "sso-min-32bit",
287                feature = "sso-min-64bit",
288                feature = "sso-lv10",
289                feature = "sso-lv20"
290            ))]
291            MqttString::Small(buffer) => ((buffer[0] as usize) << 8) | (buffer[1] as usize),
292        }
293    }
294
295    /// Check if the string is empty
296    ///
297    /// Returns `true` if the string contains no characters,
298    /// `false` otherwise.
299    ///
300    /// # Returns
301    ///
302    /// `true` if the string is empty, `false` otherwise
303    ///
304    /// # Examples
305    ///
306    /// ```ignore
307    /// use mqtt_protocol_core::mqtt;
308    ///
309    /// let empty = mqtt::packet::MqttString::new("").unwrap();
310    /// assert!(empty.is_empty());
311    ///
312    /// let text = mqtt::packet::MqttString::new("x").unwrap();
313    /// assert!(!text.is_empty());
314    /// ```
315    pub fn is_empty(&self) -> bool {
316        self.len() == 0
317    }
318
319    /// Get the total encoded size including the length field
320    ///
321    /// Returns the total number of bytes in the encoded representation,
322    /// including the 2-byte length prefix and the UTF-8 string data.
323    ///
324    /// # Returns
325    ///
326    /// The total size in bytes (length prefix + string data)
327    ///
328    /// # Examples
329    ///
330    /// ```ignore
331    /// use mqtt_protocol_core::mqtt;
332    ///
333    /// let mqtt_str = mqtt::packet::MqttString::new("hello").unwrap();
334    /// assert_eq!(mqtt_str.size(), 7); // 2 bytes prefix + 5 bytes data
335    /// assert_eq!(mqtt_str.len(), 5);  // Only string length
336    /// ```
337    pub fn size(&self) -> usize {
338        match self {
339            MqttString::Large(encoded) => encoded.len(),
340            #[cfg(any(
341                feature = "sso-min-32bit",
342                feature = "sso-min-64bit",
343                feature = "sso-lv10",
344                feature = "sso-lv20"
345            ))]
346            MqttString::Small(buffer) => {
347                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
348                2 + len
349            }
350        }
351    }
352
353    /// Create IoSlice buffers for efficient network I/O
354    ///
355    /// Returns a vector of `IoSlice` objects that can be used for vectored I/O
356    /// operations, allowing zero-copy writes to network sockets.
357    ///
358    /// # Returns
359    ///
360    /// A vector containing a single `IoSlice` referencing the complete encoded buffer
361    ///
362    /// # Examples
363    ///
364    /// ```ignore
365    /// use mqtt_protocol_core::mqtt;
366    /// use std::io::IoSlice;
367    ///
368    /// let mqtt_str = mqtt::packet::MqttString::new("data").unwrap();
369    /// let buffers = mqtt_str.to_buffers();
370    /// // Can be used with vectored write operations
371    /// // socket.write_vectored(&buffers)?;
372    /// ```
373    #[cfg(feature = "std")]
374    pub fn to_buffers(&self) -> Vec<IoSlice<'_>> {
375        match self {
376            MqttString::Large(encoded) => vec![IoSlice::new(encoded)],
377            #[cfg(any(
378                feature = "sso-min-32bit",
379                feature = "sso-min-64bit",
380                feature = "sso-lv10",
381                feature = "sso-lv20"
382            ))]
383            MqttString::Small(buffer) => {
384                let len = ((buffer[0] as usize) << 8) | (buffer[1] as usize);
385                vec![IoSlice::new(&buffer[..2 + len])]
386            }
387        }
388    }
389
390    /// Create a continuous buffer containing the complete packet data
391    ///
392    /// Returns a vector containing all packet bytes in a single continuous buffer.
393    /// This method is compatible with no-std environments and provides an alternative
394    /// to [`to_buffers()`] when vectored I/O is not needed.
395    ///
396    /// # Returns
397    ///
398    /// A vector containing the complete encoded buffer
399    ///
400    /// # Examples
401    ///
402    /// ```ignore
403    /// use mqtt_protocol_core::mqtt;
404    ///
405    /// let mqtt_str = mqtt::packet::MqttString::new("data").unwrap();
406    /// let buffer = mqtt_str.to_continuous_buffer();
407    /// // buffer contains all packet bytes
408    /// ```
409    ///
410    /// [`to_buffers()`]: #method.to_buffers
411    pub fn to_continuous_buffer(&self) -> Vec<u8> {
412        self.as_bytes().to_vec()
413    }
414
415    /// Parse string data from a byte sequence
416    ///
417    /// Decodes MQTT string data from a byte buffer according to the MQTT protocol.
418    /// The buffer must start with a 2-byte length prefix followed by valid UTF-8 bytes.
419    ///
420    /// # Parameters
421    ///
422    /// * `data` - Byte buffer containing the encoded string data
423    ///
424    /// # Returns
425    ///
426    /// * `Ok((MqttString, bytes_consumed))` - Successfully parsed string and number of bytes consumed
427    /// * `Err(MqttError::MalformedPacket)` - If the buffer is too short, malformed, or contains invalid UTF-8
428    ///
429    /// # Examples
430    ///
431    /// ```ignore
432    /// use mqtt_protocol_core::mqtt;
433    ///
434    /// // Buffer: [length_high, length_low, utf8_bytes...]
435    /// let buffer = &[0x00, 0x05, b'h', b'e', b'l', b'l', b'o'];
436    /// let (mqtt_str, consumed) = mqtt::packet::MqttString::decode(buffer).unwrap();
437    ///
438    /// assert_eq!(mqtt_str.as_str(), "hello");
439    /// assert_eq!(consumed, 7);
440    /// ```
441    pub fn decode(data: &[u8]) -> Result<(Self, usize), MqttError> {
442        if data.len() < 2 {
443            return Err(MqttError::MalformedPacket);
444        }
445
446        let string_len = ((data[0] as usize) << 8) | (data[1] as usize);
447        if data.len() < 2 + string_len {
448            return Err(MqttError::MalformedPacket);
449        }
450
451        // Verify UTF-8 validity - return MQTT error on parse failure
452        if core::str::from_utf8(&data[2..2 + string_len]).is_err() {
453            return Err(MqttError::MalformedPacket);
454        }
455
456        let total_encoded_len = 2 + string_len;
457
458        // Try to fit in Small variant if SSO is enabled
459        #[cfg(any(
460            feature = "sso-min-32bit",
461            feature = "sso-min-64bit",
462            feature = "sso-lv10",
463            feature = "sso-lv20"
464        ))]
465        if string_len <= SSO_DATA_THRESHOLD {
466            let mut buffer = [0u8; SSO_BUFFER_SIZE];
467            buffer[0] = data[0];
468            buffer[1] = data[1];
469            buffer[2..2 + string_len].copy_from_slice(&data[2..2 + string_len]);
470            return Ok((Self::Small(buffer), total_encoded_len));
471        }
472
473        // Fallback to Large variant
474        let mut encoded = Vec::with_capacity(total_encoded_len);
475        encoded.extend_from_slice(&data[0..total_encoded_len]);
476
477        Ok((Self::Large(encoded), total_encoded_len))
478    }
479
480    /// Check if the string contains a specific character
481    ///
482    /// Returns `true` if the string contains the specified character,
483    /// `false` otherwise.
484    ///
485    /// # Parameters
486    ///
487    /// * `c` - The character to search for
488    ///
489    /// # Returns
490    ///
491    /// `true` if the character is found, `false` otherwise
492    ///
493    /// # Examples
494    ///
495    /// ```ignore
496    /// use mqtt_protocol_core::mqtt;
497    ///
498    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
499    /// assert!(mqtt_str.contains('w'));
500    /// assert!(!mqtt_str.contains('x'));
501    /// ```
502    pub fn contains(&self, c: char) -> bool {
503        self.as_str().contains(c)
504    }
505
506    /// Check if the string starts with the specified prefix
507    ///
508    /// Returns `true` if the string starts with the given prefix,
509    /// `false` otherwise.
510    ///
511    /// # Parameters
512    ///
513    /// * `prefix` - The prefix string to check for
514    ///
515    /// # Returns
516    ///
517    /// `true` if the string starts with the prefix, `false` otherwise
518    ///
519    /// # Examples
520    ///
521    /// ```ignore
522    /// use mqtt_protocol_core::mqtt;
523    ///
524    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
525    /// assert!(mqtt_str.starts_with("hello"));
526    /// assert!(!mqtt_str.starts_with("world"));
527    /// ```
528    pub fn starts_with(&self, prefix: &str) -> bool {
529        self.as_str().starts_with(prefix)
530    }
531
532    /// Check if the string ends with the specified suffix
533    ///
534    /// Returns `true` if the string ends with the given suffix,
535    /// `false` otherwise.
536    ///
537    /// # Parameters
538    ///
539    /// * `suffix` - The suffix string to check for
540    ///
541    /// # Returns
542    ///
543    /// `true` if the string ends with the suffix, `false` otherwise
544    ///
545    /// # Examples
546    ///
547    /// ```ignore
548    /// use mqtt_protocol_core::mqtt;
549    ///
550    /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
551    /// assert!(mqtt_str.ends_with("world"));
552    /// assert!(!mqtt_str.ends_with("hello"));
553    /// ```
554    pub fn ends_with(&self, suffix: &str) -> bool {
555        self.as_str().ends_with(suffix)
556    }
557}
558
559/// Implementation of `AsRef<str>` for `MqttString`
560///
561/// Returns the string content when the `MqttString` is used in contexts
562/// expecting a string slice reference.
563impl AsRef<str> for MqttString {
564    fn as_ref(&self) -> &str {
565        self.as_str()
566    }
567}
568
569/// Implementation of `Display` for `MqttString`
570///
571/// Formats the string content for display purposes.
572/// This allows `MqttString` to be used with `println!`, `format!`, etc.
573impl core::fmt::Display for MqttString {
574    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
575        write!(f, "{}", self.as_str())
576    }
577}
578
579/// Implementation of `Deref` for `MqttString`
580///
581/// Allows `MqttString` to be used directly as a string slice in many contexts
582/// through automatic dereferencing. This enables method calls like `mqtt_str.len()`
583/// to work directly on the string content.
584impl core::ops::Deref for MqttString {
585    type Target = str;
586
587    fn deref(&self) -> &Self::Target {
588        self.as_str()
589    }
590}
591
592/// Implementation of `Serialize` for `MqttString`
593///
594/// Serializes the string content as a string value.
595/// This is useful for JSON serialization and other serialization formats.
596impl Serialize for MqttString {
597    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
598    where
599        S: Serializer,
600    {
601        self.as_str().serialize(serializer)
602    }
603}
604
605/// Implementation of `PartialEq<str>` for `MqttString`
606///
607/// Allows direct comparison between `MqttString` and `str`.
608impl core::cmp::PartialEq<str> for MqttString {
609    fn eq(&self, other: &str) -> bool {
610        self.as_str() == other
611    }
612}
613
614/// Implementation of `PartialEq<&str>` for `MqttString`
615///
616/// Allows direct comparison between `MqttString` and `&str`.
617impl core::cmp::PartialEq<&str> for MqttString {
618    fn eq(&self, other: &&str) -> bool {
619        self.as_str() == *other
620    }
621}
622
623/// Implementation of `PartialEq<String>` for `MqttString`
624///
625/// Allows direct comparison between `MqttString` and `String`.
626impl core::cmp::PartialEq<String> for MqttString {
627    fn eq(&self, other: &String) -> bool {
628        self.as_str() == other.as_str()
629    }
630}
631
632/// Implementation of `Hash` for `MqttString`
633///
634/// Hashes the string content, allowing `MqttString` to be used in hash-based
635/// collections like `HashMap` and `HashSet`.
636impl core::hash::Hash for MqttString {
637    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
638        self.as_str().hash(state);
639    }
640}
641
642/// Implementation of `Default` for `MqttString`
643///
644/// Creates an empty `MqttString` with zero-length string content.
645/// The internal buffer contains only the 2-byte length prefix (0x00, 0x00).
646impl Default for MqttString {
647    fn default() -> Self {
648        MqttString::new("").unwrap()
649    }
650}
651
652impl core::fmt::Debug for MqttString {
653    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
654        f.debug_struct("MqttString")
655            .field("value", &self.as_str())
656            .finish()
657    }
658}
659
660/// Implementation of `TryFrom<&str>` for `MqttString`
661///
662/// Converts a string slice to `MqttString`. This is a convenient way to
663/// create `MqttString` instances from string literals.
664impl TryFrom<&str> for MqttString {
665    type Error = MqttError;
666
667    fn try_from(s: &str) -> Result<Self, Self::Error> {
668        MqttString::new(s)
669    }
670}
671
672/// Implementation of `TryFrom<String>` for `MqttString`
673///
674/// Converts an owned `String` to `MqttString`. This is a convenient way to
675/// create `MqttString` instances from owned strings.
676impl TryFrom<String> for MqttString {
677    type Error = MqttError;
678
679    fn try_from(s: String) -> Result<Self, Self::Error> {
680        MqttString::new(s)
681    }
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687
688    #[test]
689    fn test_empty_string() {
690        let string = MqttString::new("").unwrap();
691        assert_eq!(string.len(), 0);
692        assert!(string.is_empty());
693        assert_eq!(string.as_str(), "");
694        assert_eq!(string.as_bytes(), &[0x00, 0x00]);
695    }
696
697    #[test]
698    fn test_small_string() {
699        let string = MqttString::new("hello").unwrap();
700        assert_eq!(string.len(), 5);
701        assert!(!string.is_empty());
702        assert_eq!(string.as_str(), "hello");
703        assert_eq!(
704            string.as_bytes(),
705            &[0x00, 0x05, b'h', b'e', b'l', b'l', b'o']
706        );
707    }
708
709    #[cfg(feature = "std")]
710    #[test]
711    fn test_to_buffers() {
712        let data = "buffer test";
713        let string = MqttString::new(data).unwrap();
714        let buffers = string.to_buffers();
715
716        // Both variants should return 1 buffer containing the encoded data
717        assert_eq!(buffers.len(), 1);
718
719        // Verify the buffer contains the complete encoded data
720        let buffer_data: &[u8] = &buffers[0];
721        assert_eq!(buffer_data, string.as_bytes());
722    }
723
724    #[test]
725    fn test_string_variants() {
726        // Test small data (Small variant if SSO is enabled)
727        let small_data = "small";
728        let string = MqttString::new(small_data).unwrap();
729
730        #[cfg(any(
731            feature = "sso-min-32bit",
732            feature = "sso-min-64bit",
733            feature = "sso-lv10",
734            feature = "sso-lv20"
735        ))]
736        assert!(matches!(string, MqttString::Small(_)));
737
738        #[cfg(not(any(
739            feature = "sso-min-32bit",
740            feature = "sso-min-64bit",
741            feature = "sso-lv10",
742            feature = "sso-lv20"
743        )))]
744        assert!(matches!(string, MqttString::Large(_)));
745
746        // Test medium-size data that fits in sso-lv20 but not smaller SSO buffers
747        let medium_data = "This is a medium-size string that is longer than small SSO buffers but fits in the largest one"; // ~90 chars
748        let string = MqttString::new(medium_data).unwrap();
749
750        // With sso-lv20 (48 bytes), this should be Large (exceeds 48 bytes)
751        // With smaller SSO features, this should also be Large
752        assert!(matches!(string, MqttString::Large(_)));
753
754        // Test data that should always be Large variant (larger than largest SSO buffer)
755        let very_large_data = "This is a very long string that exceeds even the largest SSO buffer size to ensure it's always stored in the Large variant";
756        let string = MqttString::new(very_large_data).unwrap();
757        assert!(matches!(string, MqttString::Large(_)));
758    }
759}