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