use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::OperationBindingRecord,
inventory::InventoryRecord,
operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
};
use thiserror::Error;
pub const MAX_MEMBERSHIP_REMOTE_OBSERVATIONS: u32 = 1024;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MembershipBoundary {
BeforeEffect,
AfterEffect,
}
#[derive(Clone, Debug)]
pub struct MembershipObservationRequest<'a> {
plan: &'a OperationPlanRecord,
binding: OperationBindingRecord,
challenge: ArtifactChecksumRecord,
boundary: MembershipBoundary,
max_remote_observations: u32,
}
impl<'a> MembershipObservationRequest<'a> {
pub fn new(
plan: &'a OperationPlanRecord,
operation_sequence: u64,
challenge: ArtifactChecksumRecord,
boundary: MembershipBoundary,
max_remote_observations: u32,
) -> Result<Self, MembershipRequestError> {
if max_remote_observations > MAX_MEMBERSHIP_REMOTE_OBSERVATIONS {
return Err(MembershipRequestError::ObservationLimitTooLarge);
}
let binding = plan
.attempt_authority(operation_sequence)?
.binding()
.clone();
Ok(Self {
plan,
binding,
challenge,
boundary,
max_remote_observations,
})
}
#[must_use]
pub const fn binding(&self) -> &OperationBindingRecord {
&self.binding
}
#[must_use]
pub const fn inventory(&self) -> &InventoryRecord {
self.plan.inventory()
}
#[must_use]
pub fn selected_targets(&self) -> &[String] {
self.plan.selected_targets()
}
#[must_use]
pub const fn challenge(&self) -> &ArtifactChecksumRecord {
&self.challenge
}
#[must_use]
pub const fn boundary(&self) -> MembershipBoundary {
self.boundary
}
#[must_use]
pub const fn max_remote_observations(&self) -> u32 {
self.max_remote_observations
}
#[must_use]
pub fn digest(&self) -> ArtifactChecksumRecord {
let mut bytes = b"ic-backup/membership-request/v1\0".to_vec();
bytes.extend_from_slice(self.binding.intent().as_bytes());
bytes.extend_from_slice(&self.binding.operation_sequence().to_be_bytes());
bytes.extend_from_slice(self.challenge.hash().as_bytes());
bytes.push(match self.boundary {
MembershipBoundary::BeforeEffect => 0,
MembershipBoundary::AfterEffect => 1,
});
bytes.extend_from_slice(&self.max_remote_observations.to_be_bytes());
ArtifactChecksumRecord::from_bytes(&bytes)
}
}
#[derive(Clone, Debug)]
pub struct MembershipObservation {
pub request: ArtifactChecksumRecord,
pub context: PlanContextRecord,
pub inventory: InventoryRecord,
pub revision: Option<ArtifactChecksumRecord>,
pub evidence: ArtifactChecksumRecord,
pub remote_observations: u32,
}
#[derive(Debug, Error)]
pub enum MembershipRequestError {
#[error("membership call ceiling exceeds {MAX_MEMBERSHIP_REMOTE_OBSERVATIONS}")]
ObservationLimitTooLarge,
#[error(transparent)]
Plan(#[from] OperationPlanError),
}
#[cfg(test)]
mod tests;