1use crate::error::{MqttError, Result};
2use crate::packet::{FixedHeader, MqttPacket, PacketType};
3use crate::protocol::v5::properties::Properties;
4use crate::protocol::v5::reason_codes::ReasonCode;
5use bytes::{Buf, BufMut};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct AuthPacket {
10 pub reason_code: ReasonCode,
12 pub properties: Properties,
14}
15
16impl AuthPacket {
17 #[must_use]
19 pub fn new(reason_code: ReasonCode) -> Self {
20 Self {
21 reason_code,
22 properties: Properties::default(),
23 }
24 }
25
26 #[must_use]
27 pub fn with_properties(reason_code: ReasonCode, properties: Properties) -> Self {
29 Self {
30 reason_code,
31 properties,
32 }
33 }
34
35 pub fn continue_authentication(
41 auth_method: String,
42 auth_data: Option<Vec<u8>>,
43 ) -> Result<Self> {
44 let mut properties = Properties::default();
45
46 properties.add(
48 crate::protocol::v5::properties::PropertyId::AuthenticationMethod,
49 crate::protocol::v5::properties::PropertyValue::Utf8String(auth_method),
50 )?;
51
52 if let Some(data) = auth_data {
54 properties.add(
55 crate::protocol::v5::properties::PropertyId::AuthenticationData,
56 crate::protocol::v5::properties::PropertyValue::BinaryData(data.into()),
57 )?;
58 }
59
60 Ok(Self::with_properties(
61 ReasonCode::ContinueAuthentication,
62 properties,
63 ))
64 }
65
66 pub fn re_authenticate(auth_method: String, auth_data: Option<Vec<u8>>) -> Result<Self> {
72 let mut properties = Properties::default();
73
74 properties.add(
76 crate::protocol::v5::properties::PropertyId::AuthenticationMethod,
77 crate::protocol::v5::properties::PropertyValue::Utf8String(auth_method),
78 )?;
79
80 if let Some(data) = auth_data {
82 properties.add(
83 crate::protocol::v5::properties::PropertyId::AuthenticationData,
84 crate::protocol::v5::properties::PropertyValue::BinaryData(data.into()),
85 )?;
86 }
87
88 Ok(Self::with_properties(
89 ReasonCode::ReAuthenticate,
90 properties,
91 ))
92 }
93
94 pub fn success(auth_method: String) -> Result<Self> {
100 let mut properties = Properties::default();
101
102 properties.add(
103 crate::protocol::v5::properties::PropertyId::AuthenticationMethod,
104 crate::protocol::v5::properties::PropertyValue::Utf8String(auth_method),
105 )?;
106
107 Ok(Self::with_properties(ReasonCode::Success, properties))
108 }
109
110 pub fn failure(reason_code: ReasonCode, reason_string: Option<String>) -> Result<Self> {
116 if reason_code.is_success() {
117 return Err(MqttError::ProtocolError(
118 "Cannot create failure AUTH packet with success reason code".to_string(),
119 ));
120 }
121
122 let mut properties = Properties::default();
123
124 if let Some(reason) = reason_string {
125 properties.add(
126 crate::protocol::v5::properties::PropertyId::ReasonString,
127 crate::protocol::v5::properties::PropertyValue::Utf8String(reason),
128 )?;
129 }
130
131 Ok(Self::with_properties(reason_code, properties))
132 }
133
134 #[must_use]
135 pub fn authentication_method(&self) -> Option<&str> {
137 if let Some(crate::protocol::v5::properties::PropertyValue::Utf8String(method)) = self
138 .properties
139 .get(crate::protocol::v5::properties::PropertyId::AuthenticationMethod)
140 {
141 Some(method)
142 } else {
143 None
144 }
145 }
146
147 #[must_use]
148 pub fn authentication_data(&self) -> Option<&[u8]> {
150 if let Some(crate::protocol::v5::properties::PropertyValue::BinaryData(data)) = self
151 .properties
152 .get(crate::protocol::v5::properties::PropertyId::AuthenticationData)
153 {
154 Some(data)
155 } else {
156 None
157 }
158 }
159
160 #[must_use]
161 pub fn reason_string(&self) -> Option<&str> {
163 if let Some(crate::protocol::v5::properties::PropertyValue::Utf8String(reason)) = self
164 .properties
165 .get(crate::protocol::v5::properties::PropertyId::ReasonString)
166 {
167 Some(reason)
168 } else {
169 None
170 }
171 }
172
173 pub fn validate(&self) -> Result<()> {
179 if self.authentication_method().is_none()
181 && (self.reason_code == ReasonCode::ContinueAuthentication
182 || self.reason_code == ReasonCode::ReAuthenticate)
183 {
184 return Err(MqttError::ProtocolError(
185 "Authentication method is required for AUTH packets with ContinueAuthentication or ReAuthenticate reason codes".to_string()
186 ));
187 }
188
189 Ok(())
190 }
191}
192
193impl MqttPacket for AuthPacket {
194 fn packet_type(&self) -> PacketType {
195 PacketType::Auth
196 }
197
198 fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
199 self.validate()?;
200
201 if self.reason_code == ReasonCode::Success && self.properties.is_empty() {
204 return Ok(());
206 }
207
208 buf.put_u8(u8::from(self.reason_code));
210
211 self.properties.encode(buf)?;
213
214 Ok(())
215 }
216
217 fn decode_body<B: Buf>(buf: &mut B, fixed_header: &FixedHeader) -> Result<Self> {
218 if fixed_header.remaining_length == 0 {
219 return Ok(Self::new(ReasonCode::Success));
221 }
222
223 if !buf.has_remaining() {
225 return Err(MqttError::MalformedPacket(
226 "Missing reason code in AUTH packet".to_string(),
227 ));
228 }
229
230 let reason_code_val = buf.get_u8();
231 let reason_code = ReasonCode::from_u8(reason_code_val)
232 .ok_or(MqttError::InvalidReasonCode(reason_code_val))?;
233
234 let properties = if buf.has_remaining() {
236 Properties::decode(buf)?
237 } else {
238 Properties::default()
239 };
240
241 let packet = Self {
242 reason_code,
243 properties,
244 };
245
246 packet.validate()?;
247 Ok(packet)
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::protocol::v5::properties::{PropertyId, PropertyValue};
255 use bytes::BytesMut;
256
257 #[test]
258 fn test_auth_packet_new() {
259 let packet = AuthPacket::new(ReasonCode::Success);
260 assert_eq!(packet.reason_code, ReasonCode::Success);
261 assert!(packet.properties.is_empty());
262 }
263
264 #[test]
265 fn test_auth_packet_continue_authentication() {
266 let packet = AuthPacket::continue_authentication(
267 "SCRAM-SHA-256".to_string(),
268 Some(b"challenge_data".to_vec()),
269 )
270 .unwrap();
271
272 assert_eq!(packet.reason_code, ReasonCode::ContinueAuthentication);
273 assert_eq!(packet.authentication_method(), Some("SCRAM-SHA-256"));
274 assert_eq!(
275 packet.authentication_data(),
276 Some(b"challenge_data".as_ref())
277 );
278 }
279
280 #[test]
281 fn test_auth_packet_re_authenticate() {
282 let packet = AuthPacket::re_authenticate("OAUTH2".to_string(), None).unwrap();
283
284 assert_eq!(packet.reason_code, ReasonCode::ReAuthenticate);
285 assert_eq!(packet.authentication_method(), Some("OAUTH2"));
286 assert!(packet.authentication_data().is_none());
287 }
288
289 #[test]
290 fn test_auth_packet_success() {
291 let packet = AuthPacket::success("PLAIN".to_string()).unwrap();
292
293 assert_eq!(packet.reason_code, ReasonCode::Success);
294 assert_eq!(packet.authentication_method(), Some("PLAIN"));
295 }
296
297 #[test]
298 fn test_auth_packet_failure() {
299 let packet = AuthPacket::failure(
300 ReasonCode::BadAuthenticationMethod,
301 Some("Unsupported method".to_string()),
302 )
303 .unwrap();
304
305 assert_eq!(packet.reason_code, ReasonCode::BadAuthenticationMethod);
306 assert_eq!(packet.reason_string(), Some("Unsupported method"));
307 }
308
309 #[test]
310 fn test_auth_packet_failure_with_success_code_fails() {
311 let result = AuthPacket::failure(ReasonCode::Success, None);
312 assert!(result.is_err());
313 }
314
315 #[test]
316 fn test_auth_packet_encode_decode_empty() {
317 let packet = AuthPacket::new(ReasonCode::Success);
318 let mut buf = BytesMut::new();
319
320 packet.encode(&mut buf).unwrap();
321
322 let fixed_header = FixedHeader::decode(&mut buf).unwrap();
324 let decoded = AuthPacket::decode_body(&mut buf, &fixed_header).unwrap();
325
326 assert_eq!(decoded.reason_code, ReasonCode::Success);
327 assert!(decoded.properties.is_empty());
328 }
329
330 #[test]
331 fn test_auth_packet_encode_decode_with_properties() {
332 let packet = AuthPacket::continue_authentication(
333 "SCRAM-SHA-1".to_string(),
334 Some(b"server_nonce".to_vec()),
335 )
336 .unwrap();
337
338 let mut buf = BytesMut::new();
339 packet.encode(&mut buf).unwrap();
340
341 let fixed_header = FixedHeader::decode(&mut buf).unwrap();
342 let decoded = AuthPacket::decode_body(&mut buf, &fixed_header).unwrap();
343
344 assert_eq!(decoded.reason_code, ReasonCode::ContinueAuthentication);
345 assert_eq!(decoded.authentication_method(), Some("SCRAM-SHA-1"));
346 assert_eq!(
347 decoded.authentication_data(),
348 Some(b"server_nonce".as_ref())
349 );
350 }
351
352 #[test]
353 fn test_auth_packet_encode_decode_failure() {
354 let packet = AuthPacket::failure(
355 ReasonCode::NotAuthorized,
356 Some("Invalid credentials".to_string()),
357 )
358 .unwrap();
359
360 let mut buf = BytesMut::new();
361 packet.encode(&mut buf).unwrap();
362
363 let fixed_header = FixedHeader::decode(&mut buf).unwrap();
364 let decoded = AuthPacket::decode_body(&mut buf, &fixed_header).unwrap();
365
366 assert_eq!(decoded.reason_code, ReasonCode::NotAuthorized);
367 assert_eq!(decoded.reason_string(), Some("Invalid credentials"));
368 }
369
370 #[test]
371 fn test_auth_packet_validation_missing_auth_method() {
372 let packet = AuthPacket::new(ReasonCode::ContinueAuthentication);
373 let result = packet.validate();
375 assert!(result.is_err());
376 }
377
378 #[test]
379 fn test_auth_packet_decode_malformed() {
380 let mut buf = BytesMut::new();
381 let result = AuthPacket::decode_body(&mut buf, &FixedHeader::new(PacketType::Auth, 0, 1));
384 assert!(result.is_err());
385 }
386
387 #[test]
388 fn test_auth_packet_decode_invalid_reason_code() {
389 let mut buf = BytesMut::new();
390 buf.put_u8(0xFF); buf.put_u8(0x00); let result = AuthPacket::decode_body(&mut buf, &FixedHeader::new(PacketType::Auth, 0, 2));
394 assert!(result.is_err());
395 }
396
397 #[test]
398 fn test_auth_packet_property_getters() {
399 let mut properties = Properties::default();
400 properties
401 .add(
402 PropertyId::AuthenticationMethod,
403 PropertyValue::Utf8String("TEST".to_string()),
404 )
405 .unwrap();
406 properties
407 .add(
408 PropertyId::AuthenticationData,
409 PropertyValue::BinaryData(b"data".to_vec().into()),
410 )
411 .unwrap();
412 properties
413 .add(
414 PropertyId::ReasonString,
415 PropertyValue::Utf8String("reason".to_string()),
416 )
417 .unwrap();
418
419 let packet = AuthPacket::with_properties(ReasonCode::Success, properties);
420
421 assert_eq!(packet.authentication_method(), Some("TEST"));
422 assert_eq!(packet.authentication_data(), Some(b"data".as_ref()));
423 assert_eq!(packet.reason_string(), Some("reason"));
424 }
425}