use crate::WireError;
use bytes::{Bytes, BytesMut};
pub trait WireEncode {
fn encode(&self, buf: &mut BytesMut);
fn encoded_size(&self) -> usize;
#[must_use]
fn to_bytes(&self) -> Bytes {
let mut buf = BytesMut::with_capacity(self.encoded_size());
self.encode(&mut buf);
buf.freeze()
}
}
pub trait WireDecode: Sized {
fn decode(buf: &[u8]) -> Result<(Self, usize), WireError>;
fn decode_from(buf: &[u8]) -> Result<Self, WireError> {
Self::decode(buf).map(|(val, _)| val)
}
}
#[inline]
pub fn read_u8(buf: &[u8], offset: usize) -> Result<u8, WireError> {
buf.get(offset)
.copied()
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 1,
have: buf.len().saturating_sub(offset),
})
}
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn read_u16_le(buf: &[u8], offset: usize) -> Result<u16, WireError> {
let end = offset
.checked_add(2)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 2,
have: buf.len().saturating_sub(offset),
})?;
let slice = buf
.get(offset..end)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 2,
have: buf.len().saturating_sub(offset),
})?;
Ok(u16::from_le_bytes(
slice.try_into().expect("slice is exactly 2 bytes"),
))
}
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn read_u32_le(buf: &[u8], offset: usize) -> Result<u32, WireError> {
let end = offset
.checked_add(4)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 4,
have: buf.len().saturating_sub(offset),
})?;
let slice = buf
.get(offset..end)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 4,
have: buf.len().saturating_sub(offset),
})?;
Ok(u32::from_le_bytes(
slice.try_into().expect("slice is exactly 4 bytes"),
))
}
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn read_u64_le(buf: &[u8], offset: usize) -> Result<u64, WireError> {
let end = offset
.checked_add(8)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 8,
have: buf.len().saturating_sub(offset),
})?;
let slice = buf
.get(offset..end)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 8,
have: buf.len().saturating_sub(offset),
})?;
Ok(u64::from_le_bytes(
slice.try_into().expect("slice is exactly 8 bytes"),
))
}
#[inline]
pub fn read_str(buf: &[u8], offset: usize, len: usize) -> Result<String, WireError> {
let end = offset
.checked_add(len)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: len,
have: buf.len().saturating_sub(offset),
})?;
let slice = buf
.get(offset..end)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: len,
have: buf.len().saturating_sub(offset),
})?;
std::str::from_utf8(slice)
.map(str::to_string)
.map_err(|_| WireError::InvalidUtf8 { offset })
}
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn read_u128_le(buf: &[u8], offset: usize) -> Result<u128, WireError> {
let end = offset
.checked_add(16)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 16,
have: buf.len().saturating_sub(offset),
})?;
let slice = buf
.get(offset..end)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 16,
have: buf.len().saturating_sub(offset),
})?;
Ok(u128::from_le_bytes(
slice.try_into().expect("slice is exactly 16 bytes"),
))
}
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn read_f32_le(buf: &[u8], offset: usize) -> Result<f32, WireError> {
let end = offset
.checked_add(4)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 4,
have: buf.len().saturating_sub(offset),
})?;
let slice = buf
.get(offset..end)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: 4,
have: buf.len().saturating_sub(offset),
})?;
Ok(f32::from_le_bytes(
slice.try_into().expect("slice is exactly 4 bytes"),
))
}
#[inline]
pub fn read_bytes(buf: &[u8], offset: usize, len: usize) -> Result<&[u8], WireError> {
let end = offset
.checked_add(len)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: len,
have: buf.len().saturating_sub(offset),
})?;
buf.get(offset..end)
.ok_or_else(|| WireError::UnexpectedEof {
offset,
need: len,
have: buf.len().saturating_sub(offset),
})
}
#[must_use]
#[inline]
pub fn bounded_capacity(count: usize, remaining: usize, min_element_size: usize) -> usize {
count.min(remaining / min_element_size.max(1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bounded_capacity_limits_allocation() {
assert_eq!(bounded_capacity(1_000_000, 100, 10), 10);
assert_eq!(bounded_capacity(5, 100, 10), 5);
assert_eq!(bounded_capacity(10, 100, 10), 10);
assert_eq!(bounded_capacity(11, 100, 10), 10);
assert_eq!(bounded_capacity(0, 100, 10), 0);
assert_eq!(bounded_capacity(100, 0, 10), 0);
}
#[test]
fn bounded_capacity_zero_element_size_still_bounds_by_the_buffer() {
assert_eq!(bounded_capacity(1_000_000, 100, 0), 100);
}
}