use crate::model::{
artifacts::ArtifactChecksumRecord,
consistency::{
ApplicationFenceEvidence, ApplicationFenceState, CaptureState, ConsistencyEvidence,
ConsistencyGuaranteeRecord, ConsistencyObservation, ConsistencyRequest,
TargetCaptureEvidence,
},
};
use thiserror::Error;
#[derive(Clone, Debug)]
pub struct ConsistencyView<'a> {
request: ArtifactChecksumRecord,
requirement: ArtifactChecksumRecord,
observation: &'a ConsistencyObservation,
}
impl ConsistencyView<'_> {
#[must_use]
pub const fn request(&self) -> &ArtifactChecksumRecord {
&self.request
}
#[must_use]
pub const fn requirement(&self) -> &ArtifactChecksumRecord {
&self.requirement
}
#[must_use]
pub fn targets(&self) -> &[TargetCaptureEvidence] {
self.observation.targets()
}
#[must_use]
pub fn fence(&self) -> Option<&ApplicationFenceEvidence> {
match self.observation.consistency() {
ConsistencyEvidence::PerCanister => None,
ConsistencyEvidence::ApplicationCoordinated(fence) => Some(fence),
}
}
#[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: &ConsistencyRequest<'_>,
observation: &'a ConsistencyObservation,
) -> Result<ConsistencyView<'a>, ConsistencyError> {
let digest = request.digest();
if *observation.request() != digest {
return Err(ConsistencyError::RequestMismatch);
}
let binding = request.binding();
for (field, expected, actual) in [
(
"network",
binding.network(),
observation.context().network(),
),
("caller", binding.caller(), observation.context().caller()),
(
"release",
binding.release(),
observation.context().release(),
),
] {
if actual != expected {
return Err(ConsistencyError::ContextMismatch(field));
}
}
if observation.inventory() != request.inventory() {
return Err(ConsistencyError::InventoryMismatch);
}
if !observation
.targets()
.iter()
.map(|target| &target.target)
.eq(request.selected_targets())
{
return Err(ConsistencyError::SelectionMismatch);
}
if observation.remote_observations() > request.max_remote_observations() {
return Err(ConsistencyError::ObservationLimitExceeded {
limit: request.max_remote_observations(),
reported: observation.remote_observations(),
});
}
if observation
.targets()
.iter()
.any(|target| target.state != CaptureState::Stopped)
{
return Err(ConsistencyError::TargetNotStopped);
}
match (request.requirement().guarantee(), observation.consistency()) {
(ConsistencyGuaranteeRecord::PerCanister, ConsistencyEvidence::PerCanister) => {}
(
ConsistencyGuaranteeRecord::ApplicationCoordinated,
ConsistencyEvidence::ApplicationCoordinated(fence),
) => {
let expected = request
.expected_fence()
.ok_or(ConsistencyError::FenceMismatch)?;
if fence.identity != expected.identity {
return Err(ConsistencyError::FenceMismatch);
}
if fence.state != ApplicationFenceState::Active {
return Err(ConsistencyError::FenceNotActive);
}
let original_revision_matches =
observation.membership_revision() == Some(&expected.membership_revision);
let fence_revision_matches = fence.membership_revision == expected.membership_revision;
if !original_revision_matches || !fence_revision_matches {
return Err(ConsistencyError::MembershipRevisionMismatch);
}
}
_ => return Err(ConsistencyError::GuaranteeMismatch),
}
Ok(ConsistencyView {
request: digest,
requirement: request.requirement().digest(),
observation,
})
}
#[derive(Debug, Eq, Error, PartialEq)]
pub enum ConsistencyError {
#[error("consistency request mismatch")]
RequestMismatch,
#[error("consistency observed {0} mismatch")]
ContextMismatch(&'static str),
#[error("consistency inventory mismatch")]
InventoryMismatch,
#[error("consistency selected targets mismatch")]
SelectionMismatch,
#[error("consistency evidence guarantee mismatch")]
GuaranteeMismatch,
#[error("consistency target is not stopped")]
TargetNotStopped,
#[error("consistency fence identity mismatch")]
FenceMismatch,
#[error("consistency fence is not active")]
FenceNotActive,
#[error("consistency fence membership revision mismatch")]
MembershipRevisionMismatch,
#[error("consistency reports {reported} observations above ceiling {limit}")]
ObservationLimitExceeded {
limit: u32,
reported: u32,
},
}
#[cfg(test)]
mod tests;