mqute_codec/protocol/v5/
auth.rs

1//! # Auth Packet V5
2//!
3//! This module defines the `Auth` packet, which is used in the MQTT v5 protocol for enhanced
4//! authentication and re-authentication. The `Auth` packet includes a reason code and optional
5//! properties for authentication data, method, and user-defined properties.
6
7use crate::Error;
8use crate::codec::util::{decode_byte, decode_variable_integer, encode_variable_integer};
9use crate::codec::{Decode, Encode, RawPacket};
10use crate::protocol::util::len_bytes;
11use crate::protocol::v5::property::{
12    Property, PropertyFrame, property_decode, property_encode, property_len,
13};
14use crate::protocol::v5::reason::ReasonCode;
15use crate::protocol::{FixedHeader, PacketType};
16use bytes::{Buf, BufMut, Bytes, BytesMut};
17
18/// Validates the reason code for the `Auth` packet.
19///
20/// The `Auth` packet supports only specific reason codes: `Success` (0),
21/// `ContinueAuthentication` (24), and `ReAuthenticate` (25).
22fn validate_auth_reason_code(code: ReasonCode) -> bool {
23    matches!(code.into(), 0 | 24 | 25)
24}
25
26/// Represents the properties of the `Auth` packet.
27///
28/// # Example
29///
30/// ```rust
31/// use bytes::Bytes;
32/// use mqute_codec::protocol::v5::AuthProperties;
33///
34/// let properties = AuthProperties {
35///     auth_method: Some(String::from("custom")),
36///     auth_data: Some(Bytes::copy_from_slice(b"\x00\xff\x19\xf3\x2a")),
37///     ..Default::default()
38/// };
39///
40/// ```
41#[derive(Debug, Default, Clone, PartialEq, Eq)]
42pub struct AuthProperties {
43    /// The authentication method.
44    pub auth_method: Option<String>,
45
46    /// The authentication data.
47    pub auth_data: Option<Bytes>,
48
49    /// The reason string for the authentication.
50    pub reason_string: Option<String>,
51
52    /// User-defined properties.
53    pub user_properties: Vec<(String, String)>,
54}
55
56impl PropertyFrame for AuthProperties {
57    /// Returns the encoded length of the `AuthProperties`.
58    fn encoded_len(&self) -> usize {
59        let mut len = 0usize;
60
61        len += property_len!(&self.auth_method);
62        len += property_len!(&self.auth_data);
63        len += property_len!(&self.reason_string);
64        len += property_len!(&self.user_properties);
65
66        len
67    }
68
69    /// Encodes the `AuthProperties` into a byte buffer.
70    fn encode(&self, buf: &mut BytesMut) {
71        property_encode!(&self.auth_method, Property::AuthenticationMethod, buf);
72        property_encode!(&self.auth_data, Property::AuthenticationData, buf);
73        property_encode!(&self.reason_string, Property::ReasonString, buf);
74        property_encode!(&self.user_properties, Property::UserProp, buf);
75    }
76
77    /// Decodes the `AuthProperties` from a byte buffer.
78    fn decode(buf: &mut Bytes) -> Result<Option<Self>, Error> {
79        if buf.is_empty() {
80            return Ok(None);
81        }
82
83        let mut properties = AuthProperties::default();
84
85        while buf.has_remaining() {
86            let property: Property = decode_byte(buf)?.try_into()?;
87            match property {
88                Property::AuthenticationMethod => {
89                    property_decode!(&mut properties.auth_method, buf);
90                }
91                Property::AuthenticationData => {
92                    property_decode!(&mut properties.auth_data, buf);
93                }
94                Property::ReasonString => {
95                    property_decode!(&mut properties.reason_string, buf);
96                }
97                Property::UserProp => {
98                    property_decode!(&mut properties.user_properties, buf);
99                }
100                _ => return Err(Error::PropertyMismatch),
101            }
102        }
103
104        Ok(Some(properties))
105    }
106}
107
108/// Represents the header of the `Auth` packet.
109///
110/// The `AuthHeader` struct includes the reason code and optional properties for the `Auth` packet.
111#[derive(Debug, Clone, PartialEq, Eq)]
112struct AuthHeader {
113    /// The reason code for the `Auth` packet.
114    code: ReasonCode,
115
116    /// Optional properties for the `Auth` packet.
117    properties: Option<AuthProperties>,
118}
119
120impl AuthHeader {
121    /// Creates a new `AuthHeader` instance.
122    ///
123    /// # Panics
124    ///
125    /// Panics if the reason code is invalid.
126    pub(crate) fn new(code: ReasonCode, properties: Option<AuthProperties>) -> Self {
127        if !validate_auth_reason_code(code) {
128            panic!("Invalid reason code");
129        }
130        AuthHeader { code, properties }
131    }
132
133    /// Returns the encoded length of the `AuthHeader`.
134    ///
135    /// If the reason code is `Success` and there are no properties, the length is 0.
136    pub(crate) fn encoded_len(&self) -> usize {
137        if self.code == ReasonCode::Success && self.properties.is_none() {
138            return 0;
139        }
140
141        let properties_len = self
142            .properties
143            .as_ref()
144            .map(|properties| properties.encoded_len())
145            .unwrap_or(0);
146
147        1 + len_bytes(properties_len) + properties_len
148    }
149
150    /// Encodes the `AuthHeader` into a byte buffer.
151    pub(crate) fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
152        // The reason code and properties can be omitted if the code is 0x00 and there are no properties
153        if self.code == ReasonCode::Success && self.properties.is_none() {
154            return Ok(());
155        }
156
157        buf.put_u8(self.code.into());
158
159        let properties_len = self
160            .properties
161            .as_ref()
162            .map(|properties| properties.encoded_len())
163            .unwrap_or(0) as u32;
164
165        // Encode properties length
166        encode_variable_integer(buf, properties_len)?;
167
168        // Encode properties
169        if let Some(properties) = self.properties.as_ref() {
170            properties.encode(buf);
171        }
172
173        Ok(())
174    }
175
176    /// Decodes the `AuthHeader` from a byte buffer.
177    pub(crate) fn decode(payload: &mut Bytes) -> Result<Self, Error> {
178        if payload.is_empty() {
179            return Ok(AuthHeader {
180                code: ReasonCode::Success,
181                properties: None,
182            });
183        }
184
185        let code: ReasonCode = decode_byte(payload)?.try_into()?;
186        if !validate_auth_reason_code(code) {
187            return Err(Error::InvalidReasonCode(code.into()));
188        }
189
190        let properties_len = decode_variable_integer(payload)? as usize;
191        if payload.len() < properties_len + len_bytes(properties_len) {
192            return Err(Error::MalformedPacket);
193        }
194
195        // Skip variable byte
196        payload.advance(len_bytes(properties_len));
197
198        let mut properties_buf = payload.split_to(properties_len);
199
200        // Deserialize properties
201        let properties = AuthProperties::decode(&mut properties_buf)?;
202        Ok(AuthHeader { code, properties })
203    }
204}
205
206/// Represents an MQTT `AUTH` packet.
207///
208/// The `Auth` packet is used in the MQTT v5 protocol for enhanced authentication and
209/// re-authentication.
210///
211/// It includes a reason code and optional properties.
212///
213/// # Example
214///
215/// ```rust
216/// use bytes::Bytes;
217/// use mqute_codec::protocol::v5::{Auth, AuthProperties, ReasonCode};
218///
219/// let properties = AuthProperties {
220///     auth_method: Some("method".to_string()),
221///     auth_data: Some(Bytes::from("data")),
222///     reason_string: Some("reason".to_string()),
223///     user_properties: vec![("key".to_string(), "value".to_string())],
224/// };
225/// let auth = Auth::new(ReasonCode::ContinueAuthentication, Some(properties));
226/// assert_eq!(auth.code(), ReasonCode::ContinueAuthentication);
227/// ```
228#[derive(Debug, Clone, PartialEq, Eq)]
229pub struct Auth {
230    /// The header of the `Auth` packet, including the reason code and optional properties.
231    header: AuthHeader,
232}
233
234impl Auth {
235    /// Creates a new `Auth` packet.
236    pub fn new(code: ReasonCode, properties: Option<AuthProperties>) -> Self {
237        Auth {
238            header: AuthHeader::new(code, properties),
239        }
240    }
241
242    /// Returns the reason code of the `Auth` packet.
243    pub fn code(&self) -> ReasonCode {
244        self.header.code
245    }
246
247    /// Returns the properties of the `Auth` packet.
248    pub fn properties(&self) -> Option<AuthProperties> {
249        self.header.properties.clone()
250    }
251}
252
253impl Encode for Auth {
254    /// Encodes the `Auth` packet into a byte buffer.
255    fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
256        let header = FixedHeader::new(PacketType::Auth, self.payload_len());
257        header.encode(buf)?;
258
259        self.header.encode(buf)
260    }
261
262    /// Returns the length of the `Auth` packet payload.
263    fn payload_len(&self) -> usize {
264        self.header.encoded_len()
265    }
266}
267
268impl Decode for Auth {
269    /// Decodes an `Auth` packet from a raw MQTT packet.
270    fn decode(mut packet: RawPacket) -> Result<Self, Error> {
271        if packet.header.packet_type() != PacketType::Auth || !packet.header.flags().is_default() {
272            return Err(Error::MalformedPacket);
273        }
274
275        let header = AuthHeader::decode(&mut packet.payload)?;
276        Ok(Auth { header })
277    }
278}