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}