mqtt5_protocol/packet/
unsuback.rs1use crate::error::{MqttError, Result};
2use crate::packet::{FixedHeader, MqttPacket, PacketType};
3use crate::protocol::v5::properties::Properties;
4use bytes::{Buf, BufMut};
5
6#[derive(Debug, Clone)]
8pub struct UnsubAckPacket {
9 pub packet_id: u16,
11 pub reason_codes: Vec<UnsubAckReasonCode>,
13 pub properties: Properties,
15 pub protocol_version: u8,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(u8)]
22pub enum UnsubAckReasonCode {
23 Success = 0x00,
25 NoSubscriptionExisted = 0x11,
27 UnspecifiedError = 0x80,
29 ImplementationSpecificError = 0x83,
31 NotAuthorized = 0x87,
33 TopicFilterInvalid = 0x8F,
35 PacketIdentifierInUse = 0x91,
37}
38
39impl UnsubAckReasonCode {
40 #[must_use]
42 pub fn from_u8(value: u8) -> Option<Self> {
43 match value {
44 0x00 => Some(Self::Success),
45 0x11 => Some(Self::NoSubscriptionExisted),
46 0x80 => Some(Self::UnspecifiedError),
47 0x83 => Some(Self::ImplementationSpecificError),
48 0x87 => Some(Self::NotAuthorized),
49 0x8F => Some(Self::TopicFilterInvalid),
50 0x91 => Some(Self::PacketIdentifierInUse),
51 _ => None,
52 }
53 }
54
55 #[must_use]
57 pub fn is_success(&self) -> bool {
58 matches!(self, Self::Success)
59 }
60}
61
62impl UnsubAckPacket {
63 #[must_use]
65 pub fn new(packet_id: u16) -> Self {
66 Self {
67 packet_id,
68 reason_codes: Vec::new(),
69 properties: Properties::default(),
70 protocol_version: 5,
71 }
72 }
73
74 #[must_use]
76 pub fn new_v311(packet_id: u16) -> Self {
77 Self {
78 packet_id,
79 reason_codes: Vec::new(),
80 properties: Properties::default(),
81 protocol_version: 4,
82 }
83 }
84
85 #[must_use]
87 pub fn add_reason_code(mut self, code: UnsubAckReasonCode) -> Self {
88 self.reason_codes.push(code);
89 self
90 }
91
92 #[must_use]
94 pub fn add_success(mut self) -> Self {
95 self.reason_codes.push(UnsubAckReasonCode::Success);
96 self
97 }
98
99 #[must_use]
101 pub fn with_reason_string(mut self, reason: String) -> Self {
102 self.properties.set_reason_string(reason);
103 self
104 }
105
106 #[must_use]
108 pub fn with_user_property(mut self, key: String, value: String) -> Self {
109 self.properties.add_user_property(key, value);
110 self
111 }
112}
113
114impl MqttPacket for UnsubAckPacket {
115 fn packet_type(&self) -> PacketType {
116 PacketType::UnsubAck
117 }
118
119 fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
120 buf.put_u16(self.packet_id);
121
122 if self.protocol_version == 5 {
123 self.properties.encode(buf)?;
124
125 if self.reason_codes.is_empty() {
126 return Err(MqttError::MalformedPacket(
127 "UNSUBACK packet must contain at least one reason code".to_string(),
128 ));
129 }
130
131 for code in &self.reason_codes {
132 buf.put_u8(*code as u8);
133 }
134 }
135
136 Ok(())
137 }
138
139 fn decode_body<B: Buf>(buf: &mut B, _fixed_header: &FixedHeader) -> Result<Self> {
140 if buf.remaining() < 2 {
142 return Err(MqttError::MalformedPacket(
143 "UNSUBACK missing packet identifier".to_string(),
144 ));
145 }
146 let packet_id = buf.get_u16();
147
148 let properties = Properties::decode(buf)?;
150
151 let mut reason_codes = Vec::new();
153
154 if !buf.has_remaining() {
155 return Err(MqttError::MalformedPacket(
156 "UNSUBACK packet must contain at least one reason code".to_string(),
157 ));
158 }
159
160 while buf.has_remaining() {
161 let code_byte = buf.get_u8();
162 let code = UnsubAckReasonCode::from_u8(code_byte).ok_or_else(|| {
163 MqttError::MalformedPacket(format!(
164 "Invalid UNSUBACK reason code: 0x{code_byte:02X}"
165 ))
166 })?;
167 reason_codes.push(code);
168 }
169
170 Ok(Self {
171 packet_id,
172 reason_codes,
173 properties,
174 protocol_version: 5,
175 })
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::protocol::v5::properties::PropertyId;
183 use bytes::BytesMut;
184
185 #[test]
186 fn test_unsuback_reason_code_is_success() {
187 assert!(UnsubAckReasonCode::Success.is_success());
188 assert!(!UnsubAckReasonCode::NoSubscriptionExisted.is_success());
189 assert!(!UnsubAckReasonCode::NotAuthorized.is_success());
190 }
191
192 #[test]
193 fn test_unsuback_basic() {
194 let packet = UnsubAckPacket::new(123)
195 .add_success()
196 .add_success()
197 .add_reason_code(UnsubAckReasonCode::NoSubscriptionExisted);
198
199 assert_eq!(packet.packet_id, 123);
200 assert_eq!(packet.reason_codes.len(), 3);
201 assert_eq!(packet.reason_codes[0], UnsubAckReasonCode::Success);
202 assert_eq!(packet.reason_codes[1], UnsubAckReasonCode::Success);
203 assert_eq!(
204 packet.reason_codes[2],
205 UnsubAckReasonCode::NoSubscriptionExisted
206 );
207 }
208
209 #[test]
210 fn test_unsuback_encode_decode() {
211 let packet = UnsubAckPacket::new(789)
212 .add_success()
213 .add_reason_code(UnsubAckReasonCode::NotAuthorized)
214 .add_reason_code(UnsubAckReasonCode::TopicFilterInvalid)
215 .with_reason_string("Invalid topic filter".to_string());
216
217 let mut buf = BytesMut::new();
218 packet.encode(&mut buf).unwrap();
219
220 let fixed_header = FixedHeader::decode(&mut buf).unwrap();
221 assert_eq!(fixed_header.packet_type, PacketType::UnsubAck);
222
223 let decoded = UnsubAckPacket::decode_body(&mut buf, &fixed_header).unwrap();
224 assert_eq!(decoded.packet_id, 789);
225 assert_eq!(decoded.reason_codes.len(), 3);
226 assert_eq!(decoded.reason_codes[0], UnsubAckReasonCode::Success);
227 assert_eq!(decoded.reason_codes[1], UnsubAckReasonCode::NotAuthorized);
228 assert_eq!(
229 decoded.reason_codes[2],
230 UnsubAckReasonCode::TopicFilterInvalid
231 );
232
233 let reason_str = decoded.properties.get(PropertyId::ReasonString);
234 assert!(reason_str.is_some());
235 }
236
237 #[test]
238 fn test_unsuback_empty_reason_codes() {
239 let packet = UnsubAckPacket::new(123);
240
241 let mut buf = BytesMut::new();
242 let result = packet.encode(&mut buf);
243 assert!(result.is_err());
244 }
245}