use std::io;
use crc32fast::Hasher;
use pi_result::InteropResultExt;
pub const DEFAULT_MAGIC_BYTES: [u8; 4] = *b"pial";
pub const DEFAULT_MAGIC: u32 = u32::from_le_bytes(DEFAULT_MAGIC_BYTES);
pub const DEFAULT_VERSION: u16 = 1;
pub const DEFAULT_BODY_FIXED_LEN: usize = 16;
pub const DEFAULT_ENCODED_FIXED_LEN: usize = 28;
pub const DEFAULT_MAX_PAYLOAD_LEN: usize = 4 * 1024 * 1024;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DefaultBlockCodec {
max_payload_len: usize,
}
impl DefaultBlockCodec {
pub fn new(max_payload_len: usize) -> Self {
Self { max_payload_len }
}
pub fn max_payload_len(&self) -> usize {
self.max_payload_len
}
}
impl Default for DefaultBlockCodec {
fn default() -> Self {
Self::new(DEFAULT_MAX_PAYLOAD_LEN)
}
}
impl BlockEncoder for DefaultBlockCodec {
type Block = Vec<u8>;
fn encode(&self, block_seq: u64, flags: u16, payload: &[u8]) -> pi_result::Result<Self::Block> {
if block_seq == 0 {
return Err(invalid_input("block sequence must be greater than zero"));
}
if flags != 0 {
return Err(invalid_input("V1 does not support non-zero flags"));
}
validate_payload_len(self.max_payload_len, payload.len())?;
let body_len = DEFAULT_BODY_FIXED_LEN
.checked_add(payload.len())
.ok_or_else(|| invalid_input("body length overflow"))?;
let body_len_u32 = u32::try_from(body_len)
.map_err(|_| invalid_input("body length does not fit in u32"))?;
let encoded_len = body_len
.checked_add(12)
.ok_or_else(|| invalid_input("encoded length overflow"))?;
let mut encoded = Vec::with_capacity(encoded_len);
encoded.extend_from_slice(&body_len_u32.to_le_bytes());
encoded.extend_from_slice(&DEFAULT_MAGIC_BYTES);
encoded.extend_from_slice(&DEFAULT_VERSION.to_le_bytes());
encoded.extend_from_slice(&flags.to_le_bytes());
encoded.extend_from_slice(&block_seq.to_le_bytes());
encoded.extend_from_slice(payload);
encoded.extend_from_slice(&body_len_u32.to_le_bytes());
encoded.extend_from_slice(&crc32(&encoded).to_le_bytes());
Ok(encoded)
}
}
impl BlockDecoder for DefaultBlockCodec {
type Block = Vec<u8>;
fn decode_forward<'a>(&self, input: &'a [u8]) -> pi_result::Result<DecodedBlock<'a>> {
if input.len() < DEFAULT_ENCODED_FIXED_LEN {
return Err(invalid_data("input is shorter than the V1 envelope"));
}
let body_len = read_u32(input, 0)? as usize;
let encoded_len = checked_encoded_len(body_len)?;
if encoded_len > input.len() {
return Err(invalid_data("input does not contain a complete block"));
}
decode_exact(self.max_payload_len, &input[..encoded_len])
}
fn decode_backward<'a>(&self, input: &'a [u8]) -> pi_result::Result<DecodedBlock<'a>> {
if input.len() < DEFAULT_ENCODED_FIXED_LEN {
return Err(invalid_data("input is shorter than the V1 envelope"));
}
let trailer_start = input.len() - 8;
let body_len = read_u32(input, trailer_start)? as usize;
let encoded_len = checked_encoded_len(body_len)?;
if encoded_len > input.len() {
return Err(invalid_data("input does not contain a complete block"));
}
decode_exact(self.max_payload_len, &input[input.len() - encoded_len..])
}
fn find_last_complete(&self, input: &[u8]) -> pi_result::Result<Option<usize>> {
if input.len() < DEFAULT_ENCODED_FIXED_LEN {
return Ok(None);
}
let mut search_end = input.len();
while search_end >= DEFAULT_MAGIC_BYTES.len() {
let Some(relative_magic) = input[..search_end]
.windows(DEFAULT_MAGIC_BYTES.len())
.rposition(|window| window == DEFAULT_MAGIC_BYTES)
else {
break;
};
let magic_start = relative_magic;
let Some(prefix_start) = magic_start.checked_sub(4) else {
search_end = magic_start;
continue;
};
let Ok(body_len) = read_u32(input, prefix_start).map(|length| length as usize) else {
search_end = magic_start;
continue;
};
let Ok(encoded_len) = checked_encoded_len(body_len) else {
search_end = magic_start;
continue;
};
let Some(candidate_end) = prefix_start.checked_add(encoded_len) else {
search_end = magic_start;
continue;
};
if candidate_end <= input.len()
&& decode_exact(self.max_payload_len, &input[prefix_start..candidate_end]).is_ok()
{
return Ok(Some(candidate_end));
}
search_end = magic_start;
}
Ok(None)
}
}
fn checked_encoded_len(body_len: usize) -> pi_result::Result<usize> {
if body_len < DEFAULT_BODY_FIXED_LEN {
return Err(invalid_data(
"body length is smaller than the V1 fixed body",
));
}
body_len
.checked_add(12)
.ok_or_else(|| invalid_data("encoded length overflow"))
}
fn decode_exact<'a>(
max_payload_len: usize,
input: &'a [u8],
) -> pi_result::Result<DecodedBlock<'a>> {
let body_len = read_u32(input, 0)? as usize;
let encoded_len = checked_encoded_len(body_len)?;
if encoded_len != input.len() {
return Err(invalid_data("encoded length does not match input"));
}
let payload_len = body_len - DEFAULT_BODY_FIXED_LEN;
validate_payload_len(max_payload_len, payload_len)
.map_err(|error| invalid_data(&error.to_string()))?;
if input[4..8] != DEFAULT_MAGIC_BYTES {
return Err(invalid_data("invalid V1 magic"));
}
if read_u16(input, 8)? != DEFAULT_VERSION {
return Err(invalid_data("unsupported V1 version"));
}
let flags = read_u16(input, 10)?;
if flags != 0 {
return Err(invalid_data("V1 does not support non-zero flags"));
}
let block_seq = read_u64(input, 12)?;
if block_seq == 0 {
return Err(invalid_data("block sequence must be greater than zero"));
}
let suffix_start = 20 + payload_len;
if read_u32(input, suffix_start)? as usize != body_len {
return Err(invalid_data("prefix and suffix body lengths differ"));
}
let stored_crc = read_u32(input, suffix_start + 4)?;
if stored_crc != crc32(&input[..input.len() - 4]) {
return Err(invalid_data("CRC32 validation failed"));
}
Ok(DecodedBlock {
encoded: input,
payload: &input[20..suffix_start],
block_seq,
flags,
})
}
fn read_u16(input: &[u8], offset: usize) -> pi_result::Result<u16> {
let bytes = input
.get(offset..offset + 2)
.ok_or_else(|| invalid_data("truncated u16"))?;
Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
}
fn read_u32(input: &[u8], offset: usize) -> pi_result::Result<u32> {
let bytes = input
.get(offset..offset + 4)
.ok_or_else(|| invalid_data("truncated u32"))?;
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
fn read_u64(input: &[u8], offset: usize) -> pi_result::Result<u64> {
let bytes = input
.get(offset..offset + 8)
.ok_or_else(|| invalid_data("truncated u64"))?;
let array: [u8; 8] = bytes
.try_into()
.map_err(|_| invalid_data("truncated u64"))?;
Ok(u64::from_le_bytes(array))
}
fn invalid_data(message: &str) -> pi_result::Error {
Err::<(), _>(io::Error::new(io::ErrorKind::InvalidData, message))
.into_classified_error()
.expect_err("invalid data conversion must fail")
}
fn validate_payload_len(max_payload_len: usize, payload_len: usize) -> pi_result::Result<()> {
if payload_len > max_payload_len {
return Err(invalid_input("payload exceeds configured maximum"));
}
if payload_len > (u32::MAX as usize).saturating_sub(DEFAULT_BODY_FIXED_LEN) {
return Err(invalid_input("payload cannot be represented by V1"));
}
Ok(())
}
fn crc32(bytes: &[u8]) -> u32 {
let mut hasher = Hasher::new();
hasher.update(bytes);
hasher.finalize()
}
fn invalid_input(message: &str) -> pi_result::Error {
Err::<(), _>(io::Error::new(io::ErrorKind::InvalidInput, message))
.into_classified_error()
.expect_err("invalid input conversion must fail")
}
pub trait BlockEncoder: Send + Sync {
type Block: AsRef<[u8]> + Clone + Send + Sync + 'static;
fn encode(&self, block_seq: u64, flags: u16, payload: &[u8]) -> pi_result::Result<Self::Block>;
}
pub trait BlockDecoder: Send + Sync {
type Block: AsRef<[u8]> + Clone + Send + Sync + 'static;
fn decode_forward<'a>(&self, input: &'a [u8]) -> pi_result::Result<DecodedBlock<'a>>;
fn decode_backward<'a>(&self, input: &'a [u8]) -> pi_result::Result<DecodedBlock<'a>>;
fn find_last_complete(&self, input: &[u8]) -> pi_result::Result<Option<usize>>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DecodedBlock<'a> {
encoded: &'a [u8],
payload: &'a [u8],
block_seq: u64,
flags: u16,
}
impl<'a> DecodedBlock<'a> {
pub fn encoded(&self) -> &'a [u8] {
self.encoded
}
pub fn payload(&self) -> &'a [u8] {
self.payload
}
pub fn block_seq(&self) -> u64 {
self.block_seq
}
pub fn flags(&self) -> u16 {
self.flags
}
pub fn encoded_len(&self) -> usize {
self.encoded.len()
}
}