use crate::error::WireError;
use crate::message_layout::{
MSG_CHECKSUM_OFFSET, MSG_ID_OFFSET, MSG_OFFSET_OFFSET, MSG_ORIGIN_TIMESTAMP_OFFSET,
MSG_PAYLOAD_LEN_OFFSET, MSG_TIMESTAMP_OFFSET, MSG_USER_HEADERS_LEN_OFFSET,
WIRE_MESSAGE_HEADER_SIZE,
};
#[inline]
fn u32_at(buf: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(
buf[offset..offset + 4]
.try_into()
.expect("slice is exactly 4 bytes"),
)
}
#[inline]
fn u64_at(buf: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(
buf[offset..offset + 8]
.try_into()
.expect("slice is exactly 8 bytes"),
)
}
#[inline]
fn u128_at(buf: &[u8], offset: usize) -> u128 {
u128::from_le_bytes(
buf[offset..offset + 16]
.try_into()
.expect("slice is exactly 16 bytes"),
)
}
fn validate_frame(buf: &[u8]) -> Result<(usize, usize, usize), WireError> {
if buf.len() < WIRE_MESSAGE_HEADER_SIZE {
return Err(WireError::UnexpectedEof {
offset: 0,
need: WIRE_MESSAGE_HEADER_SIZE,
have: buf.len(),
});
}
let user_headers_len = u32_at(buf, MSG_USER_HEADERS_LEN_OFFSET) as usize;
let payload_len = u32_at(buf, MSG_PAYLOAD_LEN_OFFSET) as usize;
let total = WIRE_MESSAGE_HEADER_SIZE
.checked_add(payload_len)
.and_then(|s| s.checked_add(user_headers_len))
.ok_or_else(|| WireError::Validation("message frame size overflow".to_string()))?;
if buf.len() < total {
return Err(WireError::UnexpectedEof {
offset: 0,
need: total,
have: buf.len(),
});
}
Ok((total, payload_len, user_headers_len))
}
pub struct WireMessageView<'a> {
buf: &'a [u8],
payload_len: usize,
user_headers_len: usize,
}
impl<'a> WireMessageView<'a> {
pub fn new(buf: &'a [u8]) -> Result<Self, WireError> {
let (total, payload_len, user_headers_len) = validate_frame(buf)?;
Ok(Self {
buf: &buf[..total],
payload_len,
user_headers_len,
})
}
#[must_use]
#[inline]
pub fn checksum(&self) -> u64 {
u64_at(self.buf, MSG_CHECKSUM_OFFSET)
}
#[must_use]
#[inline]
pub fn id(&self) -> u128 {
u128_at(self.buf, MSG_ID_OFFSET)
}
#[must_use]
#[inline]
pub fn offset(&self) -> u64 {
u64_at(self.buf, MSG_OFFSET_OFFSET)
}
#[must_use]
#[inline]
pub fn timestamp(&self) -> u64 {
u64_at(self.buf, MSG_TIMESTAMP_OFFSET)
}
#[must_use]
#[inline]
pub fn origin_timestamp(&self) -> u64 {
u64_at(self.buf, MSG_ORIGIN_TIMESTAMP_OFFSET)
}
#[must_use]
#[inline]
pub const fn payload_length(&self) -> usize {
self.payload_len
}
#[must_use]
#[inline]
pub const fn user_headers_length(&self) -> usize {
self.user_headers_len
}
#[must_use]
pub fn payload(&self) -> &'a [u8] {
&self.buf[WIRE_MESSAGE_HEADER_SIZE..WIRE_MESSAGE_HEADER_SIZE + self.payload_len]
}
#[must_use]
pub fn user_headers(&self) -> &'a [u8] {
let start = WIRE_MESSAGE_HEADER_SIZE + self.payload_len;
&self.buf[start..start + self.user_headers_len]
}
#[must_use]
#[inline]
pub const fn total_size(&self) -> usize {
self.buf.len()
}
#[must_use]
pub const fn as_bytes(&self) -> &'a [u8] {
self.buf
}
}
pub struct WireMessageViewMut<'a> {
buf: &'a mut [u8],
payload_len: usize,
user_headers_len: usize,
}
impl<'a> WireMessageViewMut<'a> {
pub fn new(buf: &'a mut [u8]) -> Result<Self, WireError> {
let (total, payload_len, user_headers_len) = validate_frame(buf)?;
let (frame, _) = buf.split_at_mut(total);
Ok(Self {
buf: frame,
payload_len,
user_headers_len,
})
}
#[must_use]
#[inline]
pub fn checksum(&self) -> u64 {
u64_at(self.buf, MSG_CHECKSUM_OFFSET)
}
#[must_use]
#[inline]
pub fn id(&self) -> u128 {
u128_at(self.buf, MSG_ID_OFFSET)
}
#[must_use]
#[inline]
pub fn offset(&self) -> u64 {
u64_at(self.buf, MSG_OFFSET_OFFSET)
}
#[must_use]
#[inline]
pub fn timestamp(&self) -> u64 {
u64_at(self.buf, MSG_TIMESTAMP_OFFSET)
}
#[must_use]
#[inline]
pub fn origin_timestamp(&self) -> u64 {
u64_at(self.buf, MSG_ORIGIN_TIMESTAMP_OFFSET)
}
#[must_use]
#[inline]
pub const fn payload_length(&self) -> usize {
self.payload_len
}
#[must_use]
#[inline]
pub const fn user_headers_length(&self) -> usize {
self.user_headers_len
}
#[must_use]
pub fn payload(&self) -> &[u8] {
&self.buf[WIRE_MESSAGE_HEADER_SIZE..WIRE_MESSAGE_HEADER_SIZE + self.payload_len]
}
#[must_use]
pub fn user_headers(&self) -> &[u8] {
let start = WIRE_MESSAGE_HEADER_SIZE + self.payload_len;
&self.buf[start..start + self.user_headers_len]
}
#[must_use]
#[inline]
pub const fn total_size(&self) -> usize {
self.buf.len()
}
pub fn set_checksum(&mut self, value: u64) {
self.buf[MSG_CHECKSUM_OFFSET..MSG_CHECKSUM_OFFSET + 8]
.copy_from_slice(&value.to_le_bytes());
}
pub fn set_id(&mut self, value: u128) {
self.buf[MSG_ID_OFFSET..MSG_ID_OFFSET + 16].copy_from_slice(&value.to_le_bytes());
}
pub fn set_offset(&mut self, value: u64) {
self.buf[MSG_OFFSET_OFFSET..MSG_OFFSET_OFFSET + 8].copy_from_slice(&value.to_le_bytes());
}
pub fn set_timestamp(&mut self, value: u64) {
self.buf[MSG_TIMESTAMP_OFFSET..MSG_TIMESTAMP_OFFSET + 8]
.copy_from_slice(&value.to_le_bytes());
}
}
pub struct WireMessageIterator<'a> {
buf: &'a [u8],
pos: usize,
remaining: u32,
}
impl<'a> WireMessageIterator<'a> {
#[must_use]
pub const fn new(buf: &'a [u8], count: u32) -> Self {
Self {
buf,
pos: 0,
remaining: count,
}
}
}
impl<'a> Iterator for WireMessageIterator<'a> {
type Item = Result<WireMessageView<'a>, WireError>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
match WireMessageView::new(&self.buf[self.pos..]) {
Ok(view) => {
self.pos += view.total_size();
self.remaining -= 1;
Some(Ok(view))
}
Err(e) => {
self.remaining = 0;
Some(Err(e))
}
}
}
}
pub struct WireMessageIteratorMut<'a> {
buf: &'a mut [u8],
pos: usize,
remaining: u32,
}
impl<'a> WireMessageIteratorMut<'a> {
pub const fn new(buf: &'a mut [u8], count: u32) -> Self {
Self {
buf,
pos: 0,
remaining: count,
}
}
pub fn next_view_mut(&mut self) -> Option<Result<WireMessageViewMut<'_>, WireError>> {
if self.remaining == 0 {
return None;
}
let (total, available) = {
let rest = &self.buf[self.pos..];
if rest.len() < WIRE_MESSAGE_HEADER_SIZE {
self.remaining = 0;
return Some(Err(WireError::UnexpectedEof {
offset: self.pos,
need: WIRE_MESSAGE_HEADER_SIZE,
have: rest.len(),
}));
}
let user_headers_len = u32_at(rest, MSG_USER_HEADERS_LEN_OFFSET) as usize;
let payload_len = u32_at(rest, MSG_PAYLOAD_LEN_OFFSET) as usize;
let Some(total) = WIRE_MESSAGE_HEADER_SIZE
.checked_add(payload_len)
.and_then(|s| s.checked_add(user_headers_len))
else {
self.remaining = 0;
return Some(Err(WireError::Validation(
"message frame size overflow".to_string(),
)));
};
(total, rest.len())
};
if available < total {
self.remaining = 0;
return Some(Err(WireError::UnexpectedEof {
offset: self.pos,
need: total,
have: available,
}));
}
let start = self.pos;
self.pos += total;
self.remaining -= 1;
Some(WireMessageViewMut::new(&mut self.buf[start..start + total]))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message_layout::{
MSG_ID_OFFSET, MSG_ORIGIN_TIMESTAMP_OFFSET, MSG_PAYLOAD_LEN_OFFSET,
MSG_USER_HEADERS_LEN_OFFSET, WIRE_MESSAGE_HEADER_SIZE,
};
fn make_frame(payload: &[u8], user_headers: &[u8], id: u128, origin_ts: u64) -> Vec<u8> {
let total = WIRE_MESSAGE_HEADER_SIZE + payload.len() + user_headers.len();
let mut frame = vec![0u8; total];
frame[MSG_ID_OFFSET..MSG_ID_OFFSET + 16].copy_from_slice(&id.to_le_bytes());
frame[MSG_ORIGIN_TIMESTAMP_OFFSET..MSG_ORIGIN_TIMESTAMP_OFFSET + 8]
.copy_from_slice(&origin_ts.to_le_bytes());
#[allow(clippy::cast_possible_truncation)]
{
frame[MSG_USER_HEADERS_LEN_OFFSET..MSG_USER_HEADERS_LEN_OFFSET + 4]
.copy_from_slice(&(user_headers.len() as u32).to_le_bytes());
frame[MSG_PAYLOAD_LEN_OFFSET..MSG_PAYLOAD_LEN_OFFSET + 4]
.copy_from_slice(&(payload.len() as u32).to_le_bytes());
}
frame[WIRE_MESSAGE_HEADER_SIZE..WIRE_MESSAGE_HEADER_SIZE + payload.len()]
.copy_from_slice(payload);
frame[WIRE_MESSAGE_HEADER_SIZE + payload.len()..].copy_from_slice(user_headers);
frame
}
#[test]
fn view_rejects_short_buffer() {
let buf = [0u8; WIRE_MESSAGE_HEADER_SIZE - 1];
assert!(WireMessageView::new(&buf).is_err());
}
#[test]
fn view_rejects_truncated_payload() {
let mut buf = vec![0u8; WIRE_MESSAGE_HEADER_SIZE + 5];
buf[MSG_PAYLOAD_LEN_OFFSET..MSG_PAYLOAD_LEN_OFFSET + 4]
.copy_from_slice(&10u32.to_le_bytes());
assert!(WireMessageView::new(&buf).is_err());
}
#[test]
fn view_accessors() {
let frame = make_frame(b"hello", b"hdr", 42, 999);
let view = WireMessageView::new(&frame).unwrap();
assert_eq!(view.checksum(), 0);
assert_eq!(view.id(), 42);
assert_eq!(view.offset(), 0);
assert_eq!(view.timestamp(), 0);
assert_eq!(view.origin_timestamp(), 999);
assert_eq!(view.payload_length(), 5);
assert_eq!(view.user_headers_length(), 3);
assert_eq!(view.payload(), b"hello");
assert_eq!(view.user_headers(), b"hdr");
assert_eq!(view.total_size(), WIRE_MESSAGE_HEADER_SIZE + 5 + 3);
assert_eq!(view.as_bytes().len(), view.total_size());
}
#[test]
fn view_empty_payload_and_headers() {
let frame = make_frame(b"", b"", 1, 0);
let view = WireMessageView::new(&frame).unwrap();
assert_eq!(view.payload(), b"");
assert_eq!(view.user_headers(), b"");
assert_eq!(view.total_size(), WIRE_MESSAGE_HEADER_SIZE);
}
#[test]
fn view_from_larger_buffer() {
let mut buf = make_frame(b"pay", b"", 1, 0);
buf.extend_from_slice(b"trailing_garbage");
let view = WireMessageView::new(&buf).unwrap();
assert_eq!(view.total_size(), WIRE_MESSAGE_HEADER_SIZE + 3);
assert_eq!(view.as_bytes().len(), WIRE_MESSAGE_HEADER_SIZE + 3);
}
#[test]
fn view_mut_set_get_roundtrip() {
let mut frame = make_frame(b"data", b"", 1, 0);
let mut view = WireMessageViewMut::new(&mut frame).unwrap();
view.set_checksum(0xDEAD_BEEF);
view.set_id(0x1234);
view.set_offset(100);
view.set_timestamp(200);
assert_eq!(view.checksum(), 0xDEAD_BEEF);
assert_eq!(view.id(), 0x1234);
assert_eq!(view.offset(), 100);
assert_eq!(view.timestamp(), 200);
assert_eq!(view.origin_timestamp(), 0);
assert_eq!(view.payload(), b"data");
}
#[test]
fn view_mut_set_does_not_corrupt_adjacent_fields() {
let mut frame = make_frame(b"x", b"y", 42, 999);
{
let mut view = WireMessageViewMut::new(&mut frame).unwrap();
view.set_offset(0xFFFF_FFFF_FFFF_FFFF);
}
let view = WireMessageView::new(&frame).unwrap();
assert_eq!(view.id(), 42);
assert_eq!(view.offset(), 0xFFFF_FFFF_FFFF_FFFF);
assert_eq!(view.timestamp(), 0);
assert_eq!(view.origin_timestamp(), 999);
assert_eq!(view.payload(), b"x");
assert_eq!(view.user_headers(), b"y");
}
#[test]
fn iterator_zero_count() {
let buf = [];
let mut iter = WireMessageIterator::new(&buf, 0);
assert!(iter.next().is_none());
}
#[test]
fn iterator_single_message() {
let frame = make_frame(b"hello", b"", 1, 100);
let mut iter = WireMessageIterator::new(&frame, 1);
let view = iter.next().unwrap().unwrap();
assert_eq!(view.id(), 1);
assert_eq!(view.payload(), b"hello");
assert!(iter.next().is_none());
}
#[test]
fn iterator_multiple_messages() {
let f1 = make_frame(b"aaa", b"", 1, 100);
let f2 = make_frame(b"bbb", b"hh", 2, 200);
let f3 = make_frame(b"c", b"", 3, 300);
let mut buf = Vec::new();
buf.extend_from_slice(&f1);
buf.extend_from_slice(&f2);
buf.extend_from_slice(&f3);
let views: Vec<_> = WireMessageIterator::new(&buf, 3)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(views.len(), 3);
assert_eq!(views[0].id(), 1);
assert_eq!(views[0].payload(), b"aaa");
assert_eq!(views[1].id(), 2);
assert_eq!(views[1].payload(), b"bbb");
assert_eq!(views[1].user_headers(), b"hh");
assert_eq!(views[2].id(), 3);
assert_eq!(views[2].payload(), b"c");
}
#[test]
fn iterator_truncated_mid_frame() {
let frame = make_frame(b"hello", b"", 1, 0);
let truncated = &frame[..frame.len() / 2];
let mut iter = WireMessageIterator::new(truncated, 1);
assert!(iter.next().unwrap().is_err());
assert!(iter.next().is_none());
}
#[test]
fn iterator_mut_mutate_then_read() {
let f1 = make_frame(b"aaa", b"", 1, 100);
let f2 = make_frame(b"bbb", b"", 2, 200);
let mut buf = Vec::new();
buf.extend_from_slice(&f1);
buf.extend_from_slice(&f2);
let mut iter = WireMessageIteratorMut::new(&mut buf, 2);
{
let mut view = iter.next_view_mut().unwrap().unwrap();
view.set_offset(10);
view.set_timestamp(1000);
}
{
let mut view = iter.next_view_mut().unwrap().unwrap();
view.set_offset(20);
view.set_timestamp(2000);
}
assert!(iter.next_view_mut().is_none());
let views: Vec<_> = WireMessageIterator::new(&buf, 2)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(views[0].offset(), 10);
assert_eq!(views[0].timestamp(), 1000);
assert_eq!(views[0].payload(), b"aaa");
assert_eq!(views[1].offset(), 20);
assert_eq!(views[1].timestamp(), 2000);
assert_eq!(views[1].payload(), b"bbb");
}
#[test]
fn iterator_mut_count_matches() {
let f1 = make_frame(b"a", b"", 1, 0);
let f2 = make_frame(b"b", b"", 2, 0);
let mut buf = Vec::new();
buf.extend_from_slice(&f1);
buf.extend_from_slice(&f2);
let mut iter = WireMessageIteratorMut::new(&mut buf, 2);
let mut count = 0;
while let Some(Ok(_)) = iter.next_view_mut() {
count += 1;
}
assert_eq!(count, 2);
}
}