use std::io;
use super::format::Endianness;
use crate::byte_order::{
read_network_u64,
write_network_u16,
write_network_u32,
write_network_u64,
};
pub(crate) const ERR_UNSUPPORTED_PREFIX: &str = "unsupported length prefix size";
pub(crate) const ERR_FRAME_TOO_LARGE: &str = "frame too large";
pub(crate) const ERR_INCOMPLETE_PREFIX: &str = "incomplete length prefix";
#[inline]
fn u64_from_le_bytes(bytes: [u8; 8]) -> u64 {
#[expect(
clippy::little_endian_bytes,
reason = "Wire endianness is explicit; from_le_bytes keeps decoding host-independent."
)]
u64::from_le_bytes(bytes)
}
fn checked_prefix_cast<T: TryFrom<usize>>(len: usize) -> io::Result<T> {
T::try_from(len).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, ERR_FRAME_TOO_LARGE))
}
pub fn bytes_to_u64(bytes: &[u8], size: usize, endianness: Endianness) -> io::Result<u64> {
if !matches!(size, 1 | 2 | 4 | 8) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
ERR_UNSUPPORTED_PREFIX,
));
}
if bytes.len() < size {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
ERR_INCOMPLETE_PREFIX,
));
}
let mut buf = [0u8; 8];
let prefix = bytes
.get(..size)
.ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, ERR_INCOMPLETE_PREFIX))?;
match endianness {
Endianness::Big => {
if let Some(dst) = buf.get_mut(8 - size..) {
dst.copy_from_slice(prefix);
} else {
debug_assert!(false, "validated size should fit into prefix buffer");
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
ERR_UNSUPPORTED_PREFIX,
));
}
}
Endianness::Little => {
if let Some(dst) = buf.get_mut(..size) {
dst.copy_from_slice(prefix);
} else {
debug_assert!(false, "validated size should fit into prefix buffer");
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
ERR_UNSUPPORTED_PREFIX,
));
}
}
}
let val = match endianness {
Endianness::Big => read_network_u64(buf),
Endianness::Little => u64_from_le_bytes(buf),
};
Ok(val)
}
fn convert_len_to_value(len: usize, size: usize) -> io::Result<u64> {
let value = match size {
1 => u64::from(checked_prefix_cast::<u8>(len)?),
2 => u64::from(checked_prefix_cast::<u16>(len)?),
4 => u64::from(checked_prefix_cast::<u32>(len)?),
8 => checked_prefix_cast(len)?,
_ => {
debug_assert!(false, "size should be validated upstream");
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
ERR_UNSUPPORTED_PREFIX,
));
}
};
Ok(value)
}
fn write_bytes_with_endianness(value: u64, endianness: Endianness, prefix: &mut [u8]) {
let size = prefix.len();
match endianness {
Endianness::Big => write_big_endian_prefix(value, size, prefix),
Endianness::Little => write_little_endian_prefix(value, prefix),
}
}
fn write_big_endian_prefix(value: u64, size: usize, prefix: &mut [u8]) {
#[expect(
clippy::cast_possible_truncation,
reason = "caller validates value fits in prefix via checked_prefix_cast"
)]
match size {
1 => prefix.copy_from_slice(&[value as u8]),
2 => prefix.copy_from_slice(&write_network_u16(value as u16)),
4 => prefix.copy_from_slice(&write_network_u32(value as u32)),
8 => prefix.copy_from_slice(&write_network_u64(value)),
_ => debug_assert!(false, "size validated upstream to be 1, 2, 4, or 8"),
}
}
fn write_little_endian_prefix(value: u64, prefix: &mut [u8]) {
for (i, byte) in prefix.iter_mut().enumerate() {
let shift = 8 * i;
*byte = ((value >> shift) & 0xff) as u8;
}
}
#[must_use = "length prefix byte count must be used"]
pub fn u64_to_bytes(
len: usize,
size: usize,
endianness: Endianness,
out: &mut [u8; 8],
) -> io::Result<usize> {
if !matches!(size, 1 | 2 | 4 | 8) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
ERR_UNSUPPORTED_PREFIX,
));
}
let value = convert_len_to_value(len, size)?;
#[expect(
clippy::indexing_slicing,
reason = "size validated to be within the 8-byte prefix buffer"
)]
let prefix = &mut out[..size];
write_bytes_with_endianness(value, endianness, prefix);
if let Some(tail) = out.get_mut(size..) {
tail.fill(0);
}
Ok(size)
}