1use core::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u8)]
14pub enum ReasonCode {
15 Success = 0x00,
17 GrantedQoS1 = 0x01,
19 GrantedQoS2 = 0x02,
21 DisconnectWithWillMessage = 0x04,
23 NoMatchingSubscribers = 0x10,
25 NoSubscriptionExisted = 0x11,
27 ContinueAuthentication = 0x18,
29 Reauthenticate = 0x19,
31 UnspecifiedError = 0x80,
33 MalformedPacket = 0x81,
35 ProtocolError = 0x82,
37 ImplementationSpecificError = 0x83,
39 UnsupportedProtocolVersion = 0x84,
41 ClientIdentifierNotValid = 0x85,
43 BadUserNameOrPassword = 0x86,
45 NotAuthorized = 0x87,
47 ServerUnavailable = 0x88,
49 ServerBusy = 0x89,
51 Banned = 0x8a,
53 ServerShuttingDown = 0x8b,
55 BadAuthenticationMethod = 0x8c,
57 KeepAliveTimeout = 0x8d,
59 SessionTakenOver = 0x8e,
61 TopicFilterInvalid = 0x8f,
63 TopicNameInvalid = 0x90,
65 PacketIdentifierInUse = 0x91,
67 PacketIdentifierNotFound = 0x92,
69 ReceiveMaximumExceeded = 0x93,
71 TopicAliasInvalid = 0x94,
73 PacketTooLarge = 0x95,
75 MessageRateTooHigh = 0x96,
77 QuotaExceeded = 0x97,
79 AdministrativeAction = 0x98,
81 PayloadFormatInvalid = 0x99,
83 RetainNotSupported = 0x9a,
85 QoSNotSupported = 0x9b,
87 UseAnotherServer = 0x9c,
89 ServerMoved = 0x9d,
91 SharedSubscriptionsNotSupported = 0x9e,
93 ConnectionRateExceeded = 0x9f,
95 MaximumConnectTime = 0xa0,
97 SubscriptionIdentifiersNotSupported = 0xa1,
99 WildcardSubscriptionsNotSupported = 0xa2,
101}
102
103impl ReasonCode {
104 #[must_use]
106 pub const fn is_error(self) -> bool {
107 (self as u8) >= 0x80
108 }
109
110 #[allow(clippy::result_unit_err)]
115 pub const fn from_u8(v: u8) -> Result<Self, ()> {
116 match v {
117 0x00 => Ok(Self::Success),
118 0x01 => Ok(Self::GrantedQoS1),
119 0x02 => Ok(Self::GrantedQoS2),
120 0x04 => Ok(Self::DisconnectWithWillMessage),
121 0x10 => Ok(Self::NoMatchingSubscribers),
122 0x11 => Ok(Self::NoSubscriptionExisted),
123 0x18 => Ok(Self::ContinueAuthentication),
124 0x19 => Ok(Self::Reauthenticate),
125 0x80 => Ok(Self::UnspecifiedError),
126 0x81 => Ok(Self::MalformedPacket),
127 0x82 => Ok(Self::ProtocolError),
128 0x83 => Ok(Self::ImplementationSpecificError),
129 0x84 => Ok(Self::UnsupportedProtocolVersion),
130 0x85 => Ok(Self::ClientIdentifierNotValid),
131 0x86 => Ok(Self::BadUserNameOrPassword),
132 0x87 => Ok(Self::NotAuthorized),
133 0x88 => Ok(Self::ServerUnavailable),
134 0x89 => Ok(Self::ServerBusy),
135 0x8a => Ok(Self::Banned),
136 0x8b => Ok(Self::ServerShuttingDown),
137 0x8c => Ok(Self::BadAuthenticationMethod),
138 0x8d => Ok(Self::KeepAliveTimeout),
139 0x8e => Ok(Self::SessionTakenOver),
140 0x8f => Ok(Self::TopicFilterInvalid),
141 0x90 => Ok(Self::TopicNameInvalid),
142 0x91 => Ok(Self::PacketIdentifierInUse),
143 0x92 => Ok(Self::PacketIdentifierNotFound),
144 0x93 => Ok(Self::ReceiveMaximumExceeded),
145 0x94 => Ok(Self::TopicAliasInvalid),
146 0x95 => Ok(Self::PacketTooLarge),
147 0x96 => Ok(Self::MessageRateTooHigh),
148 0x97 => Ok(Self::QuotaExceeded),
149 0x98 => Ok(Self::AdministrativeAction),
150 0x99 => Ok(Self::PayloadFormatInvalid),
151 0x9a => Ok(Self::RetainNotSupported),
152 0x9b => Ok(Self::QoSNotSupported),
153 0x9c => Ok(Self::UseAnotherServer),
154 0x9d => Ok(Self::ServerMoved),
155 0x9e => Ok(Self::SharedSubscriptionsNotSupported),
156 0x9f => Ok(Self::ConnectionRateExceeded),
157 0xa0 => Ok(Self::MaximumConnectTime),
158 0xa1 => Ok(Self::SubscriptionIdentifiersNotSupported),
159 0xa2 => Ok(Self::WildcardSubscriptionsNotSupported),
160 _ => Err(()),
161 }
162 }
163}
164
165impl fmt::Display for ReasonCode {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 write!(f, "{:?}(0x{:02x})", self, *self as u8)
168 }
169}
170
171#[cfg(test)]
172#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn success_is_zero() {
178 assert_eq!(ReasonCode::Success as u8, 0);
179 assert!(!ReasonCode::Success.is_error());
180 }
181
182 #[test]
183 fn error_codes_have_high_bit() {
184 for c in [
185 ReasonCode::UnspecifiedError,
186 ReasonCode::MalformedPacket,
187 ReasonCode::ProtocolError,
188 ReasonCode::TopicNameInvalid,
189 ReasonCode::WildcardSubscriptionsNotSupported,
190 ] {
191 assert!(c.is_error());
192 assert!((c as u8) >= 0x80);
193 }
194 }
195
196 #[test]
197 fn round_trip_all_codes() {
198 let codes = [
199 0x00, 0x01, 0x02, 0x04, 0x10, 0x11, 0x18, 0x19, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85,
200 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93,
201 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1,
202 0xa2,
203 ];
204 for c in codes {
205 assert_eq!(ReasonCode::from_u8(c).unwrap() as u8, c);
206 }
207 }
208
209 #[test]
210 fn unknown_code_rejected() {
211 assert!(ReasonCode::from_u8(0xff).is_err());
212 assert!(ReasonCode::from_u8(0x03).is_err());
213 }
214
215 #[test]
216 fn granted_qos_codes_are_not_errors() {
217 assert!(!ReasonCode::GrantedQoS1.is_error());
218 assert!(!ReasonCode::GrantedQoS2.is_error());
219 }
220
221 #[test]
222 fn display_formats_with_hex() {
223 let s = alloc::format!("{}", ReasonCode::Success);
224 assert!(s.contains("0x00"));
225 }
226}