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_for_journal(
81        operation_sequence,
82        journal,
83        IcSnapshotUploadAttemptError::AuthorityMismatch,
84    )?;
85    if payload.target() != authority.binding().target()
86        || payload.binding_digest().hash() != authority.binding().request()
87    {
88        return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
89    }
90    if !source_context_matches(plan, payload) {
91        return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
92    }
93    Ok(authority)
94}
95
96pub(crate) fn source_context_matches(
97    plan: &OperationPlanRecord,
98    payload: &IcSnapshotUploadRequest<'_>,
99) -> bool {
100    plan.context().network() == payload.source_plan().context().network()
101        && plan.context().release() == payload.source_plan().context().release()
102}
103
104/// Original upload reservation rejection; no variant changes spending or recovery.
105#[derive(Debug, Error)]
106pub enum IcSnapshotUploadAttemptError {
107    /// Upload intent changes the original source network or release identity.
108    #[error("snapshot upload source network or release differs")]
109    SourceContextMismatch,
110    /// Original full-plan authority/context/operation/allowances differ.
111    #[error("snapshot upload original authority mismatch")]
112    AuthorityMismatch,
113    /// Exact canonical target or original source/wire binding digest differs.
114    #[error("snapshot upload original payload mismatch")]
115    PayloadMismatch,
116    /// No original unresolved mutation was reserved.
117    #[error("snapshot upload requires an original pending mutation")]
118    NoPendingMutation,
119    /// Original mutation has changed or already settled.
120    #[error("snapshot upload original mutation differs")]
121    MutationMismatch,
122    /// An already reserved recovery observation prevents original submission association.
123    #[error("snapshot upload recovery observation is pending")]
124    ObservationPending,
125    /// Original full plan has no such operation or cannot derive its authority.
126    #[error(transparent)]
127    Plan(#[from] OperationPlanError),
128}
129
130impl From<crate::model::attempt_journal::MutationReservationError>
131    for IcSnapshotUploadAttemptError
132{
133    fn from(error: crate::model::attempt_journal::MutationReservationError) -> Self {
134        match error {
135            Reservation::NoPendingMutation => Self::NoPendingMutation,
136            Reservation::AuthorityMismatch => Self::AuthorityMismatch,
137            Reservation::MutationMismatch => Self::MutationMismatch,
138            Reservation::ObservationPending => Self::ObservationPending,
139        }
140    }
141}