use bytes::{Buf, BufMut, Bytes, BytesMut};
use crate::error::{MqttError, MqttResult};
pub const MAX_VARINT: u32 = 268_435_455;
pub fn encode_varint(mut value: u32, out: &mut BytesMut) -> MqttResult<()> {
if value > MAX_VARINT {
return Err(MqttError::Protocol(format!(
"value {value} exceeds maximum variable byte integer {MAX_VARINT}"
)));
}
loop {
let mut byte = (value % 128) as u8;
value /= 128;
if value > 0 {
byte |= 0x80;
}
out.put_u8(byte);
if value == 0 {
break;
}
}
Ok(())
}
pub fn decode_varint(buf: &mut Bytes) -> MqttResult<Option<u32>> {
let mut value: u32 = 0;
for (i, &byte) in buf.iter().take(4).enumerate() {
value |= u32::from(byte & 0x7F) << (7 * i);
if byte & 0x80 == 0 {
buf.advance(i + 1);
return Ok(Some(value));
}
}
if buf.len() >= 4 {
return Err(MqttError::Protocol("variable byte integer too long".into()));
}
Ok(None) }
pub fn peek_varint(bytes: &[u8]) -> MqttResult<Option<(u32, usize)>> {
let mut value: u32 = 0;
for (i, &byte) in bytes.iter().take(4).enumerate() {
value |= u32::from(byte & 0x7F) << (7 * i);
if byte & 0x80 == 0 {
return Ok(Some((value, i + 1)));
}
}
if bytes.len() >= 4 {
return Err(MqttError::Protocol("variable byte integer too long".into()));
}
Ok(None)
}
pub fn varint_len(value: u32) -> usize {
match value {
0..=127 => 1,
128..=16_383 => 2,
16_384..=2_097_151 => 3,
_ => 4,
}
}
pub fn encode_utf8_string(s: &str, out: &mut BytesMut) -> MqttResult<()> {
if s.len() > u16::MAX as usize {
return Err(MqttError::Protocol(
"string too long for MQTT encoding".into(),
));
}
if s.contains('\0') {
return Err(MqttError::Protocol("UTF-8 string contains U+0000".into()));
}
out.put_u16(s.len() as u16);
out.put_slice(s.as_bytes());
Ok(())
}
pub fn decode_utf8_string(buf: &mut Bytes) -> MqttResult<String> {
if buf.remaining() < 2 {
return Err(MqttError::MalformedPacket("truncated string length".into()));
}
let len = buf.get_u16() as usize;
if buf.remaining() < len {
return Err(MqttError::MalformedPacket("truncated string body".into()));
}
let raw = buf.copy_to_bytes(len);
let s = String::from_utf8(raw.to_vec())
.map_err(|e| MqttError::MalformedPacket(format!("invalid UTF-8 string: {e}")))?;
if s.contains('\0') {
return Err(MqttError::MalformedPacket(
"UTF-8 string contains U+0000".into(),
));
}
Ok(s)
}
pub fn encode_binary(data: &[u8], out: &mut BytesMut) -> MqttResult<()> {
if data.len() > u16::MAX as usize {
return Err(MqttError::Protocol(
"binary data too long for MQTT encoding".into(),
));
}
out.put_u16(data.len() as u16);
out.put_slice(data);
Ok(())
}
pub fn decode_binary(buf: &mut Bytes) -> MqttResult<Bytes> {
if buf.remaining() < 2 {
return Err(MqttError::MalformedPacket("truncated binary length".into()));
}
let len = buf.get_u16() as usize;
if buf.remaining() < len {
return Err(MqttError::MalformedPacket("truncated binary body".into()));
}
Ok(buf.copy_to_bytes(len))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varint_roundtrip_boundaries() {
for value in [
0u32, 1, 127, 128, 16_383, 16_384, 2_097_151, 2_097_152, MAX_VARINT,
] {
let mut out = BytesMut::new();
encode_varint(value, &mut out).unwrap();
assert_eq!(out.len(), varint_len(value));
let mut bytes = out.freeze();
let decoded = decode_varint(&mut bytes).unwrap().unwrap();
assert_eq!(decoded, value);
}
}
#[test]
fn varint_rejects_overflow() {
let mut out = BytesMut::new();
assert!(encode_varint(MAX_VARINT + 1, &mut out).is_err());
}
#[test]
fn varint_overlong_encoding_is_an_error_not_an_overflow() {
let mut buf = Bytes::from_static(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]);
assert!(decode_varint(&mut buf).is_err());
let mut buf = Bytes::from_static(&[0x80, 0x80, 0x80, 0x80]);
assert!(decode_varint(&mut buf).is_err());
}
#[test]
fn varint_incomplete_returns_none() {
let mut buf = Bytes::from_static(&[0x80]);
assert_eq!(decode_varint(&mut buf).unwrap(), None);
}
#[test]
fn peek_varint_matches_decode_without_consuming() {
for value in [
0u32, 127, 128, 16_383, 16_384, 2_097_151, 2_097_152, MAX_VARINT,
] {
let mut out = BytesMut::new();
encode_varint(value, &mut out).unwrap();
out.extend_from_slice(b"trailing");
assert_eq!(
peek_varint(&out).unwrap(),
Some((value, varint_len(value))),
"value {value}"
);
}
assert_eq!(peek_varint(&[0x80]).unwrap(), None);
assert_eq!(peek_varint(&[]).unwrap(), None);
assert!(peek_varint(&[0x80, 0x80, 0x80, 0x80]).is_err());
}
#[test]
fn utf8_string_rejects_nul() {
assert!(encode_utf8_string("a\0b", &mut BytesMut::new()).is_err());
let raw = Bytes::from_static(&[0, 3, b'a', 0, b'b']);
assert!(decode_utf8_string(&mut raw.clone()).is_err());
}
#[test]
fn utf8_string_roundtrip() {
let mut out = BytesMut::new();
encode_utf8_string("hello/mqtt", &mut out).unwrap();
let mut bytes = out.freeze();
assert_eq!(decode_utf8_string(&mut bytes).unwrap(), "hello/mqtt");
}
#[test]
fn binary_roundtrip() {
let mut out = BytesMut::new();
encode_binary(&[1, 2, 3, 4], &mut out).unwrap();
let mut bytes = out.freeze();
assert_eq!(&decode_binary(&mut bytes).unwrap()[..], &[1, 2, 3, 4]);
}
#[test]
fn oversized_strings_and_binary_are_rejected_on_encode() {
let mut out = BytesMut::new();
assert!(encode_utf8_string(&"a".repeat(65_536), &mut out).is_err());
assert!(encode_binary(&vec![0u8; 65_536], &mut out).is_err());
assert!(out.is_empty());
}
}