Skip to main content

ic_backup/model/ic_update_response/
mod.rs

1//! Shared passive IC update-response fields, bounded admission and redacted formatting.
2
3use crate::model::{
4    artifacts::ArtifactChecksumRecord, attempt_journal::MAX_OPERATION_ATTEMPTS,
5    operation_plan::PlanContextRecord,
6};
7use std::fmt;
8use thiserror::Error;
9
10/// Passive provider fields; actual authentication and association are integration-owned.
11#[derive(Clone)]
12pub struct IcUpdateResponseInput {
13    /// Full original authority digest, including plan/context/operation/limits.
14    pub authority: ArtifactChecksumRecord,
15    /// Original already reserved mutation attempt, never a new call number.
16    pub mutation_attempt: u32,
17    /// Actual authenticated network/caller/release claimed by the provider.
18    pub context: PlanContextRecord,
19    /// Actual response routing target claimed by the provider; canonicalized on admission.
20    pub target: String,
21    /// Exact raw Candid reply, retained under the existing codec byte ceilings.
22    pub reply: Vec<u8>,
23    /// Opaque provider evidence binding original request/attempt and all actual fields.
24    pub evidence: ArtifactChecksumRecord,
25}
26
27/// Passive response admission failure; no variant grants authority or changes spending.
28#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
29pub enum IcUpdateResponseError {
30    /// Original attempt is outside the existing 1..=1,024 bound.
31    #[error("invalid IC update response attempt")]
32    InvalidAttempt,
33    /// Raw reply exceeds the selected response type's finite byte ceiling.
34    #[error("IC update response reply too large")]
35    ReplyTooLarge,
36    /// Actual provider target is not a principal.
37    #[error("invalid IC update response target")]
38    InvalidTarget,
39}
40
41impl IcUpdateResponseInput {
42    pub(crate) fn admit(&mut self, max_reply_bytes: usize) -> Result<(), IcUpdateResponseError> {
43        if self.mutation_attempt == 0 || self.mutation_attempt > MAX_OPERATION_ATTEMPTS {
44            return Err(IcUpdateResponseError::InvalidAttempt);
45        }
46        if self.reply.len() > max_reply_bytes {
47            return Err(IcUpdateResponseError::ReplyTooLarge);
48        }
49        self.target = crate::model::principal::canonical_text(&self.target)
50            .ok_or(IcUpdateResponseError::InvalidTarget)?;
51        Ok(())
52    }
53
54    pub(crate) fn fmt_redacted(
55        &self,
56        formatter: &mut fmt::Formatter<'_>,
57        name: &str,
58    ) -> fmt::Result {
59        formatter
60            .debug_struct(name)
61            .field("authority", &self.authority)
62            .field("mutation_attempt", &self.mutation_attempt)
63            .field("target", &self.target)
64            .field("reply_bytes", &self.reply.len())
65            .finish_non_exhaustive()
66    }
67}
68
69#[cfg(test)]
70mod tests;