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),
})
}
#[inline]
#[must_use]
pub fn capped_capacity(count: usize, remaining: usize, min_item_size: usize) -> usize {
if min_item_size == 0 {
return count;
}
count.min(remaining / min_item_size)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capped_capacity_limits_allocation() {
assert_eq!(capped_capacity(1_000_000, 100, 10), 10);
assert_eq!(capped_capacity(5, 100, 10), 5);
assert_eq!(capped_capacity(10, 100, 10), 10);
assert_eq!(capped_capacity(11, 100, 10), 10);
assert_eq!(capped_capacity(0, 100, 10), 0);
assert_eq!(capped_capacity(100, 0, 10), 0);
}
#[test]
fn capped_capacity_zero_item_size_returns_count() {
assert_eq!(capped_capacity(1_000_000, 100, 0), 1_000_000);
}
}