use crate::canonical;
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;
#[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;
let draft = Record::new(
id,
timestamp,
actor,
action,
target,
outcome,
prev_hash,
Digest::ZERO,
);
let hash = canonical::compute(&mut self.hasher, &draft);
let record = draft.with_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
}
}