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(operation_sequence)?;
81 if journal.authority() != &authority {
82 return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
83 }
84 if payload.target() != authority.binding().target()
85 || payload.binding_digest().hash() != authority.binding().request()
86 {
87 return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
88 }
89 if !source_context_matches(plan, payload) {
90 return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
91 }
92 Ok(authority)
93}
94
95pub(crate) fn source_context_matches(
96 plan: &OperationPlanRecord,
97 payload: &IcSnapshotUploadRequest<'_>,
98) -> bool {
99 plan.context().network() == payload.source_plan().context().network()
100 && plan.context().release() == payload.source_plan().context().release()
101}
102
103#[derive(Debug, Error)]
105pub enum IcSnapshotUploadAttemptError {
106 #[error("snapshot upload source network or release differs")]
108 SourceContextMismatch,
109 #[error("snapshot upload original authority mismatch")]
111 AuthorityMismatch,
112 #[error("snapshot upload original payload mismatch")]
114 PayloadMismatch,
115 #[error("snapshot upload requires an original pending mutation")]
117 NoPendingMutation,
118 #[error("snapshot upload original mutation differs")]
120 MutationMismatch,
121 #[error("snapshot upload recovery observation is pending")]
123 ObservationPending,
124 #[error(transparent)]
126 Plan(#[from] OperationPlanError),
127}
128
129impl From<crate::model::attempt_journal::MutationReservationError>
130 for IcSnapshotUploadAttemptError
131{
132 fn from(error: crate::model::attempt_journal::MutationReservationError) -> Self {
133 match error {
134 Reservation::NoPendingMutation => Self::NoPendingMutation,
135 Reservation::AuthorityMismatch => Self::AuthorityMismatch,
136 Reservation::MutationMismatch => Self::MutationMismatch,
137 Reservation::ObservationPending => Self::ObservationPending,
138 }
139 }
140}