use serde::Serialize;
use serde::de::DeserializeOwned;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub const MAX_MESSAGE_SIZE: u32 = 16 * 1024 * 1024;
const _: () = assert!(
MAX_MESSAGE_SIZE == 16 * 1024 * 1024,
"MAX_MESSAGE_SIZE must be exactly 16 MiB per ADR 013 §Wire Framing; any change (bump or shrink) requires an ADR amendment",
);
#[derive(Debug, thiserror::Error)]
pub enum FrameError {
#[error("frame I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("frame length {0} exceeds MAX_MESSAGE_SIZE ({MAX_MESSAGE_SIZE})")]
TooLarge(u32),
#[error("malformed varint length prefix")]
Varint,
#[error("postcard decode error: {0}")]
Decode(#[from] postcard::Error),
}
pub fn encode_message<M: Serialize>(msg: &M) -> Result<Vec<u8>, FrameError> {
Ok(postcard::to_allocvec(msg)?)
}
pub fn decode_message<M: DeserializeOwned>(frame: &[u8]) -> Result<(M, &[u8]), FrameError> {
Ok(postcard::take_from_bytes::<M>(frame)?)
}
pub trait TopLevelEnum {
const VARIANT_COUNT: u32;
}
pub fn is_unknown_variant<M: TopLevelEnum>(frame: &[u8]) -> bool {
matches!(leading_varint_u32(frame), Some(d) if d >= M::VARIANT_COUNT)
}
fn leading_varint_u32(frame: &[u8]) -> Option<u32> {
let mut result: u32 = 0;
for (i, &byte) in frame.iter().take(5).enumerate() {
let data = u32::from(byte & 0x7F);
if i == 4 && byte & 0x70 != 0 {
return None;
}
let shift = u32::try_from(i).ok()?.saturating_mul(7);
let shifted = data.checked_shl(shift)?;
result |= shifted;
if byte & 0x80 == 0 {
return Some(result);
}
}
None
}
pub fn parse_frame(buf: &[u8]) -> Result<Option<(usize, usize)>, FrameError> {
let Some((len, header_len)) = decode_varint_prefix(buf)? else {
return Ok(None);
};
if len > MAX_MESSAGE_SIZE {
return Err(FrameError::TooLarge(len));
}
let payload_len = len as usize;
if buf.len().saturating_sub(header_len) < payload_len {
return Ok(None);
}
Ok(Some((header_len, payload_len)))
}
fn decode_varint_prefix(buf: &[u8]) -> Result<Option<(u32, usize)>, FrameError> {
let mut result: u32 = 0;
for (i, &byte) in buf.iter().take(5).enumerate() {
let data = u32::from(byte & 0x7F);
if i == 4 && byte & 0x70 != 0 {
return Err(FrameError::Varint);
}
let shift = u32::try_from(i)
.map_err(|_| FrameError::Varint)?
.saturating_mul(7);
let shifted = data.checked_shl(shift).ok_or(FrameError::Varint)?;
result |= shifted;
if byte & 0x80 == 0 {
return Ok(Some((result, i + 1)));
}
}
if buf.len() >= 5 {
return Err(FrameError::Varint);
}
Ok(None)
}
pub async fn read_frame<R: AsyncRead + Unpin>(r: &mut R) -> Result<Vec<u8>, FrameError> {
let len = read_varint_u32(r).await?;
if len > MAX_MESSAGE_SIZE {
return Err(FrameError::TooLarge(len));
}
let mut buf = vec![0u8; len as usize];
r.read_exact(&mut buf).await?;
Ok(buf)
}
pub async fn write_frame<W: AsyncWrite + Unpin>(
w: &mut W,
payload: &[u8],
) -> Result<(), FrameError> {
let len = u32::try_from(payload.len()).map_err(|_| FrameError::TooLarge(u32::MAX))?;
if len > MAX_MESSAGE_SIZE {
return Err(FrameError::TooLarge(len));
}
write_varint_u32(w, len).await?;
w.write_all(payload).await?;
Ok(())
}
async fn read_varint_u32<R: AsyncRead + Unpin>(r: &mut R) -> Result<u32, FrameError> {
let mut result: u32 = 0;
for i in 0u32..5 {
let mut b = [0u8; 1];
r.read_exact(&mut b).await?;
let byte = b[0];
let data = u32::from(byte & 0x7F);
let shift = i.saturating_mul(7);
if i == 4 && byte & 0x70 != 0 {
return Err(FrameError::Varint);
}
let shifted = data.checked_shl(shift).ok_or(FrameError::Varint)?;
result |= shifted;
if byte & 0x80 == 0 {
return Ok(result);
}
}
Err(FrameError::Varint)
}
#[must_use]
pub(crate) fn encode_varint_u32(mut value: u32, out: &mut [u8; 5]) -> usize {
let mut idx = 0usize;
loop {
let byte = (value & 0x7F) as u8;
value >>= 7;
let last = value == 0;
let Some(slot) = out.get_mut(idx) else {
break;
};
*slot = if last { byte } else { byte | 0x80 };
idx = idx.saturating_add(1);
if last {
break;
}
}
debug_assert!(idx <= 5, "u32 varint must fit in 5 bytes, idx={idx}");
idx
}
async fn write_varint_u32<W: AsyncWrite + Unpin>(w: &mut W, value: u32) -> std::io::Result<()> {
let mut buf = [0u8; 5];
let idx = encode_varint_u32(value, &mut buf);
let Some(bytes) = buf.get(..idx) else {
return Err(std::io::Error::other(format!(
"varint length prefix wants {idx} bytes but only {} were encoded",
buf.len()
)));
};
w.write_all(bytes).await
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
mod proptests;
#[cfg(test)]
mod tests;