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, vec, vec::Vec};
24use serde::{Serialize, Serializer};
25#[cfg(feature = "std")]
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 { core::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 #[cfg(feature = "std")]
271 pub fn to_buffers(&self) -> Vec<IoSlice<'_>> {
272 vec![IoSlice::new(&self.encoded)]
273 }
274
275 /// Create a continuous buffer containing the complete packet data
276 ///
277 /// Returns a vector containing all packet bytes in a single continuous buffer.
278 /// This method is compatible with no-std environments and provides an alternative
279 /// to [`to_buffers()`] when vectored I/O is not needed.
280 ///
281 /// # Returns
282 ///
283 /// A vector containing the complete encoded buffer
284 ///
285 /// # Examples
286 ///
287 /// ```ignore
288 /// use mqtt_protocol_core::mqtt;
289 ///
290 /// let mqtt_str = mqtt::packet::MqttString::new("data").unwrap();
291 /// let buffer = mqtt_str.to_continuous_buffer();
292 /// // buffer contains all packet bytes
293 /// ```
294 ///
295 /// [`to_buffers()`]: #method.to_buffers
296 pub fn to_continuous_buffer(&self) -> Vec<u8> {
297 self.encoded.clone()
298 }
299
300 /// Parse string data from a byte sequence
301 ///
302 /// Decodes MQTT string data from a byte buffer according to the MQTT protocol.
303 /// The buffer must start with a 2-byte length prefix followed by valid UTF-8 bytes.
304 ///
305 /// # Parameters
306 ///
307 /// * `data` - Byte buffer containing the encoded string data
308 ///
309 /// # Returns
310 ///
311 /// * `Ok((MqttString, bytes_consumed))` - Successfully parsed string and number of bytes consumed
312 /// * `Err(MqttError::MalformedPacket)` - If the buffer is too short, malformed, or contains invalid UTF-8
313 ///
314 /// # Examples
315 ///
316 /// ```ignore
317 /// use mqtt_protocol_core::mqtt;
318 ///
319 /// // Buffer: [length_high, length_low, utf8_bytes...]
320 /// let buffer = &[0x00, 0x05, b'h', b'e', b'l', b'l', b'o'];
321 /// let (mqtt_str, consumed) = mqtt::packet::MqttString::decode(buffer).unwrap();
322 ///
323 /// assert_eq!(mqtt_str.as_str(), "hello");
324 /// assert_eq!(consumed, 7);
325 /// ```
326 pub fn decode(data: &[u8]) -> Result<(Self, usize), MqttError> {
327 if data.len() < 2 {
328 return Err(MqttError::MalformedPacket);
329 }
330
331 let string_len = ((data[0] as usize) << 8) | (data[1] as usize);
332 if data.len() < 2 + string_len {
333 return Err(MqttError::MalformedPacket);
334 }
335
336 // Verify UTF-8 validity - return MQTT error on parse failure
337 if core::str::from_utf8(&data[2..2 + string_len]).is_err() {
338 return Err(MqttError::MalformedPacket);
339 }
340
341 // Create encoded buffer
342 let mut encoded = Vec::with_capacity(2 + string_len);
343 encoded.extend_from_slice(&data[0..2 + string_len]);
344
345 Ok((Self { encoded }, 2 + string_len))
346 }
347
348 /// Check if the string contains a specific character
349 ///
350 /// Returns `true` if the string contains the specified character,
351 /// `false` otherwise.
352 ///
353 /// # Parameters
354 ///
355 /// * `c` - The character to search for
356 ///
357 /// # Returns
358 ///
359 /// `true` if the character is found, `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.contains('w'));
368 /// assert!(!mqtt_str.contains('x'));
369 /// ```
370 pub fn contains(&self, c: char) -> bool {
371 self.as_str().contains(c)
372 }
373
374 /// Check if the string starts with the specified prefix
375 ///
376 /// Returns `true` if the string starts with the given prefix,
377 /// `false` otherwise.
378 ///
379 /// # Parameters
380 ///
381 /// * `prefix` - The prefix string to check for
382 ///
383 /// # Returns
384 ///
385 /// `true` if the string starts with the prefix, `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.starts_with("hello"));
394 /// assert!(!mqtt_str.starts_with("world"));
395 /// ```
396 pub fn starts_with(&self, prefix: &str) -> bool {
397 self.as_str().starts_with(prefix)
398 }
399
400 /// Check if the string ends with the specified suffix
401 ///
402 /// Returns `true` if the string ends with the given suffix,
403 /// `false` otherwise.
404 ///
405 /// # Parameters
406 ///
407 /// * `suffix` - The suffix string to check for
408 ///
409 /// # Returns
410 ///
411 /// `true` if the string ends with the suffix, `false` otherwise
412 ///
413 /// # Examples
414 ///
415 /// ```ignore
416 /// use mqtt_protocol_core::mqtt;
417 ///
418 /// let mqtt_str = mqtt::packet::MqttString::new("hello world").unwrap();
419 /// assert!(mqtt_str.ends_with("world"));
420 /// assert!(!mqtt_str.ends_with("hello"));
421 /// ```
422 pub fn ends_with(&self, suffix: &str) -> bool {
423 self.as_str().ends_with(suffix)
424 }
425}
426
427/// Implementation of `AsRef<str>` for `MqttString`
428///
429/// Returns the string content when the `MqttString` is used in contexts
430/// expecting a string slice reference.
431impl AsRef<str> for MqttString {
432 fn as_ref(&self) -> &str {
433 self.as_str()
434 }
435}
436
437/// Implementation of `Display` for `MqttString`
438///
439/// Formats the string content for display purposes.
440/// This allows `MqttString` to be used with `println!`, `format!`, etc.
441impl core::fmt::Display for MqttString {
442 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
443 write!(f, "{}", self.as_str())
444 }
445}
446
447/// Implementation of `Deref` for `MqttString`
448///
449/// Allows `MqttString` to be used directly as a string slice in many contexts
450/// through automatic dereferencing. This enables method calls like `mqtt_str.len()`
451/// to work directly on the string content.
452impl core::ops::Deref for MqttString {
453 type Target = str;
454
455 fn deref(&self) -> &Self::Target {
456 self.as_str()
457 }
458}
459
460/// Implementation of `Serialize` for `MqttString`
461///
462/// Serializes the string content as a string value.
463/// This is useful for JSON serialization and other serialization formats.
464impl Serialize for MqttString {
465 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
466 where
467 S: Serializer,
468 {
469 self.as_str().serialize(serializer)
470 }
471}
472
473/// Implementation of `PartialEq<str>` for `MqttString`
474///
475/// Allows direct comparison between `MqttString` and `str`.
476impl core::cmp::PartialEq<str> for MqttString {
477 fn eq(&self, other: &str) -> bool {
478 self.as_str() == other
479 }
480}
481
482/// Implementation of `PartialEq<&str>` for `MqttString`
483///
484/// Allows direct comparison between `MqttString` and `&str`.
485impl core::cmp::PartialEq<&str> for MqttString {
486 fn eq(&self, other: &&str) -> bool {
487 self.as_str() == *other
488 }
489}
490
491/// Implementation of `PartialEq<String>` for `MqttString`
492///
493/// Allows direct comparison between `MqttString` and `String`.
494impl core::cmp::PartialEq<String> for MqttString {
495 fn eq(&self, other: &String) -> bool {
496 self.as_str() == other.as_str()
497 }
498}
499
500/// Implementation of `Hash` for `MqttString`
501///
502/// Hashes the string content, allowing `MqttString` to be used in hash-based
503/// collections like `HashMap` and `HashSet`.
504impl core::hash::Hash for MqttString {
505 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
506 self.as_str().hash(state);
507 }
508}
509
510/// Implementation of `Default` for `MqttString`
511///
512/// Creates an empty `MqttString` with zero-length string content.
513/// The internal buffer contains only the 2-byte length prefix (0x00, 0x00).
514impl Default for MqttString {
515 fn default() -> Self {
516 MqttString {
517 encoded: vec![0x00, 0x00],
518 }
519 }
520}
521
522/// Implementation of `TryFrom<&str>` for `MqttString`
523///
524/// Converts a string slice to `MqttString`. This is a convenient way to
525/// create `MqttString` instances from string literals.
526impl TryFrom<&str> for MqttString {
527 type Error = MqttError;
528
529 fn try_from(s: &str) -> Result<Self, Self::Error> {
530 MqttString::new(s)
531 }
532}
533
534/// Implementation of `TryFrom<String>` for `MqttString`
535///
536/// Converts an owned `String` to `MqttString`. This is a convenient way to
537/// create `MqttString` instances from owned strings.
538impl TryFrom<String> for MqttString {
539 type Error = MqttError;
540
541 fn try_from(s: String) -> Result<Self, Self::Error> {
542 MqttString::new(s)
543 }
544}