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#[derive(Debug, Clone)]
11pub struct SubAckPacket {
12 pub packet_id: u16,
14 pub reason_codes: Vec<SubAckReasonCode>,
16 pub properties: Properties,
18 pub protocol_version: u8,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u8)]
25pub enum SubAckReasonCode {
26 GrantedQoS0 = 0x00,
28 GrantedQoS1 = 0x01,
30 GrantedQoS2 = 0x02,
32 UnspecifiedError = 0x80,
34 ImplementationSpecificError = 0x83,
36 NotAuthorized = 0x87,
38 TopicFilterInvalid = 0x8F,
40 PacketIdentifierInUse = 0x91,
42 QuotaExceeded = 0x97,
44 SharedSubscriptionsNotSupported = 0x9E,
46 SubscriptionIdentifiersNotSupported = 0xA1,
48 WildcardSubscriptionsNotSupported = 0xA2,
50}
51
52impl SubAckReasonCode {
53 #[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 #[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 #[must_use]
85 pub fn is_success(&self) -> bool {
86 matches!(
87 self,
88 Self::GrantedQoS0 | Self::GrantedQoS1 | Self::GrantedQoS2
89 )
90 }
91
92 #[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 #[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 #[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 #[must_use]
138 pub fn add_reason_code(mut self, code: SubAckReasonCode) -> Self {
139 self.reason_codes.push(code);
140 self
141 }
142
143 #[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 #[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 #[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 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}