Skip to main content

ic_backup/model/ic_snapshot_upload/attempt/
mod.rs

1//! Original pending reservations over exact already retained upload payloads.
2
3use super::IcSnapshotUploadRequest;
4use crate::model::attempt_journal::MutationReservation;
5use crate::model::attempt_journal::MutationReservationError as Reservation;
6use crate::model::{
7    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
8    operation_plan::{OperationPlanError, OperationPlanRecord},
9};
10use thiserror::Error;
11
12/// Structural original pending upload identity; reconstruction is no redispatch permit.
13#[derive(Debug)]
14pub struct IcSnapshotUploadAttempt<'request, 'source> {
15    plan: &'request OperationPlanRecord,
16    payload: &'request IcSnapshotUploadRequest<'source>,
17    reservation: MutationReservation,
18}
19
20impl<'request, 'source> IcSnapshotUploadAttempt<'request, 'source> {
21    /// Bind the full original plan, exact target/bytes and pending mutation reservation.
22    ///
23    /// This performs no provider call, record creation, reservation or settlement.
24    /// A data payload includes its qualified new ID before its own plan is retained;
25    /// the prior metadata plan/allowance cannot be rebound into new data authority.
26    /// # Errors
27    /// Rejects unknown operations, different authority/payload, no pending mutation
28    /// or observation recovery already in progress.
29    pub fn new(
30        plan: &'request OperationPlanRecord,
31        operation_sequence: u64,
32        journal: &AttemptJournalRecord,
33        payload: &'request IcSnapshotUploadRequest<'source>,
34    ) -> Result<Self, IcSnapshotUploadAttemptError> {
35        let authority = original_authority(plan, operation_sequence, journal, payload)?;
36        let reservation = MutationReservation::new(authority, journal)?;
37        Ok(Self {
38            plan,
39            payload,
40            reservation,
41        })
42    }
43    /// Read the exact retained full original plan, context and allowances.
44    #[must_use]
45    pub const fn plan(&self) -> &'request OperationPlanRecord {
46        self.plan
47    }
48    /// Read the exact originally planned upload wire/source declaration.
49    #[must_use]
50    pub const fn payload(&self) -> &'request IcSnapshotUploadRequest<'source> {
51        self.payload
52    }
53    /// Read original plan-derived authority; it grants no fresh effect.
54    #[must_use]
55    pub const fn authority(&self) -> &AttemptAuthorityRecord {
56        &self.reservation.authority
57    }
58    /// Read the already consumed original mutation number.
59    #[must_use]
60    pub const fn mutation_attempt(&self) -> u32 {
61        self.reservation.mutation_attempt
62    }
63    /// Recheck exact current authority and reservation before passive association.
64    /// # Errors
65    /// Rejects changed/settled mutation, authority mismatch or pending observation.
66    pub fn validate_journal(
67        &self,
68        journal: &AttemptJournalRecord,
69    ) -> Result<(), IcSnapshotUploadAttemptError> {
70        self.reservation.validate(journal).map_err(Into::into)
71    }
72}
73
74pub(crate) fn original_authority(
75    plan: &OperationPlanRecord,
76    operation_sequence: u64,
77    journal: &AttemptJournalRecord,
78    payload: &IcSnapshotUploadRequest<'_>,
79) -> Result<AttemptAuthorityRecord, IcSnapshotUploadAttemptError> {
80    let authority = plan.attempt_authority(operation_sequence)?;
81    if journal.authority() != &authority {
82        return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
83    }
84    if payload.target() != authority.binding().target()
85        || payload.binding_digest().hash() != authority.binding().request()
86    {
87        return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
88    }
89    if !source_context_matches(plan, payload) {
90        return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
91    }
92    Ok(authority)
93}
94
95pub(crate) fn source_context_matches(
96    plan: &OperationPlanRecord,
97    payload: &IcSnapshotUploadRequest<'_>,
98) -> bool {
99    plan.context().network() == payload.source_plan().context().network()
100        && plan.context().release() == payload.source_plan().context().release()
101}
102
103/// Original upload reservation rejection; no variant changes spending or recovery.
104#[derive(Debug, Error)]
105pub enum IcSnapshotUploadAttemptError {
106    /// Upload intent changes the original source network or release identity.
107    #[error("snapshot upload source network or release differs")]
108    SourceContextMismatch,
109    /// Original full-plan authority/context/operation/allowances differ.
110    #[error("snapshot upload original authority mismatch")]
111    AuthorityMismatch,
112    /// Exact canonical target or original source/wire binding digest differs.
113    #[error("snapshot upload original payload mismatch")]
114    PayloadMismatch,
115    /// No original unresolved mutation was reserved.
116    #[error("snapshot upload requires an original pending mutation")]
117    NoPendingMutation,
118    /// Original mutation has changed or already settled.
119    #[error("snapshot upload original mutation differs")]
120    MutationMismatch,
121    /// An already reserved recovery observation prevents original submission association.
122    #[error("snapshot upload recovery observation is pending")]
123    ObservationPending,
124    /// Original full plan has no such operation or cannot derive its authority.
125    #[error(transparent)]
126    Plan(#[from] OperationPlanError),
127}
128
129impl From<crate::model::attempt_journal::MutationReservationError>
130    for IcSnapshotUploadAttemptError
131{
132    fn from(error: crate::model::attempt_journal::MutationReservationError) -> Self {
133        match error {
134            Reservation::NoPendingMutation => Self::NoPendingMutation,
135            Reservation::AuthorityMismatch => Self::AuthorityMismatch,
136            Reservation::MutationMismatch => Self::MutationMismatch,
137            Reservation::ObservationPending => Self::ObservationPending,
138        }
139    }
140}