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