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