ic_backup/model/ic_snapshot_upload/attempt/
mod.rs1use super::IcSnapshotUploadRequest;
4use crate::model::{
5 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
6 operation_plan::{OperationPlanError, OperationPlanRecord},
7};
8use thiserror::Error;
9
10#[derive(Debug)]
12pub struct IcSnapshotUploadAttempt<'request, 'source> {
13 plan: &'request OperationPlanRecord,
14 payload: &'request IcSnapshotUploadRequest<'source>,
15 authority: AttemptAuthorityRecord,
16 mutation_attempt: u32,
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 mutation_attempt = journal
36 .view()
37 .pending_mutation
38 .ok_or(IcSnapshotUploadAttemptError::NoPendingMutation)?;
39 let attempt = Self {
40 plan,
41 payload,
42 authority,
43 mutation_attempt,
44 };
45 attempt.validate_journal(journal)?;
46 Ok(attempt)
47 }
48 #[must_use]
50 pub const fn plan(&self) -> &'request OperationPlanRecord {
51 self.plan
52 }
53 #[must_use]
55 pub const fn payload(&self) -> &'request IcSnapshotUploadRequest<'source> {
56 self.payload
57 }
58 #[must_use]
60 pub const fn authority(&self) -> &AttemptAuthorityRecord {
61 &self.authority
62 }
63 #[must_use]
65 pub const fn mutation_attempt(&self) -> u32 {
66 self.mutation_attempt
67 }
68 pub fn validate_journal(
72 &self,
73 journal: &AttemptJournalRecord,
74 ) -> Result<(), IcSnapshotUploadAttemptError> {
75 if journal.authority() != &self.authority {
76 return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
77 }
78 let view = journal.view();
79 if view.pending_mutation != Some(self.mutation_attempt) {
80 return Err(IcSnapshotUploadAttemptError::MutationMismatch);
81 }
82 if view.pending_observation.is_some() {
83 return Err(IcSnapshotUploadAttemptError::ObservationPending);
84 }
85 Ok(())
86 }
87}
88
89pub(crate) fn original_authority(
90 plan: &OperationPlanRecord,
91 operation_sequence: u64,
92 journal: &AttemptJournalRecord,
93 payload: &IcSnapshotUploadRequest<'_>,
94) -> Result<AttemptAuthorityRecord, IcSnapshotUploadAttemptError> {
95 let authority = plan.attempt_authority(operation_sequence)?;
96 if journal.authority() != &authority {
97 return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
98 }
99 if payload.target() != authority.binding().target()
100 || payload.binding_digest().hash() != authority.binding().request()
101 {
102 return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
103 }
104 if plan.context().network() != payload.source_plan().context().network()
105 || plan.context().release() != payload.source_plan().context().release()
106 {
107 return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
108 }
109 Ok(authority)
110}
111
112#[derive(Debug, Error)]
114pub enum IcSnapshotUploadAttemptError {
115 #[error("snapshot upload source network or release differs")]
117 SourceContextMismatch,
118 #[error("snapshot upload original authority mismatch")]
120 AuthorityMismatch,
121 #[error("snapshot upload original payload mismatch")]
123 PayloadMismatch,
124 #[error("snapshot upload requires an original pending mutation")]
126 NoPendingMutation,
127 #[error("snapshot upload original mutation differs")]
129 MutationMismatch,
130 #[error("snapshot upload recovery observation is pending")]
132 ObservationPending,
133 #[error(transparent)]
135 Plan(#[from] OperationPlanError),
136}