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}