use crate::model::{
artifacts::ArtifactChecksumRecord,
inventory::InventoryRecord,
membership::{MembershipObservation, MembershipObservationRequest},
};
use thiserror::Error;
#[derive(Clone, Debug)]
pub struct MembershipView<'a> {
request: ArtifactChecksumRecord,
inventory: &'a InventoryRecord,
selected_targets: &'a [String],
revision: Option<&'a ArtifactChecksumRecord>,
evidence: &'a ArtifactChecksumRecord,
remote_observations: u32,
}
impl MembershipView<'_> {
#[must_use]
pub const fn request(&self) -> &ArtifactChecksumRecord {
&self.request
}
#[must_use]
pub const fn inventory(&self) -> &InventoryRecord {
self.inventory
}
#[must_use]
pub const fn selected_targets(&self) -> &[String] {
self.selected_targets
}
#[must_use]
pub const fn revision(&self) -> Option<&ArtifactChecksumRecord> {
self.revision
}
#[must_use]
pub const fn evidence(&self) -> &ArtifactChecksumRecord {
self.evidence
}
#[must_use]
pub const fn remote_observations(&self) -> u32 {
self.remote_observations
}
}
pub fn validate<'a>(
request: &'a MembershipObservationRequest<'_>,
observation: &'a MembershipObservation,
) -> Result<MembershipView<'a>, MembershipError> {
let digest = request.digest();
if observation.request != digest {
return Err(MembershipError::RequestMismatch);
}
let binding = request.binding();
if observation.context.network() != binding.network() {
return Err(MembershipError::ContextMismatch("network"));
}
if observation.context.caller() != binding.caller() {
return Err(MembershipError::ContextMismatch("caller"));
}
if observation.context.release() != binding.release() {
return Err(MembershipError::ContextMismatch("release"));
}
if observation.inventory != *request.inventory() {
return Err(MembershipError::InventoryMismatch);
}
if observation.remote_observations > request.max_remote_observations() {
return Err(MembershipError::ObservationLimitExceeded {
limit: request.max_remote_observations(),
reported: observation.remote_observations,
});
}
Ok(MembershipView {
request: digest,
inventory: &observation.inventory,
selected_targets: request.selected_targets(),
revision: observation.revision.as_ref(),
evidence: &observation.evidence,
remote_observations: observation.remote_observations,
})
}
#[derive(Debug, Eq, Error, PartialEq)]
pub enum MembershipError {
#[error("membership request identity mismatch")]
RequestMismatch,
#[error("membership observed {0} mismatch")]
ContextMismatch(&'static str),
#[error("membership inventory mismatch")]
InventoryMismatch,
#[error("membership reports {reported} observations above ceiling {limit}")]
ObservationLimitExceeded {
limit: u32,
reported: u32,
},
}
#[cfg(test)]
mod tests;