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}