ic_backup/model/ic_snapshot_upload/attempt/
mod.rs1use 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#[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 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 #[must_use]
45 pub const fn plan(&self) -> &'request OperationPlanRecord {
46 self.plan
47 }
48 #[must_use]
50 pub const fn payload(&self) -> &'request IcSnapshotUploadRequest<'source> {
51 self.payload
52 }
53 #[must_use]
55 pub const fn authority(&self) -> &AttemptAuthorityRecord {
56 &self.reservation.authority
57 }
58 #[must_use]
60 pub const fn mutation_attempt(&self) -> u32 {
61 self.reservation.mutation_attempt
62 }
63 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#[derive(Debug, Error)]
106pub enum IcSnapshotUploadAttemptError {
107 #[error("snapshot upload source network or release differs")]
109 SourceContextMismatch,
110 #[error("snapshot upload original authority mismatch")]
112 AuthorityMismatch,
113 #[error("snapshot upload original payload mismatch")]
115 PayloadMismatch,
116 #[error("snapshot upload requires an original pending mutation")]
118 NoPendingMutation,
119 #[error("snapshot upload original mutation differs")]
121 MutationMismatch,
122 #[error("snapshot upload recovery observation is pending")]
124 ObservationPending,
125 #[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}