use std::fmt::Debug;
use std::time::Duration;
use async_trait::async_trait;
use crate::core::{Digest, Epoch, RunId, Seq, StoreError};
use super::{Append, Record};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Head {
pub seq: Seq,
pub hash: Digest,
}
impl Head {
#[must_use]
pub const fn genesis() -> Self {
Self {
seq: 0,
hash: Digest::ZERO,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lease {
pub run: RunId,
pub owner: String,
pub epoch: Epoch,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Checkpoint {
pub origin: String,
pub size: u64,
pub root: Digest,
}
impl Checkpoint {
#[must_use]
pub fn to_note(&self) -> String {
format!(
"{}\n{}\n{}\n",
self.origin,
self.size,
b64(self.root.as_bytes())
)
}
pub fn from_note(note: &str) -> Result<Self, StoreError> {
let mut lines = note.lines();
let bad = |what: &str| StoreError::Backend(format!("checkpoint note: {what}"));
let origin = lines.next().ok_or_else(|| bad("no origin"))?.to_owned();
let size = lines
.next()
.ok_or_else(|| bad("no size"))?
.parse::<u64>()
.map_err(|e| bad(&format!("size is not a number: {e}")))?;
let root = lines.next().ok_or_else(|| bad("no root"))?;
let root = unb64(root).ok_or_else(|| bad("root is not base64"))?;
let root: [u8; 32] = root.try_into().map_err(|_| bad("root is not 32 bytes"))?;
Ok(Self {
origin,
size,
root: Digest::from_bytes(root),
})
}
}
fn b64(bytes: &[u8]) -> String {
const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::new();
for chunk in bytes.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
for i in 0..4 {
if i <= chunk.len() {
out.push(A[((n >> (18 - 6 * i)) & 0x3F) as usize] as char);
} else {
out.push('=');
}
}
}
out
}
fn unb64(s: &str) -> Option<Vec<u8>> {
let mut acc = 0u32;
let mut bits = 0u8;
let mut out = Vec::new();
for c in s.trim().bytes() {
if c == b'=' {
break;
}
let v = match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'+' => 62,
b'/' => 63,
_ => return None,
};
acc = (acc << 6) | u32::from(v);
bits += 6;
if bits >= 8 {
bits -= 8;
out.push(u8::try_from((acc >> bits) & 0xFF).ok()?);
}
}
Some(out)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Inclusion {
pub index: u64,
pub size: u64,
pub seal: Digest,
pub proof: Vec<Digest>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cancellation {
pub actor: String,
pub reason: String,
}
#[async_trait]
pub trait JournalStore: Send + Sync + Debug {
async fn append(&self, epoch: Epoch, batch: Vec<Append>) -> Result<Vec<Record>, StoreError>;
fn atomic(&self) -> Option<&dyn crate::journal::AtomicJournal> {
None
}
async fn read(&self, run: RunId, from: Seq) -> Result<Vec<Record>, StoreError>;
async fn runs_by_outcome(&self, outcome: &str, limit: usize) -> Result<Vec<RunId>, StoreError>;
async fn recent_runs(&self) -> Result<Vec<(RunId, u64)>, StoreError>;
async fn case_history(
&self,
case: crate::core::CaseId,
limit: usize,
) -> Result<Vec<Record>, StoreError>;
async fn head(&self, run: RunId) -> Result<Head, StoreError>;
async fn acquire(&self, run: RunId, owner: &str, ttl: Duration) -> Result<Lease, StoreError>;
async fn release_lease(&self, run: RunId, epoch: Epoch) -> Result<(), StoreError>;
fn tenant(&self) -> &str {
crate::core::TenantId::DEFAULT
}
async fn seal(&self, run: RunId, epoch: Epoch, outcome: &str) -> Result<Digest, StoreError>;
async fn checkpoint(&self) -> Result<Checkpoint, StoreError>;
async fn consistency_proof(&self, old_size: u64) -> Result<Vec<Digest>, StoreError>;
async fn inclusion_proof(&self, run: RunId) -> Result<Option<Inclusion>, StoreError>;
async fn request_cancel(
&self,
run: RunId,
actor: &str,
reason: &str,
) -> Result<bool, StoreError>;
async fn cancellation(&self, run: RunId) -> Result<Option<Cancellation>, StoreError>;
async fn verify(&self, run: RunId) -> Result<Digest, StoreError> {
let records = self.read(run, 1).await?;
Record::verify_chain(&records, Digest::ZERO)
}
}