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