use crate::{
read_fixed_le, version, write_fixed_le, CompactSize, FixedEncodedLen, ZainoVersionedSerde,
};
use blake2::{
digest::{Update, VariableOutput},
Blake2bVar,
};
use corez::io::{self, Read, Write};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct StoredEntryFixed<T: ZainoVersionedSerde + FixedEncodedLen> {
pub(crate) item: T,
pub(crate) checksum: [u8; 32],
}
impl<T: ZainoVersionedSerde + FixedEncodedLen> StoredEntryFixed<T> {
pub(crate) fn new<K: AsRef<[u8]>>(key: K, item: T) -> Self {
let body = {
let mut v = Vec::with_capacity(T::VERSIONED_LEN);
item.serialize(&mut v).unwrap();
v
};
let checksum = Self::blake2b256(&[key.as_ref(), &body].concat());
Self { item, checksum }
}
pub(crate) fn verify<K: AsRef<[u8]>>(&self, key: K) -> bool {
let mut v = T::VERSION;
loop {
match self.item.to_bytes_with_version(v) {
Ok(item_bytes) => {
let candidate = Self::blake2b256(&[key.as_ref(), &item_bytes].concat());
if candidate == self.checksum {
return true;
}
}
Err(_) => {
}
}
if v == 1 {
break;
}
v = v.saturating_sub(1);
}
false
}
pub(crate) fn inner(&self) -> &T {
&self.item
}
pub(crate) fn blake2b256(data: &[u8]) -> [u8; 32] {
let mut hasher = Blake2bVar::new(32).expect("Failed to create hasher");
hasher.update(data);
let mut output = [0u8; 32];
hasher
.finalize_variable(&mut output)
.expect("Failed to finalize hash");
output
}
#[cfg(test)]
pub(crate) fn to_bytes_with_item_version<K: AsRef<[u8]>>(
key: K,
item: &T,
item_version: u8,
) -> io::Result<Vec<u8>> {
let item_bytes = item.to_bytes_with_version(item_version)?;
if item_bytes.len() != T::VERSIONED_LEN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"encoded fixed-length item has an unexpected length",
));
}
let checksum = Self::blake2b256(&[key.as_ref(), &item_bytes].concat());
let mut stored_entry_bytes = Vec::with_capacity(1 + T::VERSIONED_LEN + 32);
stored_entry_bytes.push(Self::VERSION);
stored_entry_bytes.extend_from_slice(&item_bytes);
stored_entry_bytes.extend_from_slice(&checksum);
Ok(stored_entry_bytes)
}
}
impl<T: ZainoVersionedSerde + FixedEncodedLen> ZainoVersionedSerde for StoredEntryFixed<T> {
const VERSION: u8 = version::V1;
fn encode_latest<W: Write>(&self, w: &mut W) -> io::Result<()> {
Self::encode_v1(self, w)
}
fn decode_latest<R: Read>(r: &mut R) -> io::Result<Self> {
Self::decode_v1(r)
}
fn encode_v1<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.item.serialize(&mut *w)?;
write_fixed_le::<32, _>(&mut *w, &self.checksum)
}
fn decode_v1<R: Read>(r: &mut R) -> io::Result<Self> {
let mut body = vec![0u8; T::VERSIONED_LEN];
r.read_exact(&mut body)?;
let item = T::deserialize(&body[..])?;
let checksum = read_fixed_le::<32, _>(r)?;
Ok(Self { item, checksum })
}
}
impl<T: ZainoVersionedSerde + FixedEncodedLen> FixedEncodedLen for StoredEntryFixed<T> {
const ENCODED_LEN: usize = T::VERSIONED_LEN + 32;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct StoredEntryVar<T: ZainoVersionedSerde> {
pub(crate) item: T,
pub(crate) checksum: [u8; 32],
}
impl<T: ZainoVersionedSerde> StoredEntryVar<T> {
pub(crate) fn new<K: AsRef<[u8]>>(key: K, item: T) -> Self {
let body = {
let mut v = Vec::new();
item.serialize(&mut v).unwrap();
v
};
let checksum = Self::blake2b256(&[key.as_ref(), &body].concat());
Self { item, checksum }
}
pub(crate) fn verify<K: AsRef<[u8]>>(&self, key: K) -> bool {
let mut v = T::VERSION;
loop {
match self.item.to_bytes_with_version(v) {
Ok(item_bytes) => {
let candidate = Self::blake2b256(&[key.as_ref(), &item_bytes].concat());
if candidate == self.checksum {
return true;
}
}
Err(_) => {
}
}
if v == 1 {
break;
}
v = v.saturating_sub(1);
}
false
}
pub(crate) fn inner(&self) -> &T {
&self.item
}
pub(crate) fn blake2b256(data: &[u8]) -> [u8; 32] {
let mut hasher = Blake2bVar::new(32).expect("Failed to create hasher");
hasher.update(data);
let mut output = [0u8; 32];
hasher
.finalize_variable(&mut output)
.expect("Failed to finalize hash");
output
}
#[cfg(test)]
pub(crate) fn to_bytes_with_item_version<K: AsRef<[u8]>>(
key: K,
item: &T,
item_version: u8,
) -> io::Result<Vec<u8>> {
let item_bytes = item.to_bytes_with_version(item_version)?;
let checksum = Self::blake2b256(&[key.as_ref(), &item_bytes].concat());
let mut stored_entry_bytes = Vec::new();
stored_entry_bytes.push(Self::VERSION);
CompactSize::write(&mut stored_entry_bytes, item_bytes.len())?;
stored_entry_bytes.extend_from_slice(&item_bytes);
write_fixed_le::<32, _>(&mut stored_entry_bytes, &checksum)?;
Ok(stored_entry_bytes)
}
}
impl<T: ZainoVersionedSerde> ZainoVersionedSerde for StoredEntryVar<T> {
const VERSION: u8 = version::V1;
fn encode_latest<W: Write>(&self, w: &mut W) -> io::Result<()> {
Self::encode_v1(self, w)
}
fn decode_latest<R: Read>(r: &mut R) -> io::Result<Self> {
Self::decode_v1(r)
}
fn encode_v1<W: Write>(&self, w: &mut W) -> io::Result<()> {
let mut body = Vec::new();
self.item.serialize(&mut body)?;
CompactSize::write(&mut *w, body.len())?;
w.write_all(&body)?;
write_fixed_le::<32, _>(&mut *w, &self.checksum)
}
fn decode_v1<R: Read>(r: &mut R) -> io::Result<Self> {
let len = CompactSize::read(&mut *r)? as usize;
let mut body = vec![0u8; len];
r.read_exact(&mut body)?;
let item = T::deserialize(&body[..])?;
let checksum = read_fixed_le::<32, _>(r)?;
Ok(Self { item, checksum })
}
}
#[cfg(test)]
mod tests {
use crate::{read_u32_be, read_u32_le, write_u32_be, write_u32_le};
use super::*;
#[derive(Clone, Debug, PartialEq, Eq)]
struct TestInner {
pub x: u32,
}
impl ZainoVersionedSerde for TestInner {
const VERSION: u8 = version::V2;
fn encode_latest<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.encode_v2(w)
}
fn decode_latest<R: Read>(r: &mut R) -> io::Result<Self> {
Self::decode_v2(r)
}
fn encode_v1<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_u32_le(w, self.x)
}
fn encode_v2<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_u32_be(w, self.x)
}
fn decode_v1<R: Read>(r: &mut R) -> io::Result<Self> {
let x = read_u32_le(r)?;
Ok(TestInner { x })
}
fn decode_v2<R: Read>(r: &mut R) -> io::Result<Self> {
let x = read_u32_be(r)?;
Ok(TestInner { x })
}
}
impl FixedEncodedLen for TestInner {
const ENCODED_LEN: usize = 4;
}
fn key_bytes() -> Vec<u8> {
b"test-key".to_vec()
}
#[test]
fn stored_entry_fixed_roundtrip_latest() {
let inner = TestInner { x: 0x1122_3344 };
let key = key_bytes();
let wrapper = StoredEntryFixed::new(&key, inner.clone());
assert!(
wrapper.verify(&key),
"wrapper verify (latest) should succeed"
);
let bytes = wrapper.to_bytes().expect("wrapper to_bytes");
let parsed = StoredEntryFixed::<TestInner>::from_bytes(&bytes).expect("from_bytes");
assert_eq!(parsed.item, inner);
assert_eq!(parsed.checksum, wrapper.checksum);
assert!(parsed.verify(&key));
}
#[test]
fn stored_entry_fixed_verify_old_v1() {
let inner = TestInner { x: 0xAABB_CCDD };
let key = key_bytes();
let item_bytes_v1 = inner
.to_bytes_with_version(version::V1)
.expect("inner v1 bytes");
let mut digest_input = Vec::with_capacity(key.len() + item_bytes_v1.len());
digest_input.extend_from_slice(&key);
digest_input.extend_from_slice(&item_bytes_v1);
let checksum = StoredEntryFixed::<TestInner>::blake2b256(&digest_input);
let mut raw = Vec::new();
raw.push(StoredEntryFixed::<TestInner>::VERSION);
raw.extend_from_slice(&item_bytes_v1);
raw.extend_from_slice(&checksum);
let parsed = StoredEntryFixed::<TestInner>::from_bytes(&raw).expect("from_bytes v1");
assert_eq!(parsed.item, inner);
assert!(parsed.verify(&key));
}
#[test]
fn stored_entry_fixed_verify_tamper() {
let inner = TestInner { x: 0x0102_0304 };
let key = key_bytes();
let mut wrapper = StoredEntryFixed::new(&key, inner.clone());
assert!(wrapper.verify(&key));
wrapper.checksum[0] ^= 0xff;
assert!(
!wrapper.verify(&key),
"verify should fail with tampered checksum"
);
wrapper = StoredEntryFixed::new(&key, inner.clone());
assert!(wrapper.verify(&key));
let wrong_key = b"other-key".to_vec();
assert!(
!wrapper.verify(&wrong_key),
"verify should fail with wrong key"
);
}
#[test]
fn stored_entry_var_roundtrip_latest() {
let inner = TestInner { x: 0x5566_7788 };
let key = key_bytes();
let wrapper = StoredEntryVar::new(&key, inner.clone());
assert!(
wrapper.verify(&key),
"var wrapper verify (latest) should succeed"
);
let bytes = wrapper.to_bytes().expect("var to_bytes");
let parsed = StoredEntryVar::<TestInner>::from_bytes(&bytes).expect("var from_bytes");
assert_eq!(parsed.item, inner);
assert_eq!(parsed.checksum, wrapper.checksum);
assert!(parsed.verify(&key));
}
#[test]
fn stored_entry_var_verify_old_v1() {
let inner = TestInner { x: 0xDEAD_BEEF };
let key = key_bytes();
let item_bytes_v1 = inner
.to_bytes_with_version(version::V1)
.expect("inner v1 bytes");
let mut digest_input = Vec::with_capacity(key.len() + item_bytes_v1.len());
digest_input.extend_from_slice(&key);
digest_input.extend_from_slice(&item_bytes_v1);
let checksum = StoredEntryVar::<TestInner>::blake2b256(&digest_input);
let mut raw = Vec::new();
raw.push(StoredEntryVar::<TestInner>::VERSION);
CompactSize::write(&mut raw, item_bytes_v1.len()).expect("write compactsize");
raw.extend_from_slice(&item_bytes_v1);
write_fixed_le::<32, _>(&mut raw, &checksum).expect("write checksum");
let parsed = StoredEntryVar::<TestInner>::from_bytes(&raw).expect("var from_bytes v1");
assert_eq!(parsed.item, inner);
assert!(parsed.verify(&key));
}
#[test]
fn stored_entry_var_verify_tamper() {
let inner = TestInner { x: 0xCAFEBABE };
let key = key_bytes();
let mut wrapper = StoredEntryVar::new(&key, inner);
assert!(wrapper.verify(&key));
wrapper.checksum[31] ^= 0xff;
assert!(!wrapper.verify(&key));
let wrapper = StoredEntryVar::new(&key, TestInner { x: 0xCAFEBABE });
let wrong_key = b"bad-key".to_vec();
assert!(!wrapper.verify(&wrong_key));
}
}