use crate::consts::HASH_FIELD_SIZE;
use crate::entry::PartitionEntry;
use crate::error::Error;
use crate::hash::HashAlgo;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableBlockHeader {
pub partition_count: u8,
pub next_table_offset: u64,
pub table_hash_algo: HashAlgo,
pub table_hash: [u8; HASH_FIELD_SIZE],
}
impl TableBlockHeader {
pub fn to_bytes(&self) -> [u8; 74] {
let mut b = [0u8; 74];
b[0] = self.partition_count;
b[1..9].copy_from_slice(&self.next_table_offset.to_le_bytes());
b[9] = self.table_hash_algo.id();
b[10..74].copy_from_slice(&self.table_hash);
b
}
pub fn from_bytes(b: &[u8; 74]) -> Result<Self, Error> {
let partition_count = b[0];
let next_table_offset = u64::from_le_bytes(b[1..9].try_into().unwrap());
let table_hash_algo = HashAlgo::from_id(b[9])?;
let mut table_hash = [0u8; HASH_FIELD_SIZE];
table_hash.copy_from_slice(&b[10..74]);
Ok(TableBlockHeader {
partition_count,
next_table_offset,
table_hash_algo,
table_hash,
})
}
}
pub fn compute_table_hash(
algo: HashAlgo,
next_table_offset: u64,
entries: &[PartitionEntry],
) -> [u8; HASH_FIELD_SIZE] {
let header = TableBlockHeader {
partition_count: entries.len() as u8,
next_table_offset,
table_hash_algo: algo,
table_hash: [0u8; HASH_FIELD_SIZE], };
let mut image = Vec::with_capacity(74 + entries.len() * 141);
image.extend_from_slice(&header.to_bytes());
for e in entries {
image.extend_from_slice(&e.to_bytes());
}
algo.compute(&image)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_roundtrip() {
let h = TableBlockHeader {
partition_count: 3,
next_table_offset: 4096,
table_hash_algo: HashAlgo::Sha256,
table_hash: HashAlgo::Sha256.compute(b"abc"),
};
assert_eq!(TableBlockHeader::from_bytes(&h.to_bytes()).unwrap(), h);
}
#[test]
fn empty_block_hash_is_stable() {
let a = compute_table_hash(HashAlgo::Sha256, 0, &[]);
let b = compute_table_hash(HashAlgo::Sha256, 0, &[]);
assert_eq!(a, b);
}
}