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