use super::IcSnapshotUploadRequest;
use crate::model::attempt_journal::MutationReservation;
use crate::model::{
attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
operation_plan::{OperationPlanError, OperationPlanRecord},
};
use thiserror::Error;
#[derive(Debug)]
pub struct IcSnapshotUploadAttempt<'request, 'source> {
plan: &'request OperationPlanRecord,
payload: &'request IcSnapshotUploadRequest<'source>,
reservation: MutationReservation,
}
impl<'request, 'source> IcSnapshotUploadAttempt<'request, 'source> {
pub fn new(
plan: &'request OperationPlanRecord,
operation_sequence: u64,
journal: &AttemptJournalRecord,
payload: &'request IcSnapshotUploadRequest<'source>,
) -> Result<Self, IcSnapshotUploadAttemptError> {
let authority = original_authority(plan, operation_sequence, journal, payload)?;
let reservation = MutationReservation::new(
authority,
journal,
IcSnapshotUploadAttemptError::NoPendingMutation,
IcSnapshotUploadAttemptError::AuthorityMismatch,
IcSnapshotUploadAttemptError::MutationMismatch,
IcSnapshotUploadAttemptError::ObservationPending,
)?;
Ok(Self {
plan,
payload,
reservation,
})
}
#[must_use]
pub const fn plan(&self) -> &'request OperationPlanRecord {
self.plan
}
#[must_use]
pub const fn payload(&self) -> &'request IcSnapshotUploadRequest<'source> {
self.payload
}
#[must_use]
pub const fn authority(&self) -> &AttemptAuthorityRecord {
&self.reservation.authority
}
#[must_use]
pub const fn mutation_attempt(&self) -> u32 {
self.reservation.mutation_attempt
}
pub fn validate_journal(
&self,
journal: &AttemptJournalRecord,
) -> Result<(), IcSnapshotUploadAttemptError> {
self.reservation.validate(
journal,
IcSnapshotUploadAttemptError::AuthorityMismatch,
IcSnapshotUploadAttemptError::MutationMismatch,
IcSnapshotUploadAttemptError::ObservationPending,
)
}
}
pub(crate) fn original_authority(
plan: &OperationPlanRecord,
operation_sequence: u64,
journal: &AttemptJournalRecord,
payload: &IcSnapshotUploadRequest<'_>,
) -> Result<AttemptAuthorityRecord, IcSnapshotUploadAttemptError> {
let authority = plan.attempt_authority(operation_sequence)?;
if journal.authority() != &authority {
return Err(IcSnapshotUploadAttemptError::AuthorityMismatch);
}
if payload.target() != authority.binding().target()
|| payload.binding_digest().hash() != authority.binding().request()
{
return Err(IcSnapshotUploadAttemptError::PayloadMismatch);
}
if !source_context_matches(plan, payload) {
return Err(IcSnapshotUploadAttemptError::SourceContextMismatch);
}
Ok(authority)
}
pub(crate) fn source_context_matches(
plan: &OperationPlanRecord,
payload: &IcSnapshotUploadRequest<'_>,
) -> bool {
plan.context().network() == payload.source_plan().context().network()
&& plan.context().release() == payload.source_plan().context().release()
}
#[derive(Debug, Error)]
pub enum IcSnapshotUploadAttemptError {
#[error("snapshot upload source network or release differs")]
SourceContextMismatch,
#[error("snapshot upload original authority mismatch")]
AuthorityMismatch,
#[error("snapshot upload original payload mismatch")]
PayloadMismatch,
#[error("snapshot upload requires an original pending mutation")]
NoPendingMutation,
#[error("snapshot upload original mutation differs")]
MutationMismatch,
#[error("snapshot upload recovery observation is pending")]
ObservationPending,
#[error(transparent)]
Plan(#[from] OperationPlanError),
}