use std::panic::{AssertUnwindSafe, catch_unwind};
use rustev_contract::Document;
use rustev_contract::limits::EXCHANGE_V1;
use rustev_contract::remote::ExchangeRecord;
use rustev_contract::run::{EvidenceUnavailable, ExchangeReference, RunRecord};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExchangeLookup {
Bytes(Vec<u8>),
Missing,
Expired,
Forbidden,
}
pub trait ExchangeStore {
fn lookup(&self, locator: &str, max_bytes: usize) -> ExchangeLookup;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemoteAvailability {
Available,
Missing,
Expired,
Forbidden,
Corrupt,
Inaccessible,
NotReferenced(EvidenceUnavailable),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttemptAvailability {
pub attempt_id: String,
pub availability: RemoteAvailability,
}
pub fn availability(run: &RunRecord, store: &dyn ExchangeStore) -> Vec<AttemptAvailability> {
let Some(entries) = &run.remote_evidence else {
return vec![];
};
entries
.iter()
.map(|e| AttemptAvailability {
attempt_id: e.attempt_id.clone(),
availability: match &e.exchange {
ExchangeReference::Unavailable { reason } => {
RemoteAvailability::NotReferenced(*reason)
}
ExchangeReference::Retained {
digest,
schema,
locator,
..
} => check(store, locator, schema, digest),
},
})
.collect()
}
fn check(
store: &dyn ExchangeStore,
locator: &str,
schema: &str,
digest: &str,
) -> RemoteAvailability {
if schema != <ExchangeRecord as Document>::SCHEMA {
return RemoteAvailability::Corrupt;
}
let max = EXCHANGE_V1.max_bytes;
let found = catch_unwind(AssertUnwindSafe(|| store.lookup(locator, max)));
match found {
Err(_) => RemoteAvailability::Inaccessible,
Ok(ExchangeLookup::Missing) => RemoteAvailability::Missing,
Ok(ExchangeLookup::Expired) => RemoteAvailability::Expired,
Ok(ExchangeLookup::Forbidden) => RemoteAvailability::Forbidden,
Ok(ExchangeLookup::Bytes(b)) if b.len() > max => RemoteAvailability::Corrupt,
Ok(ExchangeLookup::Bytes(b)) => match ExchangeRecord::parse_checked(&b) {
Ok(r)
if r.record_digest().ok().map(|d| d.as_str().to_string())
== Some(digest.to_string())
&& r.record_canonical().ok().as_deref() == Some(&b[..]) =>
{
RemoteAvailability::Available
}
_ => RemoteAvailability::Corrupt,
},
}
}