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