use crate::clock::{Clock, Timestamp};
use crate::error::{Error, Result};
use crate::hash::{Digest, Hasher};
use crate::record::{Action, Actor, Outcome, Record, RecordId, Target};
use crate::sink::Sink;
const FIELD_SEP: u8 = 0x1f;
#[derive(Debug)]
pub struct Chain<H, S, C>
where
H: Hasher,
S: Sink,
C: Clock,
{
hasher: H,
sink: S,
clock: C,
next_id: u64,
last_hash: Digest,
last_timestamp: Timestamp,
}
impl<H, S, C> Chain<H, S, C>
where
H: Hasher,
S: Sink,
C: Clock,
{
#[inline]
pub fn new(hasher: H, sink: S, clock: C) -> Self {
Self {
hasher,
sink,
clock,
next_id: 0,
last_hash: Digest::ZERO,
last_timestamp: Timestamp::EPOCH,
}
}
#[inline]
pub fn resume(
hasher: H,
sink: S,
clock: C,
next_id: RecordId,
last_hash: Digest,
last_timestamp: Timestamp,
) -> Self {
Self {
hasher,
sink,
clock,
next_id: next_id.as_u64(),
last_hash,
last_timestamp,
}
}
#[inline]
pub const fn next_id(&self) -> RecordId {
RecordId::from_u64(self.next_id)
}
#[inline]
pub const fn last_hash(&self) -> Digest {
self.last_hash
}
#[inline]
pub const fn last_timestamp(&self) -> Timestamp {
self.last_timestamp
}
pub fn append(
&mut self,
actor: Actor<'_>,
action: Action<'_>,
target: Target<'_>,
outcome: Outcome,
) -> Result<RecordId> {
let timestamp = self.clock.now();
if self.next_id > 0 && timestamp <= self.last_timestamp {
return Err(Error::NonMonotonicClock);
}
let id = RecordId::from_u64(self.next_id);
let next = self.next_id.checked_add(1).ok_or(Error::Capacity)?;
let prev_hash = self.last_hash;
self.hasher.reset();
self.hasher.update(&id.as_u64().to_be_bytes());
self.hasher.update(&[FIELD_SEP]);
self.hasher.update(×tamp.as_nanos().to_be_bytes());
self.hasher.update(&[FIELD_SEP]);
self.hasher.update(actor.as_str().as_bytes());
self.hasher.update(&[FIELD_SEP]);
self.hasher.update(action.as_str().as_bytes());
self.hasher.update(&[FIELD_SEP]);
self.hasher.update(target.as_str().as_bytes());
self.hasher.update(&[FIELD_SEP]);
self.hasher.update(&[outcome.as_u8()]);
self.hasher.update(&[FIELD_SEP]);
self.hasher.update(prev_hash.as_bytes());
let mut hash = Digest::ZERO;
self.hasher.finalize(&mut hash);
let record = Record::new(
id, timestamp, actor, action, target, outcome, prev_hash, hash,
);
self.sink.write(&record)?;
self.next_id = next;
self.last_hash = hash;
self.last_timestamp = timestamp;
Ok(id)
}
#[inline]
pub fn into_parts(self) -> (H, S, C) {
(self.hasher, self.sink, self.clock)
}
#[inline]
pub const fn sink(&self) -> &S {
&self.sink
}
#[inline]
pub fn sink_mut(&mut self) -> &mut S {
&mut self.sink
}
}