ic_backup/model/ic_snapshot_upload/attempt/
mod.rs1use 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#[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 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(
36 authority,
37 journal,
38 IcSnapshotUploadAttemptError::NoPendingMutation,
39 IcSnapshotUploadAttemptError::AuthorityMismatch,
40 IcSnapshotUploadAttemptError::MutationMismatch,
41 IcSnapshotUploadAttemptError::ObservationPending,
42 )?;
43 Ok(Self {
44 plan,
45 payload,
46 reservation,
47 })
48 }
49 #[must_use]
51 pub const fn plan(&self) -> &'request OperationPlanRecord {
52 self.plan
53 }
54 #[must_use]
56 pub const fn payload(&self) -> &'request IcSnapshotUploadRequest<'source> {
57 self.payload
58 }
59 #[must_use]
61 pub const fn authority(&self) -> &AttemptAuthorityRecord {
62 &self.reservation.authority
63 }
64 #[must_use]
66 pub const fn mutation_attempt(&self) -> u32 {
67 self.reservation.mutation_attempt
68 }
69 pub fn validate_journal(
73 &self,
74 journal: &AttemptJournalRecord,
75 ) -> Result<(), IcSnapshotUploadAttemptError> {
76 self.reservation.validate(
77 journal,
78 IcSnapshotUploadAttemptError::AuthorityMismatch,
79 IcSnapshotUploadAttemptError::MutationMismatch,
80 IcSnapshotUploadAttemptError::ObservationPending,
81 )
82 }
83}
84
85pub(crate) fn original_authority(
86 plan: &OperationPlanRecord,
87 operation_sequence: u64,
88 journal: &AttemptJournalRecord,
89 payload: &IcSnapshotUploadRequest<'_>,
90) -> Result<AttemptAuthorityRecord, IcSnapshotUploadAttemptError> {
91 let authority = plan.attempt_authority(operation_sequence)?;
92 if journal.authority() != &authority {
93 return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
94 }
95 if payload.target() != authority.binding().target()
96 || payload.binding_digest().hash() != authority.binding().request()
97 {
98 return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
99 }
100 if !source_context_matches(plan, payload) {
101 return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
102 }
103 Ok(authority)
104}
105
106pub(crate) fn source_context_matches(
107 plan: &OperationPlanRecord,
108 payload: &IcSnapshotUploadRequest<'_>,
109) -> bool {
110 plan.context().network() == payload.source_plan().context().network()
111 && plan.context().release() == payload.source_plan().context().release()
112}
113
114#[derive(Debug, Error)]
116pub enum IcSnapshotUploadAttemptError {
117 #[error("snapshot upload source network or release differs")]
119 SourceContextMismatch,
120 #[error("snapshot upload original authority mismatch")]
122 AuthorityMismatch,
123 #[error("snapshot upload original payload mismatch")]
125 PayloadMismatch,
126 #[error("snapshot upload requires an original pending mutation")]
128 NoPendingMutation,
129 #[error("snapshot upload original mutation differs")]
131 MutationMismatch,
132 #[error("snapshot upload recovery observation is pending")]
134 ObservationPending,
135 #[error(transparent)]
137 Plan(#[from] OperationPlanError),
138}