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