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 !source_context_matches(plan, payload) {
105 return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
106 }
107 Ok(authority)
108}
109
110pub(crate) fn source_context_matches(
111 plan: &OperationPlanRecord,
112 payload: &IcSnapshotUploadRequest<'_>,
113) -> bool {
114 plan.context().network() == payload.source_plan().context().network()
115 && plan.context().release() == payload.source_plan().context().release()
116}
117
118#[derive(Debug, Error)]
120pub enum IcSnapshotUploadAttemptError {
121 #[error("snapshot upload source network or release differs")]
123 SourceContextMismatch,
124 #[error("snapshot upload original authority mismatch")]
126 AuthorityMismatch,
127 #[error("snapshot upload original payload mismatch")]
129 PayloadMismatch,
130 #[error("snapshot upload requires an original pending mutation")]
132 NoPendingMutation,
133 #[error("snapshot upload original mutation differs")]
135 MutationMismatch,
136 #[error("snapshot upload recovery observation is pending")]
138 ObservationPending,
139 #[error(transparent)]
141 Plan(#[from] OperationPlanError),
142}