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}