use alloc::string::String;
use alloc::vec::Vec;
use crate::clock::Timestamp;
use crate::error::{Error, Result};
use crate::hash::{Digest, HASH_LEN};
use crate::owned::OwnedRecord;
use crate::record::{Outcome, Record, RecordId};
pub const FORMAT_MAGIC: &[u8; 8] = b"AUDTRAIL";
pub const FORMAT_VERSION: u8 = 0x01;
pub const FILE_HEADER_LEN: usize = 16;
const RECORD_FIXED_LEN: usize = 8 + 8 + 1 + HASH_LEN + HASH_LEN;
pub fn write_file_header(out: &mut Vec<u8>) {
out.extend_from_slice(FORMAT_MAGIC);
out.push(FORMAT_VERSION);
out.extend_from_slice(&[0u8; 7]);
}
pub fn verify_file_header(bytes: &[u8]) -> Result<()> {
if bytes.len() < FILE_HEADER_LEN {
return Err(Error::Truncated);
}
if &bytes[0..8] != FORMAT_MAGIC {
return Err(Error::InvalidFormat);
}
if bytes[8] != FORMAT_VERSION {
return Err(Error::InvalidFormat);
}
Ok(())
}
pub fn encode_record(record: &Record<'_>, out: &mut Vec<u8>) -> Result<()> {
let actor_bytes = record.actor().as_str().as_bytes();
let action_bytes = record.action().as_str().as_bytes();
let target_bytes = record.target().as_str().as_bytes();
if actor_bytes.len() > u32::MAX as usize
|| action_bytes.len() > u32::MAX as usize
|| target_bytes.len() > u32::MAX as usize
{
return Err(Error::InvalidFormat);
}
let body_len =
RECORD_FIXED_LEN + 4 + actor_bytes.len() + 4 + action_bytes.len() + 4 + target_bytes.len();
if body_len > u32::MAX as usize {
return Err(Error::InvalidFormat);
}
out.reserve(4 + body_len);
out.extend_from_slice(&(body_len as u32).to_be_bytes());
out.extend_from_slice(&record.id().as_u64().to_be_bytes());
out.extend_from_slice(&record.timestamp().as_nanos().to_be_bytes());
out.push(record.outcome().as_u8());
out.extend_from_slice(record.prev_hash().as_bytes());
out.extend_from_slice(record.hash().as_bytes());
out.extend_from_slice(&(actor_bytes.len() as u32).to_be_bytes());
out.extend_from_slice(actor_bytes);
out.extend_from_slice(&(action_bytes.len() as u32).to_be_bytes());
out.extend_from_slice(action_bytes);
out.extend_from_slice(&(target_bytes.len() as u32).to_be_bytes());
out.extend_from_slice(target_bytes);
Ok(())
}
pub fn decode_record(bytes: &[u8]) -> Result<(OwnedRecord, usize)> {
if bytes.len() < 4 {
return Err(Error::Truncated);
}
let body_len = read_u32(&bytes[0..4]) as usize;
let frame_end = 4usize.checked_add(body_len).ok_or(Error::InvalidFormat)?;
if bytes.len() < frame_end {
return Err(Error::Truncated);
}
let body = &bytes[4..frame_end];
if body.len() < RECORD_FIXED_LEN {
return Err(Error::InvalidFormat);
}
let id = RecordId::from_u64(read_u64(&body[0..8]));
let timestamp = Timestamp::from_nanos(read_u64(&body[8..16]));
let outcome = decode_outcome(body[16])?;
let mut prev_hash = [0u8; HASH_LEN];
prev_hash.copy_from_slice(&body[17..17 + HASH_LEN]);
let mut hash = [0u8; HASH_LEN];
hash.copy_from_slice(&body[17 + HASH_LEN..17 + HASH_LEN + HASH_LEN]);
let mut cursor = RECORD_FIXED_LEN;
let actor = read_string_field(body, &mut cursor)?;
let action = read_string_field(body, &mut cursor)?;
let target = read_string_field(body, &mut cursor)?;
if cursor != body.len() {
return Err(Error::InvalidFormat);
}
let record = OwnedRecord {
id,
timestamp,
actor,
action,
target,
outcome,
prev_hash: Digest::from_bytes(prev_hash),
hash: Digest::from_bytes(hash),
};
Ok((record, frame_end))
}
fn decode_outcome(byte: u8) -> Result<Outcome> {
match byte {
0 => Ok(Outcome::Success),
1 => Ok(Outcome::Failure),
2 => Ok(Outcome::Denied),
3 => Ok(Outcome::Error),
_ => Err(Error::InvalidFormat),
}
}
fn read_string_field(body: &[u8], cursor: &mut usize) -> Result<String> {
if body.len() < cursor.checked_add(4).ok_or(Error::InvalidFormat)? {
return Err(Error::InvalidFormat);
}
let len = read_u32(&body[*cursor..*cursor + 4]) as usize;
*cursor += 4;
let end = cursor.checked_add(len).ok_or(Error::InvalidFormat)?;
if body.len() < end {
return Err(Error::InvalidFormat);
}
let bytes = &body[*cursor..end];
let s = core::str::from_utf8(bytes).map_err(|_| Error::InvalidFormat)?;
*cursor = end;
Ok(String::from(s))
}
fn read_u32(bytes: &[u8]) -> u32 {
let mut buf = [0u8; 4];
buf.copy_from_slice(&bytes[0..4]);
u32::from_be_bytes(buf)
}
fn read_u64(bytes: &[u8]) -> u64 {
let mut buf = [0u8; 8];
buf.copy_from_slice(&bytes[0..8]);
u64::from_be_bytes(buf)
}