use alloc::vec::Vec;
use pamoja_core::{Error, Result};
use pamoja_security::Signature;
use sha2::{Digest, Sha256};
const HEADER_LEN: usize = 8 + 32 + 64;
#[derive(Clone, Debug)]
pub struct Entry {
index: u64,
prev: [u8; 32],
signature: Signature,
payload: Vec<u8>,
}
impl Entry {
pub(crate) fn new(index: u64, prev: [u8; 32], signature: Signature, payload: Vec<u8>) -> Self {
Self {
index,
prev,
signature,
payload,
}
}
pub fn index(&self) -> u64 {
self.index
}
pub fn previous(&self) -> [u8; 32] {
self.prev
}
pub fn payload(&self) -> &[u8] {
&self.payload
}
pub fn signature(&self) -> &Signature {
&self.signature
}
pub fn digest(&self) -> [u8; 32] {
digest(self.index, &self.prev, &self.payload)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(HEADER_LEN + self.payload.len());
bytes.extend_from_slice(&self.index.to_le_bytes());
bytes.extend_from_slice(&self.prev);
bytes.extend_from_slice(&self.signature.to_bytes());
bytes.extend_from_slice(&self.payload);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < HEADER_LEN {
return Err(Error::Codec(
"audit entry is shorter than its header".into(),
));
}
let index = u64::from_le_bytes(bytes[..8].try_into().expect("eight index bytes"));
let prev: [u8; 32] = bytes[8..40].try_into().expect("thirty-two previous bytes");
let signature: [u8; 64] = bytes[40..HEADER_LEN]
.try_into()
.expect("sixty-four signature bytes");
let payload = bytes[HEADER_LEN..].to_vec();
Ok(Self::new(
index,
prev,
Signature::from_bytes(&signature),
payload,
))
}
}
pub(crate) fn digest(index: u64, prev: &[u8; 32], payload: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(index.to_le_bytes());
hasher.update(prev);
hasher.update(payload);
let out = hasher.finalize();
let mut digest = [0u8; 32];
digest.copy_from_slice(&out);
digest
}
#[cfg(test)]
mod tests {
use super::*;
use pamoja_security::DeviceIdentity;
#[test]
fn an_entry_round_trips_through_bytes() {
let device = DeviceIdentity::from_seed(&[1u8; 32]);
let signature = device.sign(b"x");
let entry = Entry::new(3, [7u8; 32], signature, b"payload".to_vec());
let bytes = entry.to_bytes();
let restored = Entry::from_bytes(&bytes).expect("parse");
assert_eq!(restored.index(), 3);
assert_eq!(restored.previous(), [7u8; 32]);
assert_eq!(restored.payload(), b"payload");
assert_eq!(restored.to_bytes(), bytes);
}
#[test]
fn a_short_buffer_is_rejected() {
let result = Entry::from_bytes(&[0u8; 10]);
assert!(matches!(result, Err(Error::Codec(_))));
}
}