use std::sync::Arc;
use async_trait::async_trait;
use crate::core::{Digest, Epoch, RunId, StoreError, TenantId};
use crate::journal::{
Append, Cancellation, Checkpoint, Head, Inclusion, JournalStore, Lease, Record, payload,
};
use super::KeyRing;
#[derive(Debug)]
pub struct SealedJournal {
inner: Arc<dyn JournalStore>,
keys: Arc<dyn KeyRing>,
tenant: TenantId,
}
impl SealedJournal {
#[must_use]
pub fn wrap(
inner: Arc<dyn JournalStore>,
keys: Arc<dyn KeyRing>,
tenant: TenantId,
) -> Arc<Self> {
Arc::new(Self {
inner,
keys,
tenant,
})
}
fn scope_for(&self, run: RunId, case: Option<crate::core::CaseId>) -> String {
case.map_or_else(
|| super::scope(&self.tenant, &run.to_string()),
|c| super::scope(&self.tenant, &c.to_string()),
)
}
fn aad(run: RunId) -> String {
run.to_string()
}
}
fn sealing(e: &super::KeyError) -> StoreError {
StoreError::Backend(format!("sealing a journal payload failed: {e}"))
}
#[async_trait]
impl JournalStore for SealedJournal {
fn tenant(&self) -> &str {
self.inner.tenant()
}
async fn append(&self, epoch: Epoch, batch: Vec<Append>) -> Result<Vec<Record>, StoreError> {
let mut sealed = Vec::with_capacity(batch.len());
for mut entry in batch {
let scope = self.scope_for(entry.run, entry.case);
let aad = Self::aad(entry.run);
for field in payload::payloads(&mut entry.kind) {
let plain = crate::core::canon::to_bytes(&*field).map_err(|e| {
StoreError::Backend(format!("a payload would not serialise: {e}"))
})?;
let envelope =
super::envelope::seal(self.keys.as_ref(), &scope, aad.as_bytes(), &plain)
.await
.map_err(|e| sealing(&e))?;
*field = payload::wrap(&envelope);
}
sealed.push(entry);
}
let written = self.inner.append(epoch, sealed).await?;
self.open_all(written).await
}
async fn read(&self, run: RunId, from: crate::core::Seq) -> Result<Vec<Record>, StoreError> {
let records = self.inner.read(run, from).await?;
self.open_all(records).await
}
async fn case_history(
&self,
case: crate::core::CaseId,
limit: usize,
) -> Result<Vec<Record>, StoreError> {
let records = self.inner.case_history(case, limit).await?;
self.open_all(records).await
}
async fn acquire(
&self,
run: RunId,
owner: &str,
ttl: std::time::Duration,
) -> Result<Lease, StoreError> {
self.inner.acquire(run, owner, ttl).await
}
async fn release_lease(&self, run: RunId, epoch: Epoch) -> Result<(), StoreError> {
self.inner.release_lease(run, epoch).await
}
async fn runs_by_outcome(&self, outcome: &str, limit: usize) -> Result<Vec<RunId>, StoreError> {
self.inner.runs_by_outcome(outcome, limit).await
}
async fn recent_runs(&self) -> Result<Vec<(RunId, u64)>, StoreError> {
self.inner.recent_runs().await
}
async fn head(&self, run: RunId) -> Result<Head, StoreError> {
self.inner.head(run).await
}
async fn seal(&self, run: RunId, epoch: Epoch, outcome: &str) -> Result<Digest, StoreError> {
self.inner.seal(run, epoch, outcome).await
}
async fn checkpoint(&self) -> Result<Checkpoint, StoreError> {
self.inner.checkpoint().await
}
async fn consistency_proof(&self, old_size: u64) -> Result<Vec<Digest>, StoreError> {
self.inner.consistency_proof(old_size).await
}
async fn inclusion_proof(&self, run: RunId) -> Result<Option<Inclusion>, StoreError> {
self.inner.inclusion_proof(run).await
}
async fn request_cancel(
&self,
run: RunId,
actor: &str,
reason: &str,
) -> Result<bool, StoreError> {
self.inner.request_cancel(run, actor, reason).await
}
async fn cancellation(&self, run: RunId) -> Result<Option<Cancellation>, StoreError> {
self.inner.cancellation(run).await
}
}
impl SealedJournal {
async fn open_all(&self, records: Vec<Record>) -> Result<Vec<Record>, StoreError> {
let mut out = Vec::with_capacity(records.len());
for record in records {
let run = record.body.run;
let aad = Self::aad(run);
let mut kind = record.kind().clone();
let mut changed = false;
for field in payload::payloads(&mut kind) {
let Some(envelope) = payload::unwrap(field) else {
continue;
};
if let Ok(plain) =
super::envelope::open(self.keys.as_ref(), aad.as_bytes(), &envelope).await
{
*field = serde_json::from_slice(&plain)?;
changed = true;
}
}
out.push(if changed {
record.with_opened_kind(kind)
} else {
record
});
}
Ok(out)
}
}