1use serde_json::{json, Map, Value};
21
22pub 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58enum Kind {
59 Byte,
60 U16,
61 U32,
62 Varint,
63 Utf8,
64 Binary,
65 Pair,
66}
67
68fn 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#[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#[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 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
180pub 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
188pub 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
200pub 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
214pub 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
230pub 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 String::from_utf8(read_bin(buf, pos)?).ok()
250}
251
252pub const MAX_USER_PROPERTIES: usize = 128;
258
259#[derive(Clone, Debug, PartialEq, Eq)]
261pub enum PropertyError {
262 Malformed(String),
264 Duplicate(&'static str),
266 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
281pub 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
292pub 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 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#[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 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 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 pub fn append(&mut self, other: &PropertyWriter) -> &mut Self {
448 self.body.extend_from_slice(&other.body);
449 self
450 }
451
452 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
461pub 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
508pub 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
538pub 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
554pub 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
567pub 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
578pub 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
610pub 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
627pub 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
636pub 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
648pub 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}