mqtt_protocol_core/mqtt/packet/
sub_entry.rs

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