#[cfg(feature = "alloc")]
extern crate alloc;
use crate::{Error, Result};
pub const MAGIC: [u8; 3] = [0x4F, 0x58, 0x48];
pub const FORMAT_VERSION: u8 = 1;
pub const HEADER_SIZE: usize = 16;
const VERSION_OFFSET: usize = 3;
const LEN_OFFSET: usize = 4;
const CRC_OFFSET: usize = 12;
#[cfg(feature = "alloc")]
pub fn wrap_with_checksum(data: &[u8]) -> alloc::vec::Vec<u8> {
let crc = crc32fast::hash(data);
let len = data.len() as u64;
let mut output = alloc::vec::Vec::with_capacity(HEADER_SIZE + data.len());
output.extend_from_slice(&MAGIC);
output.push(FORMAT_VERSION);
output.extend_from_slice(&len.to_le_bytes());
output.extend_from_slice(&crc.to_le_bytes());
output.extend_from_slice(data);
output
}
pub fn verify_checksum(data: &[u8]) -> Result<&[u8]> {
if data.len() < HEADER_SIZE {
return Err(Error::UnexpectedEnd {
additional: HEADER_SIZE - data.len(),
});
}
if data[..3] != MAGIC {
return Err(Error::InvalidData {
message: "invalid checksum header magic",
});
}
let version = data[VERSION_OFFSET];
if version != FORMAT_VERSION {
return Err(Error::InvalidData {
message: "unsupported checksum format version",
});
}
let stored_len =
u64::from_le_bytes(data[LEN_OFFSET..LEN_OFFSET + 8].try_into().map_err(|_| {
Error::InvalidData {
message: "failed to read checksum payload length",
}
})?) as usize;
let expected_total = HEADER_SIZE + stored_len;
if data.len() < expected_total {
return Err(Error::UnexpectedEnd {
additional: expected_total - data.len(),
});
}
let stored_crc =
u32::from_le_bytes(data[CRC_OFFSET..CRC_OFFSET + 4].try_into().map_err(|_| {
Error::InvalidData {
message: "failed to read checksum value",
}
})?);
let payload = &data[HEADER_SIZE..HEADER_SIZE + stored_len];
let computed_crc = crc32fast::hash(payload);
if computed_crc != stored_crc {
return Err(Error::ChecksumMismatch {
expected: stored_crc,
found: computed_crc,
});
}
Ok(payload)
}
#[cfg(feature = "alloc")]
pub fn encode_with_checksum<E: crate::Encode>(value: &E) -> Result<alloc::vec::Vec<u8>> {
encode_with_checksum_config(value, crate::config::standard())
}
#[cfg(feature = "alloc")]
pub fn encode_with_checksum_config<E: crate::Encode, C: crate::config::Config>(
value: &E,
config: C,
) -> Result<alloc::vec::Vec<u8>> {
let payload = crate::encode_to_vec_with_config(value, config)?;
Ok(wrap_with_checksum(&payload))
}
#[cfg(feature = "alloc")]
pub fn decode_with_checksum<D: crate::Decode>(data: &[u8]) -> Result<(D, usize)> {
decode_with_checksum_config(data, crate::config::standard())
}
#[cfg(feature = "alloc")]
pub fn unwrap_with_checksum(data: &[u8]) -> Result<alloc::vec::Vec<u8>> {
verify_checksum(data).map(|payload| payload.to_vec())
}
pub type ChecksumError = crate::Error;
#[cfg(feature = "alloc")]
pub fn decode_with_checksum_config<D: crate::Decode, C: crate::config::Config>(
data: &[u8],
config: C,
) -> Result<(D, usize)> {
let payload = verify_checksum(data)?;
let (value, inner_consumed) = crate::decode_from_slice_with_config(payload, config)?;
Ok((value, HEADER_SIZE + inner_consumed))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wrap_verify_roundtrip() {
let data = b"Hello, OxiCode!";
let wrapped = wrap_with_checksum(data);
let payload = verify_checksum(&wrapped).expect("verify failed");
assert_eq!(payload, data);
}
#[test]
fn test_header_size() {
let wrapped = wrap_with_checksum(b"test");
assert_eq!(wrapped.len(), HEADER_SIZE + 4);
}
#[test]
fn test_wrong_magic() {
let mut wrapped = wrap_with_checksum(b"test");
wrapped[0] = 0xFF; assert!(verify_checksum(&wrapped).is_err());
}
#[test]
fn test_corrupted_payload() {
let mut wrapped = wrap_with_checksum(b"Hello, OxiCode!");
let payload_start = HEADER_SIZE;
wrapped[payload_start] ^= 0xFF;
let result = verify_checksum(&wrapped);
assert!(result.is_err());
if let Err(Error::ChecksumMismatch { .. }) = result {
} else {
panic!("expected ChecksumMismatch error");
}
}
#[test]
fn test_too_short() {
let result = verify_checksum(&[0x4F, 0x58]);
assert!(matches!(result, Err(Error::UnexpectedEnd { .. })));
}
#[test]
fn test_empty_payload() {
let wrapped = wrap_with_checksum(b"");
let payload = verify_checksum(&wrapped).expect("verify failed");
assert_eq!(payload, b"");
}
#[test]
fn test_large_payload() {
let data: alloc::vec::Vec<u8> = (0u8..=255).cycle().take(100000).collect();
let wrapped = wrap_with_checksum(&data);
let payload = verify_checksum(&wrapped).expect("verify failed");
assert_eq!(payload, data.as_slice());
}
}