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}