Skip to main content

ic_backup/model/control_authority/
mod.rs

1//! Canonical bounded controller identities used by IC status decoding.
2
3use crate::model::attempt_journal::OperationBindingRecord;
4use thiserror::Error;
5
6/// Maximum controllers in the maintained IC controller-set boundary.
7pub const MAX_CONTROLLERS: usize = 10;
8
9/// Canonical bounded known controller set, including an explicitly known empty set.
10///
11/// This validates identities, not observation authenticity. Missing/unknown controller
12/// evidence must fail at the provider boundary, never default into a success result.
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct ControllerSet {
15    principals: Vec<String>,
16}
17impl ControllerSet {
18    /// Normalize and sort exact principals; equivalent duplicate entries reject.
19    /// # Errors
20    /// Rejects excessive counts, malformed principals and canonical duplicates.
21    pub fn new(mut principals: Vec<String>) -> Result<Self, ControlObservationError> {
22        if principals.len() > MAX_CONTROLLERS {
23            return Err(ControlObservationError::TooManyControllers);
24        }
25        for principal in &mut principals {
26            *principal = super::principal::canonical_text(principal)
27                .ok_or(ControlObservationError::InvalidPrincipal)?;
28        }
29        principals.sort();
30        if principals.windows(2).any(|pair| pair[0] == pair[1]) {
31            return Err(ControlObservationError::DuplicateController);
32        }
33        Ok(Self { principals })
34    }
35    /// Read canonical controllers; ordering grants no routing or lifecycle semantics.
36    #[must_use]
37    pub fn principals(&self) -> &[String] {
38        &self.principals
39    }
40    /// Check the exact canonical original caller, without delegating through other controllers.
41    #[must_use]
42    pub fn contains_caller(&self, binding: &OperationBindingRecord) -> bool {
43        self.principals
44            .binary_search_by(|principal| principal.as_str().cmp(binding.caller()))
45            .is_ok()
46    }
47}
48
49/// Invalid bounded controller identities; status decoding retains this public error.
50#[derive(Debug, Eq, Error, PartialEq)]
51pub enum ControlObservationError {
52    /// Controller principal failed bounded canonical admission.
53    #[error("invalid control observation principal")]
54    InvalidPrincipal,
55    /// Full set exceeds the maintained bound.
56    #[error("controller set exceeds {MAX_CONTROLLERS}")]
57    TooManyControllers,
58    /// Equivalent principal appears twice.
59    #[error("duplicate controller principal")]
60    DuplicateController,
61}
62
63#[cfg(test)]
64mod tests;