Skip to main content

Chain

Struct Chain 

Source
pub struct Chain<H, S, C>
where H: Hasher, S: Sink, C: Clock,
{ /* private fields */ }
Expand description

An append-only chain of audit records.

The chain is generic over its three pluggable components:

  • H: Hasher — the cryptographic hash function used to link records.
  • S: Sink — the backend that persists each record.
  • C: Clock — the time source.

Chain is !Sync by virtue of holding mutable state on &mut self. Concurrent appenders should serialize on the chain or shard their writes across independent chains.

§Example

use audit_trail::{
    Action, Actor, Chain, Clock, Digest, Hasher, Outcome, Record, RecordId,
    Sink, SinkError, Target, Timestamp, HASH_LEN,
};

// Minimal (insecure) Hasher: XOR-fold bytes into a 32-byte buffer.
struct XorHasher([u8; HASH_LEN]);
impl Hasher for XorHasher {
    fn reset(&mut self) { self.0 = [0u8; HASH_LEN]; }
    fn update(&mut self, bytes: &[u8]) {
        for (i, b) in bytes.iter().enumerate() { self.0[i % HASH_LEN] ^= *b; }
    }
    fn finalize(&mut self, out: &mut Digest) { *out = Digest::from_bytes(self.0); }
}

// A clock that ticks one nanosecond per call.
struct TickClock(core::cell::Cell<u64>);
impl Clock for TickClock {
    fn now(&self) -> Timestamp {
        let v = self.0.get(); self.0.set(v + 1); Timestamp::from_nanos(v)
    }
}

// An in-memory sink that records hashes for verification.
#[derive(Default)]
struct VecSink(Vec<Digest>);
impl Sink for VecSink {
    fn write(&mut self, r: &Record<'_>) -> Result<(), SinkError> {
        self.0.push(r.hash()); Ok(())
    }
}

let mut chain = Chain::new(
    XorHasher([0u8; HASH_LEN]),
    VecSink::default(),
    TickClock(core::cell::Cell::new(1)),
);

let id = chain.append(
    Actor::new("user-1"),
    Action::new("user.login"),
    Target::new("session:abc"),
    Outcome::Success,
).expect("append");
assert_eq!(id, RecordId::GENESIS);

Implementations§

Source§

impl<H, S, C> Chain<H, S, C>
where H: Hasher, S: Sink, C: Clock,

Source

pub fn new(hasher: H, sink: S, clock: C) -> Self

Construct a fresh chain starting from genesis.

The chain begins with next_id = 0 and last_hash = Digest::ZERO. To resume a previously persisted chain, use Chain::resume.

Source

pub fn resume( hasher: H, sink: S, clock: C, next_id: RecordId, last_hash: Digest, last_timestamp: Timestamp, ) -> Self

Resume a chain from a previously persisted tail.

next_id is the identifier the next appended record will receive. last_hash is the hash of the most recently persisted record (or Digest::ZERO if the chain is empty). last_timestamp is the timestamp of the most recently persisted record (or Timestamp::EPOCH if the chain is empty).

Source

pub const fn next_id(&self) -> RecordId

Identifier the next appended record will receive.

Source

pub const fn last_hash(&self) -> Digest

Hash of the most recently appended record (or Digest::ZERO if none).

Source

pub const fn last_timestamp(&self) -> Timestamp

Timestamp of the most recently appended record (or Timestamp::EPOCH if none).

Source

pub fn append( &mut self, actor: Actor<'_>, action: Action<'_>, target: Target<'_>, outcome: Outcome, ) -> Result<RecordId>

Append a record to the chain.

Returns the new record’s RecordId. On error the chain state is left unchanged: the sink is only updated after the hash is computed and the timestamp/id checks have passed, and last_hash / last_timestamp / next_id are only updated after the sink write succeeds.

§Errors
Source

pub fn into_parts(self) -> (H, S, C)

Consume the chain and return its three pluggable components.

Source

pub const fn sink(&self) -> &S

Borrow the configured sink.

Source

pub fn sink_mut(&mut self) -> &mut S

Mutably borrow the configured sink.

Trait Implementations§

Source§

impl<H, S, C> Debug for Chain<H, S, C>
where H: Hasher + Debug, S: Sink + Debug, C: Clock + Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<H, S, C> Freeze for Chain<H, S, C>
where H: Freeze, S: Freeze, C: Freeze,

§

impl<H, S, C> RefUnwindSafe for Chain<H, S, C>

§

impl<H, S, C> Send for Chain<H, S, C>
where H: Send, S: Send, C: Send,

§

impl<H, S, C> Sync for Chain<H, S, C>
where H: Sync, S: Sync, C: Sync,

§

impl<H, S, C> Unpin for Chain<H, S, C>
where H: Unpin, S: Unpin, C: Unpin,

§

impl<H, S, C> UnsafeUnpin for Chain<H, S, C>

§

impl<H, S, C> UnwindSafe for Chain<H, S, C>
where H: UnwindSafe, S: UnwindSafe, C: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.