mqute_codec/protocol/v5/suback.rs
1//! # Subscribe Acknowledgment (SubAck) Packet - MQTT v5
2//!
3//! This module implements the MQTT v5 `SubAck` packet, which is sent by the server
4//! to acknowledge receipt and processing of a `Subscribe` packet. The `SubAck` packet
5//! contains return codes indicating the QoS level granted for each subscription.
6
7use crate::Error;
8use crate::codec::{Decode, Encode, RawPacket};
9use crate::protocol::v5::reason::ReasonCode;
10use crate::protocol::v5::util::{ack_properties, ack_properties_frame_impl, id_header};
11use crate::protocol::{Codes, FixedHeader, PacketType};
12use bytes::{Buf, Bytes, BytesMut};
13
14// Defines properties specific to `SubAck` packets
15ack_properties!(SubAckProperties);
16
17// Implements the PropertyFrame trait for SubAckProperties
18ack_properties_frame_impl!(SubAckProperties);
19
20/// Validates reason codes for `SubAck` packets
21///
22/// MQTT v5 specifies the following valid reason codes for `SubAck`:
23/// - 0x00 (Granted QoS 0)
24/// - 0x01 (Granted QoS 1)
25/// - 0x02 (Granted QoS 2)
26/// - 0x80 (Unspecified error)
27/// - 0x83 (Implementation specific error)
28/// - 0x87 (Not authorized)
29/// - 0x8F (Topic Filter invalid)
30/// - 0x91 (Packet Identifier in use)
31/// - 0x97 (Quota exceeded)
32/// - 0x9E (Shared Subscriptions not supported)
33/// - 0xA1 (Subscription Identifiers not supported)
34/// - 0xA2 (Wildcard Subscriptions not supported)
35fn validate_suback_reason_code(code: ReasonCode) -> bool {
36 matches!(
37 code.into(),
38 0 | 1 | 2 | 128 | 131 | 135 | 143 | 145 | 151 | 158 | 161 | 162
39 )
40}
41
42// Internal header structure for `SubAck` packets
43id_header!(SubAckHeader, SubAckProperties);
44
45/// Represents an MQTT v5 `SubAck` packet
46///
47/// The `SubAck` packet is sent by the server to acknowledge receipt and processing
48/// of a `Subscribe` packet. It contains:
49/// - Packet Identifier matching the `Subscribe` packet
50/// - List of return codes indicating granted QoS levels or errors
51/// - Optional properties (v5 only)
52///
53/// # Example
54///
55/// ```rust
56/// use mqute_codec::protocol::Codes;
57/// use mqute_codec::protocol::v5::{SubAck, ReasonCode};
58///
59/// // Successful subscription with different QoS levels
60/// let suback = SubAck::new(
61/// 1234,
62/// None,
63/// vec![
64/// ReasonCode::GrantedQos0,
65/// ReasonCode::GrantedQos2,
66/// ReasonCode::GrantedQos1
67/// ],
68/// );
69///
70/// assert_eq!(suback.packet_id(), 1234u16);
71/// let codes = Codes::new(vec![
72/// ReasonCode::GrantedQos0,
73/// ReasonCode::GrantedQos2,
74/// ReasonCode::GrantedQos1
75/// ]);
76/// assert_eq!(suback.codes(), codes);
77/// ```
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct SubAck {
80 header: SubAckHeader,
81 codes: Codes<ReasonCode>,
82}
83
84impl SubAck {
85 /// Creates a new `SubAck` packet
86 ///
87 /// # Panics
88 /// - If no reason codes are provided
89 /// - If any reason code is invalid for `SubAck`
90 pub fn new<T>(packet_id: u16, properties: Option<SubAckProperties>, codes: T) -> Self
91 where
92 T: IntoIterator<Item = ReasonCode>,
93 {
94 let codes: Vec<ReasonCode> = codes.into_iter().collect();
95
96 if codes.is_empty() {
97 panic!("At least one reason code is required");
98 }
99
100 if !codes.iter().all(|&code| validate_suback_reason_code(code)) {
101 panic!("Invalid reason code");
102 }
103
104 let header = SubAckHeader::new(packet_id, properties);
105
106 SubAck {
107 header,
108 codes: codes.into(),
109 }
110 }
111
112 /// Returns the packet identifier
113 pub fn packet_id(&self) -> u16 {
114 self.header.packet_id
115 }
116
117 /// Returns the list of reason codes
118 pub fn codes(&self) -> Codes<ReasonCode> {
119 self.codes.clone()
120 }
121
122 /// Returns a copy of the properties (if any)
123 pub fn properties(&self) -> Option<SubAckProperties> {
124 self.header.properties.clone()
125 }
126}
127
128impl Encode for SubAck {
129 /// Encodes the `SubAck` packet into a byte buffer
130 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
131 let header = FixedHeader::new(PacketType::SubAck, self.payload_len());
132 header.encode(buf)?;
133
134 self.header.encode(buf)?;
135 self.codes.encode(buf);
136 Ok(())
137 }
138
139 /// Calculates the total packet length
140 fn payload_len(&self) -> usize {
141 self.header.encoded_len() + self.codes.len()
142 }
143}
144
145impl Decode for SubAck {
146 /// Decodes a `SubAck` packet from raw bytes
147 fn decode(mut packet: RawPacket) -> Result<Self, Error> {
148 if packet.header.packet_type() != PacketType::SubAck || !packet.header.flags().is_default()
149 {
150 return Err(Error::MalformedPacket);
151 }
152
153 let header = SubAckHeader::decode(&mut packet.payload)?;
154 let mut codes: Vec<ReasonCode> = Codes::decode(&mut packet.payload)?.into();
155
156 // Convert legacy Success (0x00) to GrantedQos0 for consistency
157 for code in codes.iter_mut() {
158 if !validate_suback_reason_code(*code) {
159 return Err(Error::InvalidReasonCode((*code).into()));
160 }
161 if (*code) == ReasonCode::Success {
162 *code = ReasonCode::GrantedQos0;
163 }
164 }
165
166 Ok(SubAck {
167 header,
168 codes: codes.into(),
169 })
170 }
171}