use crate::model::{
artifacts::ArtifactChecksumRecord,
control_authority::{ControlObservation, ControlObservationRequest, ControllerSet},
};
use thiserror::Error;
#[derive(Clone, Debug)]
pub struct ControlAuthorityView<'a> {
request: ArtifactChecksumRecord,
observation: &'a ControlObservation,
}
impl ControlAuthorityView<'_> {
#[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 controllers(&self) -> &ControllerSet {
self.observation.controllers()
}
#[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: &ControlObservationRequest<'_>,
observation: &'a ControlObservation,
) -> Result<ControlAuthorityView<'a>, ControlAuthorityError> {
let digest = request.digest();
if *observation.request() != digest {
return Err(ControlAuthorityError::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(ControlAuthorityError::ContextMismatch(field));
}
}
if observation.target() != binding.target() {
return Err(ControlAuthorityError::TargetMismatch);
}
if observation.remote_observations() > request.max_remote_observations() {
return Err(ControlAuthorityError::ObservationLimitExceeded {
limit: request.max_remote_observations(),
reported: observation.remote_observations(),
});
}
if !observation.controllers().contains_caller(binding) {
return Err(ControlAuthorityError::CallerNotController);
}
Ok(ControlAuthorityView {
request: digest,
observation,
})
}
#[derive(Debug, Eq, Error, PartialEq)]
pub enum ControlAuthorityError {
#[error("control observation request mismatch")]
RequestMismatch,
#[error("control observed {0} mismatch")]
ContextMismatch(&'static str),
#[error("control observed target mismatch")]
TargetMismatch,
#[error("selected caller is not an observed controller")]
CallerNotController,
#[error("control reports {reported} observations above ceiling {limit}")]
ObservationLimitExceeded {
limit: u32,
reported: u32,
},
}
#[cfg(test)]
mod tests;