mqtt_protocol_core/mqtt/packet/sub_entry.rs
1use alloc::string::ToString;
2
3/**
4 * MIT License
5 *
6 * Copyright (c) 2025 Takatoshi Kondo
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26use crate::mqtt::packet::MqttString;
27use crate::mqtt::packet::Qos;
28use crate::mqtt::packet::RetainHandling;
29use crate::mqtt::result_code::MqttError;
30use alloc::{string::String, vec::Vec};
31use core::fmt;
32use serde::ser::{SerializeStruct, Serializer};
33use serde::Serialize;
34#[cfg(feature = "std")]
35use std::io::IoSlice;
36
37/// MQTT Subscription Options
38///
39/// Represents the subscription options byte used in SUBSCRIBE packets as defined
40/// in MQTT v5.0 specification. This single byte contains multiple bit fields that
41/// control various aspects of subscription behavior including QoS level, retain
42/// handling, and MQTT v5.0 specific flags.
43///
44/// # Bit Layout
45///
46/// The subscription options byte is structured as follows:
47/// ```text
48/// Bit: 7 6 5 4 3 2 1 0
49/// [Reserved] [RH] [RAP][NL][QoS]
50/// ```
51///
52/// Where:
53/// - **Bits 0-1**: QoS level (0, 1, or 2)
54/// - **Bit 2**: No Local flag (NL)
55/// - **Bit 3**: Retain As Published flag (RAP)
56/// - **Bits 4-5**: Retain Handling option (RH)
57/// - **Bits 6-7**: Reserved (must be 0)
58///
59/// # Examples
60///
61/// ```ignore
62/// use mqtt_protocol_core::mqtt;
63///
64/// // Create default subscription options (QoS 0, all flags false)
65/// let opts = mqtt::packet::SubOpts::new();
66///
67/// // Configure specific options
68/// let opts = mqtt::packet::SubOpts::new()
69/// .set_qos(mqtt::packet::Qos::AtLeastOnce)
70/// .set_nl(true) // No Local flag
71/// .set_rap(true) // Retain As Published flag
72/// .set_rh(mqtt::packet::RetainHandling::DoNotSendRetained);
73///
74/// // Parse from byte value
75/// let opts = mqtt::packet::SubOpts::from_u8(0x25).unwrap();
76/// ```
77#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
78pub struct SubOpts {
79 /// Single byte containing all subscription option flags
80 sub_opts_buf: [u8; 1],
81}
82
83impl SubOpts {
84 /// Create new subscription options with default values
85 ///
86 /// Creates a `SubOpts` instance with all options set to their default values:
87 /// - QoS: AtMostOnce (0)
88 /// - No Local flag: false
89 /// - Retain As Published flag: false
90 /// - Retain Handling: SendRetained (0)
91 ///
92 /// # Returns
93 ///
94 /// A new `SubOpts` instance with default settings
95 ///
96 /// # Examples
97 ///
98 /// ```ignore
99 /// use mqtt_protocol_core::mqtt;
100 ///
101 /// let opts = mqtt::packet::SubOpts::new();
102 /// assert_eq!(opts.qos(), mqtt::packet::Qos::AtMostOnce);
103 /// assert_eq!(opts.nl(), false);
104 /// assert_eq!(opts.rap(), false);
105 /// ```
106 pub fn new() -> Self {
107 Self { sub_opts_buf: [0] }
108 }
109
110 /// Create subscription options from a byte value
111 ///
112 /// Parses a subscription options byte and validates that all fields contain
113 /// valid values according to the MQTT v5.0 specification. This method performs
114 /// comprehensive validation to ensure protocol compliance.
115 ///
116 /// # Parameters
117 ///
118 /// * `value` - The subscription options byte to parse
119 ///
120 /// # Returns
121 ///
122 /// * `Ok(SubOpts)` - Successfully parsed and validated subscription options
123 /// * `Err(MqttError::MalformedPacket)` - If any field contains invalid values
124 ///
125 /// # Validation Rules
126 ///
127 /// 1. Reserved bits (6-7) must be 0
128 /// 2. QoS value (bits 0-1) must be 0, 1, or 2
129 /// 3. Retain Handling value (bits 4-5) must be 0, 1, or 2
130 ///
131 /// # Examples
132 ///
133 /// ```ignore
134 /// use mqtt_protocol_core::mqtt;
135 ///
136 /// // Valid subscription options byte
137 /// let opts = mqtt::packet::SubOpts::from_u8(0x25).unwrap();
138 ///
139 /// // Invalid: reserved bits set
140 /// assert!(mqtt::packet::SubOpts::from_u8(0xC0).is_err());
141 ///
142 /// // Invalid: QoS value 3
143 /// assert!(mqtt::packet::SubOpts::from_u8(0x03).is_err());
144 /// ```
145 pub fn from_u8(value: u8) -> Result<Self, MqttError> {
146 // 1. Error if reserved bits (bits 6-7) are not 0
147 if (value & 0b1100_0000) != 0 {
148 return Err(MqttError::MalformedPacket);
149 }
150
151 // 2. Error if QoS is not 0, 1, or 2
152 let qos_value = value & 0b0000_0011;
153 if qos_value > 2 {
154 return Err(MqttError::MalformedPacket);
155 }
156
157 // 3. Error if Retain Handling is not 0, 1, or 2
158 let rh_value = (value & 0b0011_0000) >> 4;
159 if rh_value > 2 {
160 return Err(MqttError::MalformedPacket);
161 }
162
163 // All validations passed, return SubOpts instance
164 Ok(Self {
165 sub_opts_buf: [value],
166 })
167 }
168
169 /// Get the QoS level from subscription options
170 ///
171 /// Extracts and returns the Quality of Service level from bits 0-1
172 /// of the subscription options byte. The QoS level determines the
173 /// delivery guarantee for messages matching this subscription.
174 ///
175 /// # Returns
176 ///
177 /// The QoS level as a `Qos` enum value:
178 /// - `Qos::AtMostOnce` for value 0
179 /// - `Qos::AtLeastOnce` for value 1
180 /// - `Qos::ExactlyOnce` for value 2
181 ///
182 /// # Examples
183 ///
184 /// ```ignore
185 /// use mqtt_protocol_core::mqtt;
186 ///
187 /// let opts = mqtt::packet::SubOpts::new().set_qos(mqtt::packet::Qos::AtLeastOnce);
188 /// assert_eq!(opts.qos(), mqtt::packet::Qos::AtLeastOnce);
189 /// ```
190 pub fn qos(&self) -> Qos {
191 // Extract bits 0-1 value
192 let qos_value = self.sub_opts_buf[0] & 0b0000_0011;
193
194 // Safe conversion (only uses values 0, 1, 2)
195 match qos_value {
196 0 => Qos::AtMostOnce,
197 1 => Qos::AtLeastOnce,
198 2 => Qos::ExactlyOnce,
199 _ => unreachable!("Invalid QoS value: {}, this should never happen", qos_value),
200 }
201 }
202
203 /// Set the QoS level in subscription options
204 ///
205 /// Updates bits 0-1 of the subscription options byte with the specified
206 /// QoS level. This method uses a builder pattern, consuming and returning
207 /// the `SubOpts` instance to allow method chaining.
208 ///
209 /// # Parameters
210 ///
211 /// * `qos` - The QoS level to set
212 ///
213 /// # Returns
214 ///
215 /// The updated `SubOpts` instance with the new QoS level
216 ///
217 /// # Examples
218 ///
219 /// ```ignore
220 /// use mqtt_protocol_core::mqtt;
221 ///
222 /// let opts = mqtt::packet::SubOpts::new()
223 /// .set_qos(mqtt::packet::Qos::ExactlyOnce);
224 /// assert_eq!(opts.qos(), mqtt::packet::Qos::ExactlyOnce);
225 /// ```
226 pub fn set_qos(mut self, qos: Qos) -> Self {
227 self.sub_opts_buf[0] &= 0b1111_1100;
228 self.sub_opts_buf[0] |= qos as u8;
229 self
230 }
231
232 /// Get the No Local flag from subscription options
233 ///
234 /// Extracts the No Local flag from bit 2 of the subscription options byte.
235 /// When set to true, messages published by this client will not be forwarded
236 /// back to it, even if it has a matching subscription.
237 ///
238 /// This flag is useful for preventing message loops in scenarios where
239 /// a client both publishes and subscribes to the same topics.
240 ///
241 /// # Returns
242 ///
243 /// `true` if the No Local flag is set, `false` otherwise
244 ///
245 /// # Examples
246 ///
247 /// ```ignore
248 /// use mqtt_protocol_core::mqtt;
249 ///
250 /// let opts = mqtt::packet::SubOpts::new().set_nl(true);
251 /// assert_eq!(opts.nl(), true);
252 /// ```
253 pub fn nl(&self) -> bool {
254 (self.sub_opts_buf[0] & 0b0000_0100) != 0
255 }
256
257 /// Set the No Local flag in subscription options
258 ///
259 /// Updates bit 2 of the subscription options byte with the specified
260 /// No Local flag value. This method uses a builder pattern, consuming
261 /// and returning the `SubOpts` instance to allow method chaining.
262 ///
263 /// # Parameters
264 ///
265 /// * `nl` - The No Local flag value to set
266 ///
267 /// # Returns
268 ///
269 /// The updated `SubOpts` instance with the new No Local flag
270 ///
271 /// # Examples
272 ///
273 /// ```ignore
274 /// use mqtt_protocol_core::mqtt;
275 ///
276 /// // Enable No Local to prevent message loops
277 /// let opts = mqtt::packet::SubOpts::new().set_nl(true);
278 /// assert_eq!(opts.nl(), true);
279 /// ```
280 pub fn set_nl(mut self, nl: bool) -> Self {
281 if nl {
282 self.sub_opts_buf[0] |= 0b0000_0100; // Set bit 2
283 } else {
284 self.sub_opts_buf[0] &= !0b0000_0100; // Clear bit 2
285 }
286 self
287 }
288
289 /// Get the Retain As Published flag from subscription options
290 ///
291 /// Extracts the Retain As Published flag from bit 3 of the subscription
292 /// options byte. When set to true, messages forwarded to this subscription
293 /// will keep their original RETAIN flag value. When false, forwarded
294 /// messages will have their RETAIN flag set to 0.
295 ///
296 /// This flag affects how the broker handles the RETAIN flag when forwarding
297 /// messages to this specific subscription.
298 ///
299 /// # Returns
300 ///
301 /// `true` if the Retain As Published flag is set, `false` otherwise
302 ///
303 /// # Examples
304 ///
305 /// ```ignore
306 /// use mqtt_protocol_core::mqtt;
307 ///
308 /// let opts = mqtt::packet::SubOpts::new().set_rap(true);
309 /// assert_eq!(opts.rap(), true);
310 /// ```
311 pub fn rap(&self) -> bool {
312 (self.sub_opts_buf[0] & 0b0000_1000) != 0
313 }
314
315 /// Set the Retain As Published flag in subscription options
316 ///
317 /// Updates bit 3 of the subscription options byte with the specified
318 /// Retain As Published flag value. This method uses a builder pattern,
319 /// consuming and returning the `SubOpts` instance to allow method chaining.
320 ///
321 /// # Parameters
322 ///
323 /// * `rap` - The Retain As Published flag value to set
324 ///
325 /// # Returns
326 ///
327 /// The updated `SubOpts` instance with the new Retain As Published flag
328 ///
329 /// # Examples
330 ///
331 /// ```ignore
332 /// use mqtt_protocol_core::mqtt;
333 ///
334 /// // Preserve original RETAIN flag in forwarded messages
335 /// let opts = mqtt::packet::SubOpts::new().set_rap(true);
336 /// assert_eq!(opts.rap(), true);
337 /// ```
338 pub fn set_rap(mut self, rap: bool) -> Self {
339 if rap {
340 self.sub_opts_buf[0] |= 0b0000_1000; // Set bit 3
341 } else {
342 self.sub_opts_buf[0] &= !0b0000_1000; // Clear bit 3
343 }
344 self
345 }
346
347 /// Get the Retain Handling option from subscription options
348 ///
349 /// Extracts and returns the Retain Handling option from bits 4-5
350 /// of the subscription options byte. This option controls how retained
351 /// messages are handled when the subscription is established.
352 ///
353 /// # Returns
354 ///
355 /// The retain handling option as a `RetainHandling` enum value:
356 /// - `RetainHandling::SendRetained` for value 0
357 /// - `RetainHandling::SendRetainedIfNotExists` for value 1
358 /// - `RetainHandling::DoNotSendRetained` for value 2
359 ///
360 /// # Examples
361 ///
362 /// ```ignore
363 /// use mqtt_protocol_core::mqtt;
364 ///
365 /// let opts = mqtt::packet::SubOpts::new()
366 /// .set_rh(mqtt::packet::RetainHandling::DoNotSendRetained);
367 /// assert_eq!(opts.rh(), mqtt::packet::RetainHandling::DoNotSendRetained);
368 /// ```
369 pub fn rh(&self) -> RetainHandling {
370 let rh_value = (self.sub_opts_buf[0] & 0b0011_0000) >> 4;
371
372 match rh_value {
373 0 => RetainHandling::SendRetained,
374 1 => RetainHandling::SendRetainedIfNotExists,
375 2 => RetainHandling::DoNotSendRetained,
376 _ => unreachable!(
377 "Invalid RetainHandling value: {}, this should never happen",
378 rh_value
379 ),
380 }
381 }
382
383 /// Set the Retain Handling option in subscription options
384 ///
385 /// Updates bits 4-5 of the subscription options byte with the specified
386 /// retain handling option. This method uses a builder pattern, consuming
387 /// and returning the `SubOpts` instance to allow method chaining.
388 ///
389 /// # Parameters
390 ///
391 /// * `rh` - The retain handling option to set
392 ///
393 /// # Returns
394 ///
395 /// The updated `SubOpts` instance with the new retain handling option
396 ///
397 /// # Examples
398 ///
399 /// ```ignore
400 /// use mqtt_protocol_core::mqtt;
401 ///
402 /// let opts = mqtt::packet::SubOpts::new()
403 /// .set_rh(mqtt::packet::RetainHandling::SendRetainedIfNotExists);
404 /// assert_eq!(opts.rh(), mqtt::packet::RetainHandling::SendRetainedIfNotExists);
405 /// ```
406 pub fn set_rh(mut self, rh: RetainHandling) -> Self {
407 self.sub_opts_buf[0] &= 0b1100_1111;
408 self.sub_opts_buf[0] |= (rh as u8) << 4;
409 self
410 }
411
412 /// Get the raw subscription options byte buffer
413 ///
414 /// Returns a reference to the internal byte buffer containing the
415 /// encoded subscription options. This can be used for direct serialization
416 /// to the MQTT wire format.
417 ///
418 /// # Returns
419 ///
420 /// A reference to the single-byte buffer containing the encoded options
421 ///
422 /// # Examples
423 ///
424 /// ```ignore
425 /// use mqtt_protocol_core::mqtt;
426 ///
427 /// let opts = mqtt::packet::SubOpts::new().set_qos(mqtt::packet::Qos::AtLeastOnce);
428 /// let buffer = opts.to_buffer();
429 /// assert_eq!(buffer[0] & 0x03, 1); // QoS bits should be 01
430 /// ```
431 pub fn to_buffer(&self) -> &[u8; 1] {
432 &self.sub_opts_buf
433 }
434}
435/// Implementation of `Default` for `SubOpts`
436///
437/// Creates subscription options with all default values.
438/// This is equivalent to calling `SubOpts::new()`.
439impl Default for SubOpts {
440 fn default() -> Self {
441 Self::new()
442 }
443}
444
445/// Implementation of `Display` for `SubOpts`
446///
447/// Formats the subscription options as a JSON string for human-readable output.
448/// This is particularly useful for logging and debugging purposes.
449/// If JSON serialization fails, an error message is displayed instead.
450impl fmt::Display for SubOpts {
451 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452 match serde_json::to_string(self) {
453 Ok(json) => write!(f, "{json}"),
454 Err(e) => write!(f, "{{\"error\": \"{e}\"}}"),
455 }
456 }
457}
458
459/// Implementation of `Serialize` for `SubOpts`
460///
461/// Serializes the subscription options to a structured format with individual
462/// fields for each option. This allows the options to be serialized to JSON
463/// format with clear field names and values.
464///
465/// # Serialized Fields
466///
467/// - `qos`: Quality of Service level as a string
468/// - `nl`: No Local flag as a boolean
469/// - `rap`: Retain As Published flag as a boolean
470/// - `rh`: Retain Handling option as a string
471impl Serialize for SubOpts {
472 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
473 where
474 S: Serializer,
475 {
476 // Define field count
477 let mut state = serializer.serialize_struct("SubOpts", 4)?;
478
479 // Serialize each option
480 state.serialize_field("qos", &self.qos().to_string())?;
481 state.serialize_field("nl", &self.nl())?;
482 state.serialize_field("rap", &self.rap())?;
483 state.serialize_field("rh", &self.rh().to_string())?;
484
485 state.end()
486 }
487}
488
489/// MQTT Subscription Entry
490///
491/// Represents a single subscription entry consisting of a topic filter and
492/// subscription options. This structure is used in SUBSCRIBE packets to
493/// specify what topics to subscribe to and how messages should be handled.
494///
495/// Each subscription entry contains:
496/// - A topic filter string that may include wildcards (`+` and `#`)
497/// - Subscription options that control message delivery behavior
498///
499/// # Topic Filter Format
500///
501/// Topic filters follow MQTT specification rules:
502/// - Single-level wildcard: `+` matches any single level
503/// - Multi-level wildcard: `#` matches any number of levels (must be last)
504/// - Example: `home/+/temperature` or `sensors/#`
505///
506/// # Wire Format
507///
508/// In the MQTT wire protocol, each subscription entry is encoded as:
509/// 1. Topic filter as an MQTT string (2-byte length + UTF-8 bytes)
510/// 2. Subscription options as a single byte
511///
512/// # Examples
513///
514/// ```ignore
515/// use mqtt_protocol_core::mqtt;
516///
517/// // Basic subscription with default options
518/// let entry = mqtt::packet::SubEntry::new(
519/// "sensors/temperature",
520/// mqtt::packet::SubOpts::new()
521/// ).unwrap();
522///
523/// // Subscription with custom options
524/// let opts = mqtt::packet::SubOpts::new()
525/// .set_qos(mqtt::packet::Qos::AtLeastOnce)
526/// .set_nl(true);
527/// let entry = mqtt::packet::SubEntry::new("home/+/status", opts).unwrap();
528/// ```
529#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
530pub struct SubEntry {
531 /// The topic filter string for this subscription
532 topic_filter: MqttString,
533 /// The subscription options controlling message delivery
534 sub_opts: SubOpts,
535}
536
537impl SubEntry {
538 /// Create a new subscription entry
539 ///
540 /// Creates a `SubEntry` with the specified topic filter and subscription options.
541 /// The topic filter is validated to ensure it's a valid UTF-8 string and within
542 /// the MQTT size limits (maximum 65,535 bytes).
543 ///
544 /// # Parameters
545 ///
546 /// * `topic_filter` - The topic filter string (may contain wildcards + and #)
547 /// * `sub_opts` - The subscription options controlling message delivery
548 ///
549 /// # Returns
550 ///
551 /// * `Ok(SubEntry)` - Successfully created subscription entry
552 /// * `Err(MqttError::MalformedPacket)` - If topic filter exceeds maximum length
553 ///
554 /// # Examples
555 ///
556 /// ```ignore
557 /// use mqtt_protocol_core::mqtt;
558 ///
559 /// // Basic subscription
560 /// let entry = mqtt::packet::SubEntry::new(
561 /// "sensors/temperature",
562 /// mqtt::packet::SubOpts::new()
563 /// ).unwrap();
564 ///
565 /// // Subscription with wildcards and custom options
566 /// let opts = mqtt::packet::SubOpts::new().set_qos(mqtt::packet::Qos::AtLeastOnce);
567 /// let entry = mqtt::packet::SubEntry::new("home/+/status", opts).unwrap();
568 /// ```
569 pub fn new(topic_filter: impl AsRef<str>, sub_opts: SubOpts) -> Result<Self, MqttError> {
570 let topic_filter = MqttString::new(topic_filter)?;
571 Ok(Self {
572 topic_filter,
573 sub_opts,
574 })
575 }
576
577 /// Get the topic filter as a string slice
578 ///
579 /// Returns the topic filter string for this subscription entry.
580 /// The topic filter may contain MQTT wildcards (`+` for single level,
581 /// `#` for multiple levels).
582 ///
583 /// # Returns
584 ///
585 /// A string slice containing the topic filter
586 ///
587 /// # Examples
588 ///
589 /// ```ignore
590 /// use mqtt_protocol_core::mqtt;
591 ///
592 /// let entry = mqtt::packet::SubEntry::new("sensors/+/temperature",
593 /// mqtt::packet::SubOpts::new()).unwrap();
594 /// assert_eq!(entry.topic_filter(), "sensors/+/temperature");
595 /// ```
596 pub fn topic_filter(&self) -> &str {
597 &self.topic_filter.as_str()
598 }
599
600 /// Get the subscription options
601 ///
602 /// Returns a reference to the subscription options that control
603 /// how messages matching this topic filter should be delivered.
604 ///
605 /// # Returns
606 ///
607 /// A reference to the `SubOpts` containing the subscription options
608 ///
609 /// # Examples
610 ///
611 /// ```ignore
612 /// use mqtt_protocol_core::mqtt;
613 ///
614 /// let opts = mqtt::packet::SubOpts::new().set_qos(mqtt::packet::Qos::AtLeastOnce);
615 /// let entry = mqtt::packet::SubEntry::new("test/topic", opts).unwrap();
616 /// assert_eq!(entry.sub_opts().qos(), mqtt::packet::Qos::AtLeastOnce);
617 /// ```
618 pub fn sub_opts(&self) -> &SubOpts {
619 &self.sub_opts
620 }
621
622 /// Set the topic filter for this subscription entry
623 ///
624 /// Updates the topic filter with a new value. The new topic filter
625 /// is validated to ensure it's valid UTF-8 and within size limits.
626 ///
627 /// # Parameters
628 ///
629 /// * `topic_filter` - The new topic filter string
630 ///
631 /// # Returns
632 ///
633 /// * `Ok(())` - Topic filter updated successfully
634 /// * `Err(MqttError::MalformedPacket)` - If topic filter exceeds maximum length
635 ///
636 /// # Examples
637 ///
638 /// ```ignore
639 /// use mqtt_protocol_core::mqtt;
640 ///
641 /// let mut entry = mqtt::packet::SubEntry::new("old/topic",
642 /// mqtt::packet::SubOpts::new()).unwrap();
643 /// entry.set_topic_filter("new/topic".to_string()).unwrap();
644 /// assert_eq!(entry.topic_filter(), "new/topic");
645 /// ```
646 pub fn set_topic_filter(&mut self, topic_filter: String) -> Result<(), MqttError> {
647 self.topic_filter = MqttString::new(topic_filter)?;
648 Ok(())
649 }
650
651 /// Set the subscription options for this entry
652 ///
653 /// Updates the subscription options that control how messages
654 /// matching this topic filter should be delivered.
655 ///
656 /// # Parameters
657 ///
658 /// * `sub_opts` - The new subscription options
659 ///
660 /// # Examples
661 ///
662 /// ```ignore
663 /// use mqtt_protocol_core::mqtt;
664 ///
665 /// let mut entry = mqtt::packet::SubEntry::new("test/topic",
666 /// mqtt::packet::SubOpts::new()).unwrap();
667 /// let new_opts = mqtt::packet::SubOpts::new().set_qos(mqtt::packet::Qos::ExactlyOnce);
668 /// entry.set_sub_opts(new_opts);
669 /// assert_eq!(entry.sub_opts().qos(), mqtt::packet::Qos::ExactlyOnce);
670 /// ```
671 pub fn set_sub_opts(&mut self, sub_opts: SubOpts) {
672 self.sub_opts = sub_opts;
673 }
674
675 /// Create IoSlice buffers for efficient network I/O
676 ///
677 /// Returns a vector of `IoSlice` objects that can be used for vectored I/O
678 /// operations, allowing zero-copy writes to network sockets. The buffers
679 /// contain the complete wire format representation of this subscription entry.
680 ///
681 /// # Returns
682 ///
683 /// A vector of `IoSlice` buffers containing:
684 /// 1. Topic filter with length prefix
685 /// 2. Subscription options byte
686 ///
687 /// # Examples
688 ///
689 /// ```ignore
690 /// use mqtt_protocol_core::mqtt;
691 ///
692 /// let entry = mqtt::packet::SubEntry::new("test/topic",
693 /// mqtt::packet::SubOpts::new()).unwrap();
694 /// let buffers = entry.to_buffers();
695 /// // Can be used with vectored write operations
696 /// // socket.write_vectored(&buffers)?;
697 /// ```
698 #[cfg(feature = "std")]
699 pub fn to_buffers(&self) -> Vec<IoSlice<'_>> {
700 let mut buffers = self.topic_filter.to_buffers();
701 buffers.push(IoSlice::new(self.sub_opts.to_buffer()));
702 buffers
703 }
704
705 /// Create a continuous buffer containing the complete entry data
706 ///
707 /// Returns a vector containing all subscription entry bytes in a single continuous buffer.
708 /// This method is compatible with no-std environments and provides an alternative
709 /// to [`to_buffers()`] when vectored I/O is not needed.
710 ///
711 /// The returned buffer contains:
712 /// 1. Topic filter with length prefix
713 /// 2. Subscription options byte
714 ///
715 /// # Returns
716 ///
717 /// A vector containing the complete entry data
718 ///
719 /// # Examples
720 ///
721 /// ```ignore
722 /// use mqtt_protocol_core::mqtt;
723 ///
724 /// let entry = mqtt::packet::SubEntry::new("test/topic",
725 /// mqtt::packet::SubOpts::new()).unwrap();
726 /// let buffer = entry.to_continuous_buffer();
727 /// // buffer contains all subscription entry bytes
728 /// ```
729 ///
730 /// [`to_buffers()`]: #method.to_buffers
731 pub fn to_continuous_buffer(&self) -> Vec<u8> {
732 let mut buf = self.topic_filter.to_continuous_buffer();
733 buf.extend_from_slice(self.sub_opts.to_buffer());
734 buf
735 }
736
737 /// Get the total encoded size of this subscription entry
738 ///
739 /// Returns the number of bytes this subscription entry will occupy
740 /// in the MQTT wire format, including the topic filter with its
741 /// length prefix and the subscription options byte.
742 ///
743 /// # Returns
744 ///
745 /// The total size in bytes for the wire format representation
746 ///
747 /// # Examples
748 ///
749 /// ```ignore
750 /// use mqtt_protocol_core::mqtt;
751 ///
752 /// let entry = mqtt::packet::SubEntry::new("test",
753 /// mqtt::packet::SubOpts::new()).unwrap();
754 /// // Size = 2 bytes (length prefix) + 4 bytes ("test") + 1 byte (options) = 7 bytes
755 /// assert_eq!(entry.size(), 7);
756 /// ```
757 pub fn size(&self) -> usize {
758 self.topic_filter.size() + self.sub_opts.to_buffer().len()
759 }
760
761 /// Parse a subscription entry from byte data
762 ///
763 /// Decodes a subscription entry from the MQTT wire format, which consists
764 /// of a topic filter (MQTT string with length prefix) followed by a
765 /// subscription options byte.
766 ///
767 /// # Parameters
768 ///
769 /// * `data` - Byte buffer containing the encoded subscription entry
770 ///
771 /// # Returns
772 ///
773 /// * `Ok((SubEntry, bytes_consumed))` - Successfully parsed entry and number of bytes consumed
774 /// * `Err(MqttError::MalformedPacket)` - If the data is malformed or incomplete
775 ///
776 /// # Wire Format
777 ///
778 /// 1. Topic filter as MQTT string (2-byte length + UTF-8 bytes)
779 /// 2. Subscription options as single byte
780 ///
781 /// # Examples
782 ///
783 /// ```ignore
784 /// use mqtt_protocol_core::mqtt;
785 ///
786 /// // Buffer containing: length=4, "test", options=0x01
787 /// let buffer = &[0x00, 0x04, b't', b'e', b's', b't', 0x01];
788 /// let (entry, consumed) = mqtt::packet::SubEntry::parse(buffer).unwrap();
789 ///
790 /// assert_eq!(entry.topic_filter(), "test");
791 /// assert_eq!(entry.sub_opts().qos(), mqtt::packet::Qos::AtLeastOnce);
792 /// assert_eq!(consumed, 7);
793 /// ```
794 pub fn parse(data: &[u8]) -> Result<(Self, usize), MqttError> {
795 let mut cursor = 0;
796
797 // 1. Parse topic filter
798 let (topic_filter, consumed) = MqttString::decode(&data[cursor..])?;
799 cursor += consumed;
800
801 // 2. Parse subscription options
802 if cursor >= data.len() {
803 return Err(MqttError::MalformedPacket);
804 }
805
806 // Parse subscription options (1 byte)
807 let sub_opts = SubOpts::from_u8(data[cursor])?;
808 cursor += 1;
809
810 Ok((
811 Self {
812 topic_filter,
813 sub_opts,
814 },
815 cursor,
816 ))
817 }
818}
819
820/// Implementation of `Default` for `SubEntry`
821///
822/// Creates a subscription entry with default values:
823/// - Empty topic filter string
824/// - Default subscription options (QoS 0, all flags false)
825///
826/// Note: An empty topic filter is not valid for actual MQTT usage
827/// but provides a default state for initialization purposes.
828impl Default for SubEntry {
829 fn default() -> Self {
830 Self {
831 topic_filter: MqttString::new(String::new()).unwrap(),
832 sub_opts: SubOpts::default(),
833 }
834 }
835}
836
837/// Implementation of `Display` for `SubEntry`
838///
839/// Formats the subscription entry as a JSON string for human-readable output.
840/// This is particularly useful for logging and debugging purposes.
841/// If JSON serialization fails, an error message is displayed instead.
842impl fmt::Display for SubEntry {
843 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844 match serde_json::to_string(self) {
845 Ok(json) => write!(f, "{json}"),
846 Err(e) => write!(f, "{{\"error\": \"{e}\"}}"),
847 }
848 }
849}
850
851/// Implementation of `Serialize` for `SubEntry`
852///
853/// Serializes the subscription entry to a structured format with separate
854/// fields for the topic filter and subscription options. This provides a
855/// clear JSON representation suitable for debugging and logging.
856///
857/// # Serialized Fields
858///
859/// - `topic_filter`: The topic filter string
860/// - `options`: The subscription options as a structured object
861impl Serialize for SubEntry {
862 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
863 where
864 S: Serializer,
865 {
866 // Define field count
867 let mut state = serializer.serialize_struct("SubEntry", 2)?;
868
869 // Serialize topic filter and options
870 state.serialize_field("topic_filter", self.topic_filter())?;
871 state.serialize_field("options", self.sub_opts())?;
872
873 state.end()
874 }
875}