mqtt5_protocol/packet/
suback.rs

1use crate::error::{MqttError, Result};
2use crate::packet::{FixedHeader, MqttPacket, PacketType};
3use crate::prelude::{format, String, ToString, Vec};
4use crate::protocol::v5::properties::Properties;
5use crate::types::ProtocolVersion;
6use crate::QoS;
7use bytes::{Buf, BufMut};
8
9/// MQTT SUBACK packet
10#[derive(Debug, Clone)]
11pub struct SubAckPacket {
12    /// Packet identifier
13    pub packet_id: u16,
14    /// Reason codes for each subscription
15    pub reason_codes: Vec<SubAckReasonCode>,
16    /// SUBACK properties (v5.0 only)
17    pub properties: Properties,
18    /// Protocol version (4 = v3.1.1, 5 = v5.0)
19    pub protocol_version: u8,
20}
21
22/// SUBACK reason codes
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u8)]
25pub enum SubAckReasonCode {
26    /// Maximum `QoS` 0
27    GrantedQoS0 = 0x00,
28    /// Maximum `QoS` 1
29    GrantedQoS1 = 0x01,
30    /// Maximum `QoS` 2
31    GrantedQoS2 = 0x02,
32    /// Unspecified error
33    UnspecifiedError = 0x80,
34    /// Implementation specific error
35    ImplementationSpecificError = 0x83,
36    /// Not authorized
37    NotAuthorized = 0x87,
38    /// Topic filter invalid
39    TopicFilterInvalid = 0x8F,
40    /// Packet identifier in use
41    PacketIdentifierInUse = 0x91,
42    /// Quota exceeded
43    QuotaExceeded = 0x97,
44    /// Shared subscriptions not supported
45    SharedSubscriptionsNotSupported = 0x9E,
46    /// Subscription identifiers not supported
47    SubscriptionIdentifiersNotSupported = 0xA1,
48    /// Wildcard subscriptions not supported
49    WildcardSubscriptionsNotSupported = 0xA2,
50}
51
52impl SubAckReasonCode {
53    /// Creates a reason code from a granted `QoS` level
54    #[must_use]
55    pub fn from_qos(qos: QoS) -> Self {
56        match qos {
57            QoS::AtMostOnce => Self::GrantedQoS0,
58            QoS::AtLeastOnce => Self::GrantedQoS1,
59            QoS::ExactlyOnce => Self::GrantedQoS2,
60        }
61    }
62
63    /// Converts a u8 to a `SubAckReasonCode`
64    #[must_use]
65    pub fn from_u8(value: u8) -> Option<Self> {
66        match value {
67            0x00 => Some(Self::GrantedQoS0),
68            0x01 => Some(Self::GrantedQoS1),
69            0x02 => Some(Self::GrantedQoS2),
70            0x80 => Some(Self::UnspecifiedError),
71            0x83 => Some(Self::ImplementationSpecificError),
72            0x87 => Some(Self::NotAuthorized),
73            0x8F => Some(Self::TopicFilterInvalid),
74            0x91 => Some(Self::PacketIdentifierInUse),
75            0x97 => Some(Self::QuotaExceeded),
76            0x9E => Some(Self::SharedSubscriptionsNotSupported),
77            0xA1 => Some(Self::SubscriptionIdentifiersNotSupported),
78            0xA2 => Some(Self::WildcardSubscriptionsNotSupported),
79            _ => None,
80        }
81    }
82
83    /// Returns true if this is a success code
84    #[must_use]
85    pub fn is_success(&self) -> bool {
86        matches!(
87            self,
88            Self::GrantedQoS0 | Self::GrantedQoS1 | Self::GrantedQoS2
89        )
90    }
91
92    /// Returns the granted `QoS` level if this is a success code
93    #[must_use]
94    pub fn granted_qos(&self) -> Option<QoS> {
95        match self {
96            Self::GrantedQoS0 => Some(QoS::AtMostOnce),
97            Self::GrantedQoS1 => Some(QoS::AtLeastOnce),
98            Self::GrantedQoS2 => Some(QoS::ExactlyOnce),
99            _ => None,
100        }
101    }
102}
103
104impl SubAckPacket {
105    /// Creates a new SUBACK packet (v5.0)
106    #[must_use]
107    pub fn new(packet_id: u16) -> Self {
108        Self {
109            packet_id,
110            reason_codes: Vec::new(),
111            properties: Properties::default(),
112            protocol_version: 5,
113        }
114    }
115
116    /// Creates a new SUBACK packet for v3.1.1
117    #[must_use]
118    pub fn new_v311(packet_id: u16) -> Self {
119        Self {
120            packet_id,
121            reason_codes: Vec::new(),
122            properties: Properties::default(),
123            protocol_version: 4,
124        }
125    }
126
127    fn reason_code_to_v311(code: SubAckReasonCode) -> u8 {
128        match code {
129            SubAckReasonCode::GrantedQoS0 => 0x00,
130            SubAckReasonCode::GrantedQoS1 => 0x01,
131            SubAckReasonCode::GrantedQoS2 => 0x02,
132            _ => 0x80,
133        }
134    }
135
136    /// Adds a reason code
137    #[must_use]
138    pub fn add_reason_code(mut self, code: SubAckReasonCode) -> Self {
139        self.reason_codes.push(code);
140        self
141    }
142
143    /// Adds a reason code for a granted `QoS`
144    #[must_use]
145    pub fn add_granted_qos(mut self, qos: QoS) -> Self {
146        self.reason_codes.push(SubAckReasonCode::from_qos(qos));
147        self
148    }
149
150    /// Sets the reason string
151    #[must_use]
152    pub fn with_reason_string(mut self, reason: String) -> Self {
153        self.properties.set_reason_string(reason);
154        self
155    }
156
157    /// Adds a user property
158    #[must_use]
159    pub fn with_user_property(mut self, key: String, value: String) -> Self {
160        self.properties.add_user_property(key, value);
161        self
162    }
163}
164
165impl MqttPacket for SubAckPacket {
166    fn packet_type(&self) -> PacketType {
167        PacketType::SubAck
168    }
169
170    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
171        buf.put_u16(self.packet_id);
172
173        if self.protocol_version == 5 {
174            self.properties.encode(buf)?;
175        }
176
177        if self.reason_codes.is_empty() {
178            return Err(MqttError::MalformedPacket(
179                "SUBACK packet must contain at least one reason code".to_string(),
180            ));
181        }
182
183        if self.protocol_version == 5 {
184            for code in &self.reason_codes {
185                buf.put_u8(*code as u8);
186            }
187        } else {
188            for code in &self.reason_codes {
189                buf.put_u8(Self::reason_code_to_v311(*code));
190            }
191        }
192
193        Ok(())
194    }
195
196    fn decode_body<B: Buf>(buf: &mut B, fixed_header: &FixedHeader) -> Result<Self> {
197        Self::decode_body_with_version(buf, fixed_header, 5)
198    }
199}
200
201impl SubAckPacket {
202    /// Decodes the packet body with a specific protocol version
203    ///
204    /// # Errors
205    ///
206    /// Returns an error if decoding fails
207    pub fn decode_body_with_version<B: Buf>(
208        buf: &mut B,
209        _fixed_header: &FixedHeader,
210        protocol_version: u8,
211    ) -> Result<Self> {
212        ProtocolVersion::try_from(protocol_version)
213            .map_err(|()| MqttError::UnsupportedProtocolVersion)?;
214
215        if buf.remaining() < 2 {
216            return Err(MqttError::MalformedPacket(
217                "SUBACK missing packet identifier".to_string(),
218            ));
219        }
220        let packet_id = buf.get_u16();
221
222        let properties = if protocol_version == 5 {
223            Properties::decode(buf)?
224        } else {
225            Properties::default()
226        };
227
228        let mut reason_codes = Vec::new();
229
230        if !buf.has_remaining() {
231            return Err(MqttError::MalformedPacket(
232                "SUBACK packet must contain at least one reason code".to_string(),
233            ));
234        }
235
236        while buf.has_remaining() {
237            let code_byte = buf.get_u8();
238            let code = SubAckReasonCode::from_u8(code_byte).ok_or_else(|| {
239                MqttError::MalformedPacket(format!("Invalid SUBACK reason code: 0x{code_byte:02X}"))
240            })?;
241            reason_codes.push(code);
242        }
243
244        Ok(Self {
245            packet_id,
246            reason_codes,
247            properties,
248            protocol_version,
249        })
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use crate::protocol::v5::properties::PropertyId;
257    use bytes::BytesMut;
258
259    #[test]
260    fn test_suback_reason_code_from_qos() {
261        assert_eq!(
262            SubAckReasonCode::from_qos(QoS::AtMostOnce),
263            SubAckReasonCode::GrantedQoS0
264        );
265        assert_eq!(
266            SubAckReasonCode::from_qos(QoS::AtLeastOnce),
267            SubAckReasonCode::GrantedQoS1
268        );
269        assert_eq!(
270            SubAckReasonCode::from_qos(QoS::ExactlyOnce),
271            SubAckReasonCode::GrantedQoS2
272        );
273    }
274
275    #[test]
276    fn test_suback_reason_code_is_success() {
277        assert!(SubAckReasonCode::GrantedQoS0.is_success());
278        assert!(SubAckReasonCode::GrantedQoS1.is_success());
279        assert!(SubAckReasonCode::GrantedQoS2.is_success());
280        assert!(!SubAckReasonCode::NotAuthorized.is_success());
281        assert!(!SubAckReasonCode::TopicFilterInvalid.is_success());
282    }
283
284    #[test]
285    fn test_suback_basic() {
286        let packet = SubAckPacket::new(123)
287            .add_granted_qos(QoS::AtLeastOnce)
288            .add_granted_qos(QoS::ExactlyOnce)
289            .add_reason_code(SubAckReasonCode::NotAuthorized);
290
291        assert_eq!(packet.packet_id, 123);
292        assert_eq!(packet.reason_codes.len(), 3);
293        assert_eq!(packet.reason_codes[0], SubAckReasonCode::GrantedQoS1);
294        assert_eq!(packet.reason_codes[1], SubAckReasonCode::GrantedQoS2);
295        assert_eq!(packet.reason_codes[2], SubAckReasonCode::NotAuthorized);
296    }
297
298    #[test]
299    fn test_suback_encode_decode() {
300        let packet = SubAckPacket::new(789)
301            .add_granted_qos(QoS::AtMostOnce)
302            .add_granted_qos(QoS::AtLeastOnce)
303            .add_reason_code(SubAckReasonCode::TopicFilterInvalid)
304            .with_reason_string("Invalid wildcard usage".to_string());
305
306        let mut buf = BytesMut::new();
307        packet.encode(&mut buf).unwrap();
308
309        let fixed_header = FixedHeader::decode(&mut buf).unwrap();
310        assert_eq!(fixed_header.packet_type, PacketType::SubAck);
311
312        let decoded = SubAckPacket::decode_body(&mut buf, &fixed_header).unwrap();
313        assert_eq!(decoded.packet_id, 789);
314        assert_eq!(decoded.reason_codes.len(), 3);
315        assert_eq!(decoded.reason_codes[0], SubAckReasonCode::GrantedQoS0);
316        assert_eq!(decoded.reason_codes[1], SubAckReasonCode::GrantedQoS1);
317        assert_eq!(
318            decoded.reason_codes[2],
319            SubAckReasonCode::TopicFilterInvalid
320        );
321
322        let reason_str = decoded.properties.get(PropertyId::ReasonString);
323        assert!(reason_str.is_some());
324    }
325
326    #[test]
327    fn test_suback_empty_reason_codes() {
328        let packet = SubAckPacket::new(123);
329
330        let mut buf = BytesMut::new();
331        let result = packet.encode(&mut buf);
332        assert!(result.is_err());
333    }
334}