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