mqtt5_protocol/packet/
suback.rs1use crate::error::{MqttError, Result};
2use crate::packet::{FixedHeader, MqttPacket, PacketType};
3use crate::protocol::v5::properties::Properties;
4use crate::QoS;
5use bytes::{Buf, BufMut};
6
7#[derive(Debug, Clone)]
9pub struct SubAckPacket {
10 pub packet_id: u16,
12 pub reason_codes: Vec<SubAckReasonCode>,
14 pub properties: Properties,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u8)]
21pub enum SubAckReasonCode {
22 GrantedQoS0 = 0x00,
24 GrantedQoS1 = 0x01,
26 GrantedQoS2 = 0x02,
28 UnspecifiedError = 0x80,
30 ImplementationSpecificError = 0x83,
32 NotAuthorized = 0x87,
34 TopicFilterInvalid = 0x8F,
36 PacketIdentifierInUse = 0x91,
38 QuotaExceeded = 0x97,
40 SharedSubscriptionsNotSupported = 0x9E,
42 SubscriptionIdentifiersNotSupported = 0xA1,
44 WildcardSubscriptionsNotSupported = 0xA2,
46}
47
48impl SubAckReasonCode {
49 #[must_use]
51 pub fn from_qos(qos: QoS) -> Self {
52 match qos {
53 QoS::AtMostOnce => Self::GrantedQoS0,
54 QoS::AtLeastOnce => Self::GrantedQoS1,
55 QoS::ExactlyOnce => Self::GrantedQoS2,
56 }
57 }
58
59 #[must_use]
61 pub fn from_u8(value: u8) -> Option<Self> {
62 match value {
63 0x00 => Some(Self::GrantedQoS0),
64 0x01 => Some(Self::GrantedQoS1),
65 0x02 => Some(Self::GrantedQoS2),
66 0x80 => Some(Self::UnspecifiedError),
67 0x83 => Some(Self::ImplementationSpecificError),
68 0x87 => Some(Self::NotAuthorized),
69 0x8F => Some(Self::TopicFilterInvalid),
70 0x91 => Some(Self::PacketIdentifierInUse),
71 0x97 => Some(Self::QuotaExceeded),
72 0x9E => Some(Self::SharedSubscriptionsNotSupported),
73 0xA1 => Some(Self::SubscriptionIdentifiersNotSupported),
74 0xA2 => Some(Self::WildcardSubscriptionsNotSupported),
75 _ => None,
76 }
77 }
78
79 #[must_use]
81 pub fn is_success(&self) -> bool {
82 matches!(
83 self,
84 Self::GrantedQoS0 | Self::GrantedQoS1 | Self::GrantedQoS2
85 )
86 }
87
88 #[must_use]
90 pub fn granted_qos(&self) -> Option<QoS> {
91 match self {
92 Self::GrantedQoS0 => Some(QoS::AtMostOnce),
93 Self::GrantedQoS1 => Some(QoS::AtLeastOnce),
94 Self::GrantedQoS2 => Some(QoS::ExactlyOnce),
95 _ => None,
96 }
97 }
98}
99
100impl SubAckPacket {
101 #[must_use]
103 pub fn new(packet_id: u16) -> Self {
104 Self {
105 packet_id,
106 reason_codes: Vec::new(),
107 properties: Properties::default(),
108 }
109 }
110
111 #[must_use]
113 pub fn add_reason_code(mut self, code: SubAckReasonCode) -> Self {
114 self.reason_codes.push(code);
115 self
116 }
117
118 #[must_use]
120 pub fn add_granted_qos(mut self, qos: QoS) -> Self {
121 self.reason_codes.push(SubAckReasonCode::from_qos(qos));
122 self
123 }
124
125 #[must_use]
127 pub fn with_reason_string(mut self, reason: String) -> Self {
128 self.properties.set_reason_string(reason);
129 self
130 }
131
132 #[must_use]
134 pub fn with_user_property(mut self, key: String, value: String) -> Self {
135 self.properties.add_user_property(key, value);
136 self
137 }
138}
139
140impl MqttPacket for SubAckPacket {
141 fn packet_type(&self) -> PacketType {
142 PacketType::SubAck
143 }
144
145 fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
146 buf.put_u16(self.packet_id);
148
149 self.properties.encode(buf)?;
151
152 if self.reason_codes.is_empty() {
154 return Err(MqttError::MalformedPacket(
155 "SUBACK packet must contain at least one reason code".to_string(),
156 ));
157 }
158
159 for code in &self.reason_codes {
160 buf.put_u8(*code as u8);
161 }
162
163 Ok(())
164 }
165
166 fn decode_body<B: Buf>(buf: &mut B, _fixed_header: &FixedHeader) -> Result<Self> {
167 if buf.remaining() < 2 {
169 return Err(MqttError::MalformedPacket(
170 "SUBACK missing packet identifier".to_string(),
171 ));
172 }
173 let packet_id = buf.get_u16();
174
175 let properties = Properties::decode(buf)?;
177
178 let mut reason_codes = Vec::new();
180
181 if !buf.has_remaining() {
182 return Err(MqttError::MalformedPacket(
183 "SUBACK packet must contain at least one reason code".to_string(),
184 ));
185 }
186
187 while buf.has_remaining() {
188 let code_byte = buf.get_u8();
189 let code = SubAckReasonCode::from_u8(code_byte).ok_or_else(|| {
190 MqttError::MalformedPacket(format!("Invalid SUBACK reason code: 0x{code_byte:02X}"))
191 })?;
192 reason_codes.push(code);
193 }
194
195 Ok(Self {
196 packet_id,
197 reason_codes,
198 properties,
199 })
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::protocol::v5::properties::PropertyId;
207 use bytes::BytesMut;
208
209 #[test]
210 fn test_suback_reason_code_from_qos() {
211 assert_eq!(
212 SubAckReasonCode::from_qos(QoS::AtMostOnce),
213 SubAckReasonCode::GrantedQoS0
214 );
215 assert_eq!(
216 SubAckReasonCode::from_qos(QoS::AtLeastOnce),
217 SubAckReasonCode::GrantedQoS1
218 );
219 assert_eq!(
220 SubAckReasonCode::from_qos(QoS::ExactlyOnce),
221 SubAckReasonCode::GrantedQoS2
222 );
223 }
224
225 #[test]
226 fn test_suback_reason_code_is_success() {
227 assert!(SubAckReasonCode::GrantedQoS0.is_success());
228 assert!(SubAckReasonCode::GrantedQoS1.is_success());
229 assert!(SubAckReasonCode::GrantedQoS2.is_success());
230 assert!(!SubAckReasonCode::NotAuthorized.is_success());
231 assert!(!SubAckReasonCode::TopicFilterInvalid.is_success());
232 }
233
234 #[test]
235 fn test_suback_basic() {
236 let packet = SubAckPacket::new(123)
237 .add_granted_qos(QoS::AtLeastOnce)
238 .add_granted_qos(QoS::ExactlyOnce)
239 .add_reason_code(SubAckReasonCode::NotAuthorized);
240
241 assert_eq!(packet.packet_id, 123);
242 assert_eq!(packet.reason_codes.len(), 3);
243 assert_eq!(packet.reason_codes[0], SubAckReasonCode::GrantedQoS1);
244 assert_eq!(packet.reason_codes[1], SubAckReasonCode::GrantedQoS2);
245 assert_eq!(packet.reason_codes[2], SubAckReasonCode::NotAuthorized);
246 }
247
248 #[test]
249 fn test_suback_encode_decode() {
250 let packet = SubAckPacket::new(789)
251 .add_granted_qos(QoS::AtMostOnce)
252 .add_granted_qos(QoS::AtLeastOnce)
253 .add_reason_code(SubAckReasonCode::TopicFilterInvalid)
254 .with_reason_string("Invalid wildcard usage".to_string());
255
256 let mut buf = BytesMut::new();
257 packet.encode(&mut buf).unwrap();
258
259 let fixed_header = FixedHeader::decode(&mut buf).unwrap();
260 assert_eq!(fixed_header.packet_type, PacketType::SubAck);
261
262 let decoded = SubAckPacket::decode_body(&mut buf, &fixed_header).unwrap();
263 assert_eq!(decoded.packet_id, 789);
264 assert_eq!(decoded.reason_codes.len(), 3);
265 assert_eq!(decoded.reason_codes[0], SubAckReasonCode::GrantedQoS0);
266 assert_eq!(decoded.reason_codes[1], SubAckReasonCode::GrantedQoS1);
267 assert_eq!(
268 decoded.reason_codes[2],
269 SubAckReasonCode::TopicFilterInvalid
270 );
271
272 let reason_str = decoded.properties.get(PropertyId::ReasonString);
273 assert!(reason_str.is_some());
274 }
275
276 #[test]
277 fn test_suback_empty_reason_codes() {
278 let packet = SubAckPacket::new(123);
279
280 let mut buf = BytesMut::new();
281 let result = packet.encode(&mut buf);
282 assert!(result.is_err());
283 }
284}