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> {
super::assert_serves(inner.tenant(), &tenant, "journal");
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(
&self,
run: RunId,
kind: &crate::journal::RecordKind,
effect: Option<crate::core::EffectKey>,
) -> String {
journal_aad(self.tenant.as_str(), run, kind, effect)
}
}
fn sealing(e: &super::KeyError) -> StoreError {
StoreError::Backend(format!("sealing a journal payload failed: {e}"))
}
#[async_trait]
impl JournalStore for SealedJournal {
fn is_shared(&self) -> bool {
self.inner.is_shared()
}
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, &entry.kind, entry.effect_key);
for field in payload::payloads(&mut entry.kind) {
match field {
payload::SealedField::Value(field) => {
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);
}
payload::SealedField::Text(field) => {
let envelope = super::envelope::seal(
self.keys.as_ref(),
&scope,
aad.as_bytes(),
field.as_bytes(),
)
.await
.map_err(|e| sealing(&e))?;
*field = payload::wrap_text(&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 read_page(
&self,
run: RunId,
from: crate::core::Seq,
limit: usize,
) -> Result<Vec<Record>, StoreError> {
let records = self.inner.read_page(run, from, limit).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 renew(
&self,
run: RunId,
owner: &str,
epoch: Epoch,
ttl: std::time::Duration,
) -> Result<Lease, StoreError> {
self.inner.renew(run, owner, epoch, ttl).await
}
async fn release_lease(&self, run: RunId, epoch: Epoch) -> Result<(), StoreError> {
self.inner.release_lease(run, epoch).await
}
async fn abandoned_runs(&self, limit: usize) -> Result<Vec<RunId>, StoreError> {
self.inner.abandoned_runs(limit).await
}
async fn waiting_runs(
&self,
limit: usize,
) -> Result<Vec<crate::journal::WaitingRun>, StoreError> {
self.inner.waiting_runs(limit).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 count_by_outcome(&self, outcome: &str) -> Result<u64, StoreError> {
self.inner.count_by_outcome(outcome).await
}
async fn admitted_as(&self, key: &str) -> Result<Option<RunId>, StoreError> {
self.inner.admitted_as(key).await
}
async fn forget_admissions(
&self,
older_than: crate::core::Timestamp,
) -> Result<usize, StoreError> {
self.inner.forget_admissions(older_than).await
}
async fn recent_runs(
&self,
after: Option<(u64, RunId)>,
limit: usize,
) -> Result<Vec<(RunId, u64)>, StoreError> {
self.inner.recent_runs(after, limit).await
}
async fn recent_runs_from(
&self,
source: &str,
after: Option<(u64, RunId)>,
limit: usize,
) -> Result<Vec<(RunId, u64)>, StoreError> {
self.inner.recent_runs_from(source, after, limit).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: &crate::core::Operator,
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 mut kind = record.kind().clone();
let opened = open_payloads(
self.keys.as_ref(),
self.tenant.as_str(),
record.body.run,
record.effect_key(),
&mut kind,
)
.await?;
out.push(if opened.opened > 0 {
record.with_opened_kind(kind)
} else {
record
});
}
Ok(out)
}
}
pub(crate) fn journal_aad(
tenant: &str,
run: RunId,
kind: &crate::journal::RecordKind,
effect: Option<crate::core::EffectKey>,
) -> String {
format!(
"journal:{tenant}:{run}:{}:{}",
kind.kind_str(),
effect.map_or_else(|| "-".to_owned(), crate::core::EffectKey::to_hex),
)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Opened {
pub(crate) opened: usize,
pub(crate) erased: usize,
}
pub(crate) async fn open_payloads(
keys: &dyn KeyRing,
tenant: &str,
run: RunId,
effect: Option<crate::core::EffectKey>,
kind: &mut crate::journal::RecordKind,
) -> Result<Opened, StoreError> {
let aad = journal_aad(tenant, run, kind, effect);
let mut found = Opened::default();
for field in payload::payloads(kind) {
match field {
payload::SealedField::Value(field) => {
let Some(envelope) = payload::unwrap(field) else {
continue;
};
match super::envelope::open_or_erased(keys, aad.as_bytes(), &envelope)
.await
.map_err(|e| StoreError::Backend(e.to_string()))?
{
Some(plain) => {
*field = serde_json::from_slice(&plain)?;
found.opened += 1;
}
None => found.erased += 1,
}
}
payload::SealedField::Text(field) => {
let Some(envelope) = payload::unwrap_text(field) else {
continue;
};
match super::envelope::open_or_erased(keys, aad.as_bytes(), &envelope)
.await
.map_err(|e| StoreError::Backend(e.to_string()))?
{
Some(plain) => {
if let Ok(text) = String::from_utf8(plain) {
*field = text;
found.opened += 1;
}
}
None => found.erased += 1,
}
}
}
}
Ok(found)
}