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