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