Skip to main content

zerodds_mqtt_bridge/
reason_codes.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! MQTT 5.0 Reason Codes — Spec §2.4.
5//!
6//! Reason Codes >= 0x80 sind Errors. Wir liefern eine vollstaendige
7//! Tabelle aller Codes aus Spec Table 2-2.
8
9use core::fmt;
10
11/// MQTT-5.0 Reason Code.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u8)]
14pub enum ReasonCode {
15    /// `0` Success / Normal Disconnection / Granted QoS 0.
16    Success = 0x00,
17    /// `1` Granted QoS 1.
18    GrantedQoS1 = 0x01,
19    /// `2` Granted QoS 2.
20    GrantedQoS2 = 0x02,
21    /// `4` Disconnect with Will Message.
22    DisconnectWithWillMessage = 0x04,
23    /// `16` No Matching Subscribers.
24    NoMatchingSubscribers = 0x10,
25    /// `17` No Subscription Existed.
26    NoSubscriptionExisted = 0x11,
27    /// `24` Continue Authentication.
28    ContinueAuthentication = 0x18,
29    /// `25` Re-authenticate.
30    Reauthenticate = 0x19,
31    /// `128` Unspecified Error.
32    UnspecifiedError = 0x80,
33    /// `129` Malformed Packet.
34    MalformedPacket = 0x81,
35    /// `130` Protocol Error.
36    ProtocolError = 0x82,
37    /// `131` Implementation specific Error.
38    ImplementationSpecificError = 0x83,
39    /// `132` Unsupported Protocol Version.
40    UnsupportedProtocolVersion = 0x84,
41    /// `133` Client Identifier not valid.
42    ClientIdentifierNotValid = 0x85,
43    /// `134` Bad User Name or Password.
44    BadUserNameOrPassword = 0x86,
45    /// `135` Not authorized.
46    NotAuthorized = 0x87,
47    /// `136` Server unavailable.
48    ServerUnavailable = 0x88,
49    /// `137` Server busy.
50    ServerBusy = 0x89,
51    /// `138` Banned.
52    Banned = 0x8a,
53    /// `139` Server shutting down.
54    ServerShuttingDown = 0x8b,
55    /// `140` Bad authentication method.
56    BadAuthenticationMethod = 0x8c,
57    /// `141` Keep Alive timeout.
58    KeepAliveTimeout = 0x8d,
59    /// `142` Session taken over.
60    SessionTakenOver = 0x8e,
61    /// `143` Topic Filter invalid.
62    TopicFilterInvalid = 0x8f,
63    /// `144` Topic Name invalid.
64    TopicNameInvalid = 0x90,
65    /// `145` Packet Identifier in use.
66    PacketIdentifierInUse = 0x91,
67    /// `146` Packet Identifier not found.
68    PacketIdentifierNotFound = 0x92,
69    /// `147` Receive Maximum exceeded.
70    ReceiveMaximumExceeded = 0x93,
71    /// `148` Topic Alias invalid.
72    TopicAliasInvalid = 0x94,
73    /// `149` Packet too large.
74    PacketTooLarge = 0x95,
75    /// `150` Message rate too high.
76    MessageRateTooHigh = 0x96,
77    /// `151` Quota exceeded.
78    QuotaExceeded = 0x97,
79    /// `152` Administrative action.
80    AdministrativeAction = 0x98,
81    /// `153` Payload format invalid.
82    PayloadFormatInvalid = 0x99,
83    /// `154` Retain not supported.
84    RetainNotSupported = 0x9a,
85    /// `155` QoS not supported.
86    QoSNotSupported = 0x9b,
87    /// `156` Use another server.
88    UseAnotherServer = 0x9c,
89    /// `157` Server moved.
90    ServerMoved = 0x9d,
91    /// `158` Shared Subscriptions not supported.
92    SharedSubscriptionsNotSupported = 0x9e,
93    /// `159` Connection rate exceeded.
94    ConnectionRateExceeded = 0x9f,
95    /// `160` Maximum connect time.
96    MaximumConnectTime = 0xa0,
97    /// `161` Subscription Identifiers not supported.
98    SubscriptionIdentifiersNotSupported = 0xa1,
99    /// `162` Wildcard Subscriptions not supported.
100    WildcardSubscriptionsNotSupported = 0xa2,
101}
102
103impl ReasonCode {
104    /// `true` wenn Code >= 0x80 (Spec §2.4.1).
105    #[must_use]
106    pub const fn is_error(self) -> bool {
107        (self as u8) >= 0x80
108    }
109
110    /// `u8 -> ReasonCode`.
111    ///
112    /// # Errors
113    /// `()` wenn Code unbekannt.
114    #[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}