Skip to main content

netget/server/mqtt/
v5.rs

1//! MQTT 5.0 properties and reason codes (OASIS MQTT v5.0, §2.2.2 and §2.4).
2//!
3//! A v5 connection differs from a 3.1.1 one in two places, and this module holds both:
4//!
5//! - **Properties.** Every variable header except PINGREQ/PINGRESP gains a property block: a
6//!   variable byte integer length, then `identifier, value` pairs whose value type is fixed by
7//!   the identifier (§2.2.2.2). [`read_properties`] decodes one block into [`Properties`] and
8//!   [`PropertyWriter`] encodes one.
9//! - **Reason codes.** CONNACK's return code, SUBACK's granted QoS and the PUBACK / PUBREC /
10//!   UNSUBACK / DISCONNECT acknowledgements carry a one-byte reason code from a table per
11//!   packet (§2.4). 3.1.1's six CONNACK codes map onto v5's, which is what
12//!   [`connack_v311_to_v5`] and [`connack_v5_to_v311`] do, so a handler written for one version
13//!   still answers the other correctly.
14//!
15//! Binary properties (Correlation Data, Authentication Data) never reach the model as bytes:
16//! they are rendered as text when they are UTF-8 and as hex otherwise, with a sibling
17//! `<name>_encoding` saying which, and the actions take the same `encoding` field back — the
18//! rule `send_tcp_data` taught this repository (AGENTS.md, "Action & event design rules").
19
20use serde_json::{json, Map, Value};
21
22// ---------------------------------------------------------------------------
23// Property identifiers (§2.2.2.2)
24// ---------------------------------------------------------------------------
25
26pub const PAYLOAD_FORMAT_INDICATOR: u8 = 0x01;
27pub const MESSAGE_EXPIRY_INTERVAL: u8 = 0x02;
28pub const CONTENT_TYPE: u8 = 0x03;
29pub const RESPONSE_TOPIC: u8 = 0x08;
30pub const CORRELATION_DATA: u8 = 0x09;
31pub const SUBSCRIPTION_IDENTIFIER: u8 = 0x0B;
32pub const SESSION_EXPIRY_INTERVAL: u8 = 0x11;
33pub const ASSIGNED_CLIENT_IDENTIFIER: u8 = 0x12;
34pub const SERVER_KEEP_ALIVE: u8 = 0x13;
35pub const AUTHENTICATION_METHOD: u8 = 0x15;
36pub const AUTHENTICATION_DATA: u8 = 0x16;
37pub const REQUEST_PROBLEM_INFORMATION: u8 = 0x17;
38pub const WILL_DELAY_INTERVAL: u8 = 0x18;
39pub const REQUEST_RESPONSE_INFORMATION: u8 = 0x19;
40pub const RESPONSE_INFORMATION: u8 = 0x1A;
41pub const SERVER_REFERENCE: u8 = 0x1C;
42pub const REASON_STRING: u8 = 0x1F;
43pub const RECEIVE_MAXIMUM: u8 = 0x21;
44pub const TOPIC_ALIAS_MAXIMUM: u8 = 0x22;
45pub const TOPIC_ALIAS: u8 = 0x23;
46pub const MAXIMUM_QOS: u8 = 0x24;
47pub const RETAIN_AVAILABLE: u8 = 0x25;
48pub const USER_PROPERTY: u8 = 0x26;
49pub const MAXIMUM_PACKET_SIZE: u8 = 0x27;
50pub const WILDCARD_SUBSCRIPTION_AVAILABLE: u8 = 0x28;
51pub const SUBSCRIPTION_IDENTIFIER_AVAILABLE: u8 = 0x29;
52pub const SHARED_SUBSCRIPTION_AVAILABLE: u8 = 0x2A;
53
54/// The value type an identifier carries. Fixed by the specification, so an identifier this
55/// table does not know is a malformed packet rather than something to skip: there is no way
56/// to know how many bytes its value occupies.
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58enum Kind {
59    Byte,
60    U16,
61    U32,
62    Varint,
63    Utf8,
64    Binary,
65    Pair,
66}
67
68/// Identifier → (snake_case name the model sees, value type).
69fn describe(id: u8) -> Option<(&'static str, Kind)> {
70    Some(match id {
71        PAYLOAD_FORMAT_INDICATOR => ("payload_format_indicator", Kind::Byte),
72        MESSAGE_EXPIRY_INTERVAL => ("message_expiry_interval", Kind::U32),
73        CONTENT_TYPE => ("content_type", Kind::Utf8),
74        RESPONSE_TOPIC => ("response_topic", Kind::Utf8),
75        CORRELATION_DATA => ("correlation_data", Kind::Binary),
76        SUBSCRIPTION_IDENTIFIER => ("subscription_identifier", Kind::Varint),
77        SESSION_EXPIRY_INTERVAL => ("session_expiry_interval", Kind::U32),
78        ASSIGNED_CLIENT_IDENTIFIER => ("assigned_client_identifier", Kind::Utf8),
79        SERVER_KEEP_ALIVE => ("server_keep_alive", Kind::U16),
80        AUTHENTICATION_METHOD => ("authentication_method", Kind::Utf8),
81        AUTHENTICATION_DATA => ("authentication_data", Kind::Binary),
82        REQUEST_PROBLEM_INFORMATION => ("request_problem_information", Kind::Byte),
83        WILL_DELAY_INTERVAL => ("will_delay_interval", Kind::U32),
84        REQUEST_RESPONSE_INFORMATION => ("request_response_information", Kind::Byte),
85        RESPONSE_INFORMATION => ("response_information", Kind::Utf8),
86        SERVER_REFERENCE => ("server_reference", Kind::Utf8),
87        REASON_STRING => ("reason_string", Kind::Utf8),
88        RECEIVE_MAXIMUM => ("receive_maximum", Kind::U16),
89        TOPIC_ALIAS_MAXIMUM => ("topic_alias_maximum", Kind::U16),
90        TOPIC_ALIAS => ("topic_alias", Kind::U16),
91        MAXIMUM_QOS => ("maximum_qos", Kind::Byte),
92        RETAIN_AVAILABLE => ("retain_available", Kind::Byte),
93        USER_PROPERTY => ("user_properties", Kind::Pair),
94        MAXIMUM_PACKET_SIZE => ("maximum_packet_size", Kind::U32),
95        WILDCARD_SUBSCRIPTION_AVAILABLE => ("wildcard_subscription_available", Kind::Byte),
96        SUBSCRIPTION_IDENTIFIER_AVAILABLE => ("subscription_identifier_available", Kind::Byte),
97        SHARED_SUBSCRIPTION_AVAILABLE => ("shared_subscription_available", Kind::Byte),
98        _ => return None,
99    })
100}
101
102/// One decoded property value.
103#[derive(Clone, Debug, PartialEq, Eq)]
104pub enum PropValue {
105    Int(u32),
106    Text(String),
107    Binary(Vec<u8>),
108    Pair(String, String),
109}
110
111/// A decoded property block, in wire order.
112#[derive(Clone, Debug, Default, PartialEq, Eq)]
113pub struct Properties {
114    pub entries: Vec<(u8, PropValue)>,
115}
116
117impl Properties {
118    pub fn is_empty(&self) -> bool {
119        self.entries.is_empty()
120    }
121
122    pub fn int(&self, id: u8) -> Option<u32> {
123        self.entries.iter().find_map(|(i, v)| match v {
124            PropValue::Int(n) if *i == id => Some(*n),
125            _ => None,
126        })
127    }
128
129    pub fn text(&self, id: u8) -> Option<&str> {
130        self.entries.iter().find_map(|(i, v)| match v {
131            PropValue::Text(s) if *i == id => Some(s.as_str()),
132            _ => None,
133        })
134    }
135
136    pub fn binary(&self, id: u8) -> Option<&[u8]> {
137        self.entries.iter().find_map(|(i, v)| match v {
138            PropValue::Binary(b) if *i == id => Some(b.as_slice()),
139            _ => None,
140        })
141    }
142
143    /// The block as the model sees it: one key per property, User Properties as a list of
144    /// `[name, value]` pairs (names may repeat, §3.1.2.11.8), Subscription Identifiers as a
145    /// list (a PUBLISH may carry several), binary values as text or hex.
146    pub fn to_json(&self) -> Value {
147        let mut out = Map::new();
148        let mut pairs: Vec<Value> = Vec::new();
149        let mut sub_ids: Vec<Value> = Vec::new();
150        for (id, value) in &self.entries {
151            let Some((name, _)) = describe(*id) else {
152                continue;
153            };
154            match value {
155                PropValue::Pair(k, v) => pairs.push(json!([k, v])),
156                PropValue::Int(n) if *id == SUBSCRIPTION_IDENTIFIER => sub_ids.push(json!(n)),
157                PropValue::Int(n) => {
158                    out.insert(name.to_string(), json!(n));
159                }
160                PropValue::Text(s) => {
161                    out.insert(name.to_string(), json!(s));
162                }
163                PropValue::Binary(b) => {
164                    let (text, encoding) = binary_to_text(b);
165                    out.insert(name.to_string(), json!(text));
166                    out.insert(format!("{name}_encoding"), json!(encoding));
167                }
168            }
169        }
170        if !pairs.is_empty() {
171            out.insert("user_properties".into(), Value::Array(pairs));
172        }
173        if !sub_ids.is_empty() {
174            out.insert("subscription_identifiers".into(), Value::Array(sub_ids));
175        }
176        Value::Object(out)
177    }
178}
179
180/// Render binary for the model: UTF-8 text when it is, hex otherwise.
181pub fn binary_to_text(bytes: &[u8]) -> (String, &'static str) {
182    match std::str::from_utf8(bytes) {
183        Ok(s) if !crate::utils::sanitize::has_controls(s) => (s.to_string(), "utf8"),
184        _ => (hex::encode(bytes), "hex"),
185    }
186}
187
188/// The inverse of [`binary_to_text`], with the encoding named by the caller rather than
189/// guessed: `"48656c6c6f"` is valid text and valid hex, and only the sender knows which.
190pub fn text_to_binary(text: &str, encoding: Option<&str>) -> Result<Vec<u8>, String> {
191    match encoding.unwrap_or("utf8") {
192        "utf8" => Ok(text.as_bytes().to_vec()),
193        "hex" => hex::decode(text).map_err(|e| format!("not valid hex: {e}")),
194        other => Err(format!(
195            "encoding must be \"utf8\" or \"hex\", not {other:?}"
196        )),
197    }
198}
199
200/// Decode a variable byte integer (§1.5.5) at `pos`, advancing it. At most four bytes.
201pub fn read_varint(buf: &[u8], pos: &mut usize) -> Option<u32> {
202    let mut value: u32 = 0;
203    for i in 0..4 {
204        let byte = *buf.get(*pos + i)?;
205        value |= u32::from(byte & 0x7F) << (7 * i);
206        if byte & 0x80 == 0 {
207            *pos += i + 1;
208            return Some(value);
209        }
210    }
211    None
212}
213
214/// Encode a variable byte integer. Values above 268 435 455 are not representable and are
215/// clamped by the caller's validation; this writes at most four bytes.
216pub fn write_varint(out: &mut Vec<u8>, mut value: u32) {
217    for _ in 0..4 {
218        let mut byte = (value % 128) as u8;
219        value /= 128;
220        if value > 0 {
221            byte |= 0x80;
222        }
223        out.push(byte);
224        if value == 0 {
225            break;
226        }
227    }
228}
229
230/// Largest value a variable byte integer can hold (§1.5.5).
231pub const VARINT_MAX: u32 = 268_435_455;
232
233fn read_u16(buf: &[u8], pos: &mut usize) -> Option<u16> {
234    let b = buf.get(*pos..*pos + 2)?;
235    *pos += 2;
236    Some(u16::from_be_bytes([b[0], b[1]]))
237}
238
239fn read_bin(buf: &[u8], pos: &mut usize) -> Option<Vec<u8>> {
240    let len = usize::from(read_u16(buf, pos)?);
241    let end = pos.checked_add(len)?;
242    let b = buf.get(*pos..end)?.to_vec();
243    *pos = end;
244    Some(b)
245}
246
247fn read_utf8(buf: &[u8], pos: &mut usize) -> Option<String> {
248    // §1.5.4: a UTF-8 Encoded String that is not well-formed is a Malformed Packet.
249    String::from_utf8(read_bin(buf, pos)?).ok()
250}
251
252/// Most User Properties one property block may carry.
253///
254/// The specification sets no count, so without one a single packet up to `max_packet_size`
255/// (16 MiB at most) could carry millions of five-byte pairs, each becoming an owned entry and
256/// then a JSON value in the model's event. Real clients send a handful.
257pub const MAX_USER_PROPERTIES: usize = 128;
258
259/// Why a property block was rejected. Each maps to the reason code a v5 DISCONNECT carries.
260#[derive(Clone, Debug, PartialEq, Eq)]
261pub enum PropertyError {
262    /// Truncated, an unknown identifier, an over-long varint or ill-formed UTF-8 (0x81).
263    Malformed(String),
264    /// A property that may appear once appeared twice (0x82, §2.2.2.2).
265    Duplicate(&'static str),
266    /// A well-formed property the packet may not carry, a value the specification calls a
267    /// Protocol Error, or more User Properties than [`MAX_USER_PROPERTIES`] (0x82).
268    Protocol(String),
269}
270
271impl std::fmt::Display for PropertyError {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        match self {
274            PropertyError::Malformed(m) => write!(f, "malformed properties: {m}"),
275            PropertyError::Duplicate(n) => write!(f, "property {n} appears more than once"),
276            PropertyError::Protocol(m) => write!(f, "invalid properties: {m}"),
277        }
278    }
279}
280
281/// Refuse a block carrying any of `ids`: properties §2.2.2.2 does not allow in `packet`.
282pub fn forbid(props: &Properties, ids: &[u8], packet: &str) -> Result<(), PropertyError> {
283    match props.entries.iter().find(|(id, _)| ids.contains(id)) {
284        Some((id, _)) => Err(PropertyError::Protocol(format!(
285            "{} is not allowed in {packet}",
286            describe(*id).map(|d| d.0).unwrap_or("property")
287        ))),
288        None => Ok(()),
289    }
290}
291
292/// Decode one property block a client sent at `pos` — its varint length, then exactly that
293/// many bytes of properties — advancing `pos` past it.
294///
295/// Besides the framing, it enforces what holds for every client-sent block: no identifier
296/// except User Property appears twice (Subscription Identifier repeats only in a PUBLISH the
297/// *server* sends, §3.3.2.3.8), a Subscription Identifier is never 0 (§3.8.2.1.2), and at most
298/// [`MAX_USER_PROPERTIES`] User Properties. Which identifiers a particular packet may carry is
299/// the caller's to check with [`forbid`].
300pub fn read_properties(buf: &[u8], pos: &mut usize) -> Result<Properties, PropertyError> {
301    let malformed = |m: &str| PropertyError::Malformed(m.to_string());
302    let len = read_varint(buf, pos).ok_or_else(|| malformed("property length"))? as usize;
303    let end = pos
304        .checked_add(len)
305        .filter(|e| *e <= buf.len())
306        .ok_or_else(|| malformed("property length runs past the packet"))?;
307    let block = &buf[*pos..end];
308    let mut p = 0usize;
309    let mut props = Properties::default();
310    let mut user_properties = 0usize;
311    while p < block.len() {
312        let id = read_varint(block, &mut p).ok_or_else(|| malformed("property identifier"))?;
313        let id = u8::try_from(id).map_err(|_| malformed("property identifier out of range"))?;
314        let (name, kind) =
315            describe(id).ok_or_else(|| malformed(&format!("unknown property 0x{id:02x}")))?;
316        let value = match kind {
317            Kind::Byte => PropValue::Int(u32::from(*block.get(p).ok_or_else(|| malformed(name))?)),
318            Kind::U16 => PropValue::Int(u32::from(
319                read_u16(block, &mut p).ok_or_else(|| malformed(name))?,
320            )),
321            Kind::U32 => {
322                let b = block.get(p..p + 4).ok_or_else(|| malformed(name))?;
323                p += 4;
324                PropValue::Int(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
325            }
326            Kind::Varint => {
327                PropValue::Int(read_varint(block, &mut p).ok_or_else(|| malformed(name))?)
328            }
329            Kind::Utf8 => PropValue::Text(read_utf8(block, &mut p).ok_or_else(|| malformed(name))?),
330            Kind::Binary => {
331                PropValue::Binary(read_bin(block, &mut p).ok_or_else(|| malformed(name))?)
332            }
333            Kind::Pair => {
334                let k = read_utf8(block, &mut p).ok_or_else(|| malformed(name))?;
335                let v = read_utf8(block, &mut p).ok_or_else(|| malformed(name))?;
336                PropValue::Pair(k, v)
337            }
338        };
339        if kind == Kind::Byte {
340            p += 1;
341        }
342        // User Property may repeat; everything else a client sends is "a Protocol Error to
343        // include ... more than once".
344        if id == USER_PROPERTY {
345            user_properties += 1;
346            if user_properties > MAX_USER_PROPERTIES {
347                return Err(PropertyError::Protocol(format!(
348                    "more than {MAX_USER_PROPERTIES} user properties"
349                )));
350            }
351        } else if props.entries.iter().any(|(i, _)| *i == id) {
352            return Err(PropertyError::Duplicate(name));
353        }
354        if id == SUBSCRIPTION_IDENTIFIER && value == PropValue::Int(0) {
355            return Err(PropertyError::Protocol(
356                "subscription identifier 0".to_string(),
357            ));
358        }
359        props.entries.push((id, value));
360    }
361    *pos = end;
362    Ok(props)
363}
364
365/// Builds one property block. [`PropertyWriter::finish`] prefixes the varint length.
366#[derive(Default, Debug, Clone)]
367pub struct PropertyWriter {
368    body: Vec<u8>,
369}
370
371impl PropertyWriter {
372    pub fn new() -> Self {
373        Self::default()
374    }
375
376    pub fn is_empty(&self) -> bool {
377        self.body.is_empty()
378    }
379
380    pub fn byte(&mut self, id: u8, v: u8) -> &mut Self {
381        self.body.extend_from_slice(&[id, v]);
382        self
383    }
384
385    pub fn u16(&mut self, id: u8, v: u16) -> &mut Self {
386        self.body.push(id);
387        self.body.extend_from_slice(&v.to_be_bytes());
388        self
389    }
390
391    pub fn u32(&mut self, id: u8, v: u32) -> &mut Self {
392        self.body.push(id);
393        self.body.extend_from_slice(&v.to_be_bytes());
394        self
395    }
396
397    pub fn varint(&mut self, id: u8, v: u32) -> &mut Self {
398        self.body.push(id);
399        write_varint(&mut self.body, v);
400        self
401    }
402
403    fn lp(&mut self, bytes: &[u8]) {
404        // Callers bound every string and binary they write to u16::MAX; see `validate_*`.
405        let len = bytes.len().min(usize::from(u16::MAX));
406        self.body.extend_from_slice(&(len as u16).to_be_bytes());
407        self.body.extend_from_slice(&bytes[..len]);
408    }
409
410    pub fn text(&mut self, id: u8, v: &str) -> &mut Self {
411        self.body.push(id);
412        self.lp(v.as_bytes());
413        self
414    }
415
416    pub fn binary(&mut self, id: u8, v: &[u8]) -> &mut Self {
417        self.body.push(id);
418        self.lp(v);
419        self
420    }
421
422    pub fn pair(&mut self, k: &str, v: &str) -> &mut Self {
423        self.body.push(USER_PROPERTY);
424        self.lp(k.as_bytes());
425        self.lp(v.as_bytes());
426        self
427    }
428
429    /// Append every entry of a decoded block (used to carry properties onward unchanged).
430    pub fn extend(&mut self, props: &Properties) -> &mut Self {
431        for (id, value) in &props.entries {
432            match (describe(*id).map(|d| d.1), value) {
433                (Some(Kind::Byte), PropValue::Int(n)) => self.byte(*id, *n as u8),
434                (Some(Kind::U16), PropValue::Int(n)) => self.u16(*id, *n as u16),
435                (Some(Kind::U32), PropValue::Int(n)) => self.u32(*id, *n),
436                (Some(Kind::Varint), PropValue::Int(n)) => self.varint(*id, *n),
437                (Some(Kind::Utf8), PropValue::Text(s)) => self.text(*id, s),
438                (Some(Kind::Binary), PropValue::Binary(b)) => self.binary(*id, b),
439                (Some(Kind::Pair), PropValue::Pair(k, v)) => self.pair(k, v),
440                _ => self,
441            };
442        }
443        self
444    }
445
446    /// Append another writer's properties after this one's.
447    pub fn append(&mut self, other: &PropertyWriter) -> &mut Self {
448        self.body.extend_from_slice(&other.body);
449        self
450    }
451
452    /// The block as it goes on the wire: varint length, then the properties.
453    pub fn finish(&self) -> Vec<u8> {
454        let mut out = Vec::with_capacity(self.body.len() + 4);
455        write_varint(&mut out, self.body.len() as u32);
456        out.extend_from_slice(&self.body);
457        out
458    }
459}
460
461/// Read a model-supplied `user_properties` field: a list of `[name, value]` pairs (the shape
462/// the events use, so a handler can echo one back) or an object of `name: value`.
463pub fn user_properties_from_json(v: Option<&Value>) -> Result<Vec<(String, String)>, String> {
464    let Some(v) = v else {
465        return Ok(Vec::new());
466    };
467    let as_text = |x: &Value| -> Result<String, String> {
468        match x {
469            Value::String(s) => Ok(s.clone()),
470            Value::Number(n) => Ok(n.to_string()),
471            Value::Bool(b) => Ok(b.to_string()),
472            other => Err(format!("user property values must be strings, not {other}")),
473        }
474    };
475    let pairs = match v {
476        Value::Null => Vec::new(),
477        Value::Object(map) => map
478            .iter()
479            .map(|(k, x)| Ok((k.clone(), as_text(x)?)))
480            .collect::<Result<Vec<_>, String>>()?,
481        Value::Array(items) => items
482            .iter()
483            .map(|item| match item {
484                Value::Array(kv) if kv.len() == 2 => Ok((as_text(&kv[0])?, as_text(&kv[1])?)),
485                Value::Object(o) => match (o.get("name").or(o.get("key")), o.get("value")) {
486                    (Some(k), Some(x)) => Ok((as_text(k)?, as_text(x)?)),
487                    _ => Err("each user property object needs \"name\" and \"value\"".into()),
488                },
489                other => Err(format!(
490                    "each user property is a [name, value] pair, not {other}"
491                )),
492            })
493            .collect::<Result<Vec<_>, String>>()?,
494        other => {
495            return Err(format!(
496                "user_properties is a list of [name, value] pairs or an object, not {other}"
497            ))
498        }
499    };
500    for (k, x) in &pairs {
501        if k.len() > usize::from(u16::MAX) || x.len() > usize::from(u16::MAX) {
502            return Err("a user property name or value is longer than 65535 bytes".into());
503        }
504    }
505    Ok(pairs)
506}
507
508// ---------------------------------------------------------------------------
509// Reason codes (§2.4)
510// ---------------------------------------------------------------------------
511
512/// CONNACK reason codes a server may send (§3.2.2.2).
513pub const CONNACK_REASON_CODES: &[(u8, &str)] = &[
514    (0x00, "success"),
515    (0x80, "unspecified error"),
516    (0x81, "malformed packet"),
517    (0x82, "protocol error"),
518    (0x83, "implementation specific error"),
519    (0x84, "unsupported protocol version"),
520    (0x85, "client identifier not valid"),
521    (0x86, "bad user name or password"),
522    (0x87, "not authorized"),
523    (0x88, "server unavailable"),
524    (0x89, "server busy"),
525    (0x8A, "banned"),
526    (0x8C, "bad authentication method"),
527    (0x90, "topic name invalid"),
528    (0x95, "packet too large"),
529    (0x97, "quota exceeded"),
530    (0x99, "payload format invalid"),
531    (0x9A, "retain not supported"),
532    (0x9B, "QoS not supported"),
533    (0x9C, "use another server"),
534    (0x9D, "server moved"),
535    (0x9F, "connection rate exceeded"),
536];
537
538/// SUBACK reason codes (§3.9.3): the granted QoS, or a refusal.
539pub const SUBACK_REASON_CODES: &[(u8, &str)] = &[
540    (0x00, "granted QoS 0"),
541    (0x01, "granted QoS 1"),
542    (0x02, "granted QoS 2"),
543    (0x80, "unspecified error"),
544    (0x83, "implementation specific error"),
545    (0x87, "not authorized"),
546    (0x8F, "topic filter invalid"),
547    (0x91, "packet identifier in use"),
548    (0x97, "quota exceeded"),
549    (0x9E, "shared subscriptions not supported"),
550    (0xA1, "subscription identifiers not supported"),
551    (0xA2, "wildcard subscriptions not supported"),
552];
553
554/// PUBACK and PUBREC reason codes (§3.4.2.1, §3.5.2.1).
555pub const PUBACK_REASON_CODES: &[(u8, &str)] = &[
556    (0x00, "success"),
557    (0x10, "no matching subscribers"),
558    (0x80, "unspecified error"),
559    (0x83, "implementation specific error"),
560    (0x87, "not authorized"),
561    (0x90, "topic name invalid"),
562    (0x91, "packet identifier in use"),
563    (0x97, "quota exceeded"),
564    (0x99, "payload format invalid"),
565];
566
567/// UNSUBACK reason codes (§3.11.3).
568pub const UNSUBACK_REASON_CODES: &[(u8, &str)] = &[
569    (0x00, "success"),
570    (0x11, "no subscription existed"),
571    (0x80, "unspecified error"),
572    (0x83, "implementation specific error"),
573    (0x87, "not authorized"),
574    (0x8F, "topic filter invalid"),
575    (0x91, "packet identifier in use"),
576];
577
578/// DISCONNECT reason codes a *server* may send (§3.14.2.1).
579pub const DISCONNECT_REASON_CODES: &[(u8, &str)] = &[
580    (0x00, "normal disconnection"),
581    (0x80, "unspecified error"),
582    (0x81, "malformed packet"),
583    (0x82, "protocol error"),
584    (0x83, "implementation specific error"),
585    (0x87, "not authorized"),
586    (0x89, "server busy"),
587    (0x8B, "server shutting down"),
588    (0x8D, "keep alive timeout"),
589    (0x8E, "session taken over"),
590    (0x8F, "topic filter invalid"),
591    (0x90, "topic name invalid"),
592    (0x93, "receive maximum exceeded"),
593    (0x94, "topic alias invalid"),
594    (0x95, "packet too large"),
595    (0x96, "message rate too high"),
596    (0x97, "quota exceeded"),
597    (0x98, "administrative action"),
598    (0x99, "payload format invalid"),
599    (0x9A, "retain not supported"),
600    (0x9B, "QoS not supported"),
601    (0x9C, "use another server"),
602    (0x9D, "server moved"),
603    (0x9E, "shared subscriptions not supported"),
604    (0x9F, "connection rate exceeded"),
605    (0xA0, "maximum connect time"),
606    (0xA1, "subscription identifiers not supported"),
607    (0xA2, "wildcard subscriptions not supported"),
608];
609
610/// Check `code` against one of the tables above, naming the valid ones on refusal.
611pub fn check_reason(code: u64, table: &[(u8, &str)], packet: &str) -> Result<u8, String> {
612    if let Ok(c) = u8::try_from(code) {
613        if table.iter().any(|(t, _)| *t == c) {
614            return Ok(c);
615        }
616    }
617    let valid: Vec<String> = table
618        .iter()
619        .map(|(c, n)| format!("{c} (0x{c:02x}, {n})"))
620        .collect();
621    Err(format!(
622        "{code} is not an MQTT 5 {packet} reason code; use one of: {}",
623        valid.join(", ")
624    ))
625}
626
627/// The name of a reason code in a table, for logs.
628pub fn reason_name(code: u8, table: &[(u8, &'static str)]) -> &'static str {
629    table
630        .iter()
631        .find(|(c, _)| *c == code)
632        .map(|(_, n)| *n)
633        .unwrap_or("unknown")
634}
635
636/// A 3.1.1 CONNACK return code (0–5) as the v5 reason code that means the same thing.
637pub fn connack_v311_to_v5(code: u8) -> u8 {
638    match code {
639        0 => 0x00,
640        1 => 0x84,
641        2 => 0x85,
642        3 => 0x88,
643        4 => 0x86,
644        _ => 0x87,
645    }
646}
647
648/// A v5 CONNACK reason code as the nearest 3.1.1 return code, for a handler that answered a
649/// 3.1.1 client with a v5 code. Every refusal stays a refusal: the codes with no 3.1.1 twin
650/// fall to 3 (server unavailable) when they describe the server's state and to 5 (not
651/// authorized) when they describe the client.
652pub fn connack_v5_to_v311(code: u8) -> u8 {
653    match code {
654        0x00 => 0,
655        0x84 => 1,
656        0x85 => 2,
657        0x86 | 0x8C => 4,
658        0x80 | 0x83 | 0x88 | 0x89 | 0x97 | 0x9C | 0x9D | 0x9F => 3,
659        _ => 5,
660    }
661}