use bytes::Bytes;
use kimberlite_crypto::{ChainHash, chain_hash};
use kimberlite_types::{Offset, RecordKind};
use crate::StorageError;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Record {
offset: Offset,
prev_hash: Option<ChainHash>,
kind: RecordKind,
payload: Bytes,
}
impl Record {
pub fn new(offset: Offset, prev_hash: Option<ChainHash>, payload: Bytes) -> Self {
Self {
offset,
prev_hash,
kind: RecordKind::Data,
payload,
}
}
pub fn with_kind(
offset: Offset,
prev_hash: Option<ChainHash>,
kind: RecordKind,
payload: Bytes,
) -> Self {
Self {
offset,
prev_hash,
kind,
payload,
}
}
pub fn offset(&self) -> Offset {
self.offset
}
pub fn prev_hash(&self) -> Option<ChainHash> {
self.prev_hash
}
pub fn kind(&self) -> RecordKind {
self.kind
}
pub fn payload(&self) -> &Bytes {
&self.payload
}
pub fn is_checkpoint(&self) -> bool {
self.kind == RecordKind::Checkpoint
}
pub fn compute_hash(&self) -> ChainHash {
let mut data = vec![self.kind.as_byte()];
data.extend_from_slice(&self.payload);
chain_hash(self.prev_hash.as_ref(), &data)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&self.offset.as_u64().to_le_bytes());
match &self.prev_hash {
Some(hash) => buf.extend_from_slice(hash.as_bytes()),
None => buf.extend_from_slice(&[0u8; 32]),
}
buf.push(self.kind.as_byte());
buf.extend_from_slice(&(self.payload.len() as u32).to_le_bytes());
buf.extend_from_slice(&self.payload);
let crc = kimberlite_crypto::crc32(&buf);
buf.extend_from_slice(&crc.to_le_bytes());
buf
}
pub fn from_bytes(data: &Bytes) -> Result<(Self, usize), StorageError> {
const HEADER_SIZE: usize = 45;
if data.len() < HEADER_SIZE {
return Err(StorageError::UnexpectedEof);
}
let offset = Offset::new(u64::from_le_bytes(
data[0..8]
.try_into()
.expect("slice is exactly 8 bytes after bounds check"),
));
let prev_hash_bytes: [u8; 32] = data[8..40]
.try_into()
.expect("slice is exactly 32 bytes after bounds check");
let prev_hash = if prev_hash_bytes == [0u8; 32] {
None
} else {
Some(ChainHash::from_bytes(&prev_hash_bytes))
};
let kind = RecordKind::from_byte(data[40]).ok_or(StorageError::InvalidRecordKind {
byte: data[40],
offset,
})?;
let length = u32::from_le_bytes(
data[41..45]
.try_into()
.expect("slice is exactly 4 bytes after bounds check"),
) as usize;
let total_size = HEADER_SIZE + length + 4;
if data.len() < total_size {
return Err(StorageError::UnexpectedEof);
}
let payload = data.slice(45..45 + length);
let stored_crc = u32::from_le_bytes(
data[45 + length..total_size]
.try_into()
.expect("slice is exactly 4 bytes after bounds check"),
);
let computed_crc = kimberlite_crypto::crc32(&data[0..45 + length]);
if stored_crc != computed_crc {
return Err(StorageError::CorruptedRecord);
}
Ok((
Record {
offset,
prev_hash,
kind,
payload,
},
total_size,
))
}
}