use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::OperationBindingRecord,
ic_request::{IcManagementRequestRecord, IcRequestError},
operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
};
use thiserror::Error;
pub const MAX_CONTROLLERS: usize = 10;
pub const MAX_CONTROL_REMOTE_OBSERVATIONS: u32 = 1024;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ControllerSet {
principals: Vec<String>,
}
impl ControllerSet {
pub fn new(mut principals: Vec<String>) -> Result<Self, ControlObservationError> {
if principals.len() > MAX_CONTROLLERS {
return Err(ControlObservationError::TooManyControllers);
}
for principal in &mut principals {
*principal = super::principal::canonical_text(principal)
.ok_or(ControlObservationError::InvalidPrincipal)?;
}
principals.sort();
if principals.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(ControlObservationError::DuplicateController);
}
Ok(Self { principals })
}
#[must_use]
pub fn principals(&self) -> &[String] {
&self.principals
}
#[must_use]
pub fn contains_caller(&self, binding: &OperationBindingRecord) -> bool {
self.principals
.binary_search_by(|principal| principal.as_str().cmp(binding.caller()))
.is_ok()
}
}
#[derive(Clone, Debug)]
pub struct ControlObservationRequest<'a> {
binding: OperationBindingRecord,
wire: &'a IcManagementRequestRecord,
challenge: ArtifactChecksumRecord,
max_remote_observations: u32,
}
impl<'a> ControlObservationRequest<'a> {
pub fn new(
plan: &OperationPlanRecord,
sequence: u64,
wire: &'a IcManagementRequestRecord,
challenge: ArtifactChecksumRecord,
max_remote_observations: u32,
) -> Result<Self, ControlRequestError> {
if max_remote_observations > MAX_CONTROL_REMOTE_OBSERVATIONS {
return Err(ControlRequestError::ObservationLimitTooLarge);
}
let binding = plan.attempt_authority(sequence)?.binding().clone();
wire.validate_mutation_binding(&binding)?;
Ok(Self {
binding,
wire,
challenge,
max_remote_observations,
})
}
#[must_use]
pub const fn binding(&self) -> &OperationBindingRecord {
&self.binding
}
#[must_use]
pub const fn wire(&self) -> &IcManagementRequestRecord {
self.wire
}
#[must_use]
pub const fn challenge(&self) -> &ArtifactChecksumRecord {
&self.challenge
}
#[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/control-observation/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.wire.digest().hash().as_bytes());
bytes.extend_from_slice(self.challenge.hash().as_bytes());
bytes.extend_from_slice(&self.max_remote_observations.to_be_bytes());
ArtifactChecksumRecord::from_bytes(&bytes)
}
}
#[derive(Clone, Debug)]
pub struct ControlObservationInput {
pub request: ArtifactChecksumRecord,
pub context: PlanContextRecord,
pub target: String,
pub controllers: ControllerSet,
pub evidence: ArtifactChecksumRecord,
pub remote_observations: u32,
}
#[derive(Clone, Debug)]
pub struct ControlObservation {
input: ControlObservationInput,
}
impl ControlObservation {
pub fn new(mut input: ControlObservationInput) -> Result<Self, ControlObservationError> {
input.target = super::principal::canonical_text(&input.target)
.ok_or(ControlObservationError::InvalidPrincipal)?;
Ok(Self { input })
}
#[must_use]
pub const fn request(&self) -> &ArtifactChecksumRecord {
&self.input.request
}
#[must_use]
pub const fn context(&self) -> &PlanContextRecord {
&self.input.context
}
#[must_use]
pub fn target(&self) -> &str {
&self.input.target
}
#[must_use]
pub const fn controllers(&self) -> &ControllerSet {
&self.input.controllers
}
#[must_use]
pub const fn evidence(&self) -> &ArtifactChecksumRecord {
&self.input.evidence
}
#[must_use]
pub const fn remote_observations(&self) -> u32 {
self.input.remote_observations
}
}
#[derive(Debug, Eq, Error, PartialEq)]
pub enum ControlObservationError {
#[error("invalid control observation principal")]
InvalidPrincipal,
#[error("controller set exceeds {MAX_CONTROLLERS}")]
TooManyControllers,
#[error("duplicate controller principal")]
DuplicateController,
}
#[derive(Debug, Error)]
pub enum ControlRequestError {
#[error("control observation ceiling exceeds {MAX_CONTROL_REMOTE_OBSERVATIONS}")]
ObservationLimitTooLarge,
#[error(transparent)]
Plan(#[from] OperationPlanError),
#[error(transparent)]
Payload(#[from] IcRequestError),
}
#[cfg(test)]
mod tests;