use crate::model::{
artifacts::ArtifactChecksumRecord,
snapshot_read::{SnapshotReadObservation, SnapshotReadRequest, SnapshotVisibility},
};
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SnapshotReadPath {
Controller,
Public,
AllowedViewer,
}
#[derive(Clone, Debug)]
pub struct SnapshotReadView<'a> {
request: ArtifactChecksumRecord,
observation: &'a SnapshotReadObservation,
path: SnapshotReadPath,
}
impl SnapshotReadView<'_> {
#[must_use]
pub const fn request(&self) -> &ArtifactChecksumRecord {
&self.request
}
#[must_use]
pub fn target(&self) -> &str {
self.observation.target()
}
#[must_use]
pub const fn visibility(&self) -> &SnapshotVisibility {
self.observation.visibility()
}
#[must_use]
pub const fn path(&self) -> SnapshotReadPath {
self.path
}
#[must_use]
pub const fn evidence(&self) -> &ArtifactChecksumRecord {
self.observation.evidence()
}
#[must_use]
pub const fn remote_observations(&self) -> u32 {
self.observation.remote_observations()
}
}
pub fn validate<'a>(
request: &SnapshotReadRequest<'_>,
observation: &'a SnapshotReadObservation,
) -> Result<SnapshotReadView<'a>, SnapshotReadError> {
let digest = request.digest();
let binding = request.binding();
super::observed_context::admit_request_context(
&digest,
observation.request(),
binding,
observation.context(),
SnapshotReadError::RequestMismatch,
SnapshotReadError::ContextMismatch,
)?;
super::observed_context::admit_target_limit(
binding,
observation.target(),
request.max_remote_observations(),
observation.remote_observations(),
SnapshotReadError::TargetMismatch,
|limit, reported| SnapshotReadError::ObservationLimitExceeded { limit, reported },
)?;
let controller = observation
.controllers()
.is_some_and(|set| set.contains_caller(binding));
let path = if controller {
SnapshotReadPath::Controller
} else {
match observation.visibility() {
SnapshotVisibility::Public => SnapshotReadPath::Public,
SnapshotVisibility::AllowedViewers(viewers) if viewers.contains_caller(binding) => {
SnapshotReadPath::AllowedViewer
}
SnapshotVisibility::Controllers | SnapshotVisibility::AllowedViewers(_) => {
return Err(if observation.controllers().is_none() {
SnapshotReadError::ControllersUnobserved
} else {
SnapshotReadError::CallerCannotRead
});
}
}
};
Ok(SnapshotReadView {
request: digest,
observation,
path,
})
}
#[derive(Debug, Eq, Error, PartialEq)]
pub enum SnapshotReadError {
#[error("snapshot read request mismatch")]
RequestMismatch,
#[error("snapshot read observed {0} mismatch")]
ContextMismatch(&'static str),
#[error("snapshot read observed target mismatch")]
TargetMismatch,
#[error("snapshot read requires unobserved controller evidence")]
ControllersUnobserved,
#[error("selected caller cannot read observed snapshots")]
CallerCannotRead,
#[error("snapshot read reports {reported} observations above ceiling {limit}")]
ObservationLimitExceeded {
limit: u32,
reported: u32,
},
}
#[cfg(test)]
mod tests;