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 journal.validate_pending_mutation(
76 &self.authority,
77 self.mutation_attempt,
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}