use alloc::vec::Vec;
use core::fmt;
use core::ops::Range;
pub const MESSAGE_MAGIC: u32 = 0x4e49_5043;
pub const VERSION_MAJOR: u16 = 1;
pub const VERSION_MINOR: u16 = 0;
pub const ENVELOPE_LEN: usize = 72;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Limits {
pub max_message_bytes: u32,
pub max_payload_bytes: u32,
pub max_records: u32,
pub max_allocation_bytes: u32,
}
pub struct DecodeContext<'a> {
limits: &'a Limits,
records_remaining: u32,
allocation_remaining: u32,
}
impl<'a> DecodeContext<'a> {
fn new(limits: &'a Limits) -> Self {
Self {
limits,
records_remaining: limits.max_records,
allocation_remaining: limits.max_allocation_bytes,
}
}
pub const fn limits(&self) -> &Limits {
self.limits
}
pub fn claim_records(&mut self, count: u32) -> Result<(), DecodeError> {
self.records_remaining = self
.records_remaining
.checked_sub(count)
.ok_or(DecodeError::LimitExceeded(LimitKind::Records))?;
Ok(())
}
pub fn claim_allocation(&mut self, bytes: u32) -> Result<(), DecodeError> {
self.allocation_remaining = self
.allocation_remaining
.checked_sub(bytes)
.ok_or(DecodeError::LimitExceeded(LimitKind::AllocationBytes))?;
Ok(())
}
pub fn copy_bytes(&mut self, source: &[u8]) -> Result<Vec<u8>, DecodeError> {
let len = u32::try_from(source.len()).map_err(|_| DecodeError::LengthOverflow)?;
self.claim_allocation(len)?;
Ok(source.to_vec())
}
}
impl Limits {
pub const fn new(
max_message_bytes: u32,
max_payload_bytes: u32,
max_records: u32,
max_allocation_bytes: u32,
) -> Self {
Self {
max_message_bytes,
max_payload_bytes,
max_records,
max_allocation_bytes,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Envelope {
pub kind: u32,
pub flags: u32,
pub generation: u64,
pub sequence: u64,
payload_len: u32,
}
impl Envelope {
pub const fn new(kind: u32, flags: u32, generation: u64, sequence: u64) -> Self {
Self {
kind,
flags,
generation,
sequence,
payload_len: 0,
}
}
pub const fn payload_len(self) -> u32 {
self.payload_len
}
pub fn total_len(self) -> Result<usize, EncodeError> {
ENVELOPE_LEN
.checked_add(self.payload_len as usize)
.ok_or(EncodeError::LengthOverflow)
}
fn with_payload_len(mut self, payload_len: usize) -> Result<Self, EncodeError> {
self.payload_len = u32::try_from(payload_len).map_err(|_| EncodeError::LengthOverflow)?;
Ok(self)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DecodedMessage<T> {
pub envelope: Envelope,
pub message: T,
}
pub trait Protocol {
type Message;
const SCHEMA_ID: [u8; 32];
fn encode_payload(
message: &Self::Message,
destination: &mut [u8],
) -> Result<usize, EncodeError>;
fn decode_payload(
source: &[u8],
context: &mut DecodeContext<'_>,
) -> Result<Self::Message, DecodeError>;
}
pub fn encode_message<P: Protocol>(
envelope: Envelope,
message: &P::Message,
destination: &mut [u8],
) -> Result<usize, EncodeError> {
if envelope.generation == 0 {
return Err(EncodeError::ZeroGeneration);
}
if envelope.sequence == 0 {
return Err(EncodeError::ZeroSequence);
}
if destination.len() < ENVELOPE_LEN {
return Err(EncodeError::DestinationTooSmall {
required: ENVELOPE_LEN,
actual: destination.len(),
});
}
let payload_len = P::encode_payload(message, &mut destination[ENVELOPE_LEN..])?;
let envelope = envelope.with_payload_len(payload_len)?;
let total_len = envelope.total_len()?;
if total_len > destination.len() {
return Err(EncodeError::DestinationTooSmall {
required: total_len,
actual: destination.len(),
});
}
encode_envelope::<P>(envelope, &mut destination[..ENVELOPE_LEN]);
Ok(total_len)
}
pub fn decode_message<P: Protocol>(
source: &[u8],
limits: &Limits,
) -> Result<DecodedMessage<P::Message>, DecodeError> {
if source.len() < ENVELOPE_LEN {
return Err(DecodeError::Truncated {
required: ENVELOPE_LEN,
actual: source.len(),
});
}
if source.len() > limits.max_message_bytes as usize {
return Err(DecodeError::LimitExceeded(LimitKind::MessageBytes));
}
let envelope = decode_envelope::<P>(&source[..ENVELOPE_LEN])?;
let payload_len = envelope.payload_len as usize;
if payload_len > limits.max_payload_bytes as usize {
return Err(DecodeError::LimitExceeded(LimitKind::PayloadBytes));
}
let total_len = ENVELOPE_LEN
.checked_add(payload_len)
.ok_or(DecodeError::LengthOverflow)?;
if total_len != source.len() {
return Err(DecodeError::NonCanonicalLength {
declared: total_len,
actual: source.len(),
});
}
let mut context = DecodeContext::new(limits);
let message = P::decode_payload(&source[ENVELOPE_LEN..], &mut context)?;
Ok(DecodedMessage { envelope, message })
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RelativeRange(Range<usize>);
impl RelativeRange {
pub fn new(offset: u32, len: u32, containing_len: usize) -> Result<Self, DecodeError> {
let start = offset as usize;
let end = start
.checked_add(len as usize)
.ok_or(DecodeError::LengthOverflow)?;
if end > containing_len {
return Err(DecodeError::RelativeRangeOutOfBounds {
offset,
len,
containing_len,
});
}
Ok(Self(start..end))
}
pub fn range(&self) -> Range<usize> {
self.0.clone()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncodeError {
DestinationTooSmall {
required: usize,
actual: usize,
},
LengthOverflow,
ZeroGeneration,
ZeroSequence,
Protocol(u16),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LimitKind {
MessageBytes,
PayloadBytes,
Records,
AllocationBytes,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecodeError {
Truncated {
required: usize,
actual: usize,
},
BadMagic(u32),
BadVersion {
major: u16,
minor: u16,
},
SchemaMismatch,
ZeroGeneration,
ZeroSequence,
NonCanonicalLength {
declared: usize,
actual: usize,
},
LengthOverflow,
RelativeRangeOutOfBounds {
offset: u32,
len: u32,
containing_len: usize,
},
LimitExceeded(LimitKind),
Protocol(u16),
}
impl fmt::Display for EncodeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "message encoding failed: {self:?}")
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "message decoding failed: {self:?}")
}
}
#[cfg(feature = "std")]
impl std::error::Error for EncodeError {}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {}
fn encode_envelope<P: Protocol>(envelope: Envelope, bytes: &mut [u8]) {
put_u32(bytes, 0, MESSAGE_MAGIC);
put_u16(bytes, 4, VERSION_MAJOR);
put_u16(bytes, 6, VERSION_MINOR);
put_u32(bytes, 8, envelope.kind);
put_u32(bytes, 12, envelope.flags);
put_u32(bytes, 16, envelope.payload_len);
put_u32(bytes, 20, ENVELOPE_LEN as u32);
put_u64(bytes, 24, envelope.generation);
put_u64(bytes, 32, envelope.sequence);
bytes[40..72].copy_from_slice(&P::SCHEMA_ID);
}
fn decode_envelope<P: Protocol>(bytes: &[u8]) -> Result<Envelope, DecodeError> {
let magic = get_u32(bytes, 0);
if magic != MESSAGE_MAGIC {
return Err(DecodeError::BadMagic(magic));
}
let major = get_u16(bytes, 4);
let minor = get_u16(bytes, 6);
if major != VERSION_MAJOR || minor != VERSION_MINOR {
return Err(DecodeError::BadVersion { major, minor });
}
let header_len = get_u32(bytes, 20);
if header_len != ENVELOPE_LEN as u32 {
return Err(DecodeError::NonCanonicalLength {
declared: header_len as usize,
actual: ENVELOPE_LEN,
});
}
if bytes[40..72] != P::SCHEMA_ID {
return Err(DecodeError::SchemaMismatch);
}
let generation = get_u64(bytes, 24);
if generation == 0 {
return Err(DecodeError::ZeroGeneration);
}
let sequence = get_u64(bytes, 32);
if sequence == 0 {
return Err(DecodeError::ZeroSequence);
}
Ok(Envelope {
kind: get_u32(bytes, 8),
flags: get_u32(bytes, 12),
generation,
sequence,
payload_len: get_u32(bytes, 16),
})
}
fn put_u16(bytes: &mut [u8], offset: usize, value: u16) {
bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
}
fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
}
fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
fn get_u16(bytes: &[u8], offset: usize) -> u16 {
u16::from_le_bytes(
bytes[offset..offset + 2]
.try_into()
.expect("fixed checked range"),
)
}
fn get_u32(bytes: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(
bytes[offset..offset + 4]
.try_into()
.expect("fixed checked range"),
)
}
fn get_u64(bytes: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(
bytes[offset..offset + 8]
.try_into()
.expect("fixed checked range"),
)
}
#[cfg(test)]
#[path = "codec_test.rs"]
mod tests;