use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
ic_snapshot_data::{IcSnapshotDataRequest, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES},
ic_snapshot_metadata::IcSnapshotMetadataRequest,
operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
};
use std::fmt;
use thiserror::Error;
#[derive(Clone, Copy, Debug)]
pub enum IcSnapshotTransferReadPayload<'request, 'metadata> {
Metadata(&'request IcSnapshotMetadataRequest),
Data(&'request IcSnapshotDataRequest<'metadata>),
}
impl IcSnapshotTransferReadPayload<'_, '_> {
#[must_use]
pub fn target(&self) -> &str {
match self {
Self::Metadata(payload) => payload.target(),
Self::Data(payload) => payload.target(),
}
}
#[must_use]
pub const fn receiver(&self) -> &'static str {
match self {
Self::Metadata(payload) => payload.receiver(),
Self::Data(payload) => payload.receiver(),
}
}
#[must_use]
pub const fn method(&self) -> &'static str {
match self {
Self::Metadata(payload) => payload.method(),
Self::Data(payload) => payload.method(),
}
}
#[must_use]
pub fn arguments(&self) -> &[u8] {
match self {
Self::Metadata(payload) => payload.arguments(),
Self::Data(payload) => payload.arguments(),
}
}
#[must_use]
pub fn digest(&self) -> ArtifactChecksumRecord {
match self {
Self::Metadata(payload) => payload.digest(),
Self::Data(payload) => payload.digest(),
}
}
}
#[derive(Debug)]
pub struct IcSnapshotTransferReadRequest<'request, 'metadata> {
plan: &'request OperationPlanRecord,
payload: IcSnapshotTransferReadPayload<'request, 'metadata>,
authority: AttemptAuthorityRecord,
mutation_attempt: u32,
}
impl<'request, 'metadata> IcSnapshotTransferReadRequest<'request, 'metadata> {
pub fn new(
plan: &'request OperationPlanRecord,
operation_sequence: u64,
journal: &AttemptJournalRecord,
payload: IcSnapshotTransferReadPayload<'request, 'metadata>,
) -> Result<Self, IcSnapshotTransferReadError> {
let authority = plan.attempt_authority(operation_sequence)?;
if journal.authority() != &authority {
return Err(IcSnapshotTransferReadError::AuthorityMismatch);
}
if payload.target() != authority.binding().target()
|| payload.digest().hash() != authority.binding().request()
{
return Err(IcSnapshotTransferReadError::PayloadMismatch);
}
let mutation_attempt = journal
.view()
.pending_mutation
.ok_or(IcSnapshotTransferReadError::NoPendingMutation)?;
let request = Self {
plan,
payload,
authority,
mutation_attempt,
};
request.validate_journal(journal)?;
Ok(request)
}
#[must_use]
pub const fn plan(&self) -> &'request OperationPlanRecord {
self.plan
}
#[must_use]
pub const fn payload(&self) -> IcSnapshotTransferReadPayload<'request, 'metadata> {
self.payload
}
#[must_use]
pub const fn authority(&self) -> &AttemptAuthorityRecord {
&self.authority
}
#[must_use]
pub const fn mutation_attempt(&self) -> u32 {
self.mutation_attempt
}
pub fn validate_journal(
&self,
journal: &AttemptJournalRecord,
) -> Result<(), IcSnapshotTransferReadError> {
if journal.authority() != &self.authority {
return Err(IcSnapshotTransferReadError::AuthorityMismatch);
}
let view = journal.view();
if view.pending_mutation != Some(self.mutation_attempt) {
return Err(IcSnapshotTransferReadError::MutationMismatch);
}
if view.pending_observation.is_some() {
return Err(IcSnapshotTransferReadError::ObservationPending);
}
Ok(())
}
}
#[derive(Clone)]
pub struct IcSnapshotTransferReadResponseInput {
pub authority: ArtifactChecksumRecord,
pub mutation_attempt: u32,
pub context: PlanContextRecord,
pub target: String,
pub reply: Vec<u8>,
pub evidence: ArtifactChecksumRecord,
}
#[derive(Clone)]
pub struct IcSnapshotTransferReadResponse {
input: IcSnapshotTransferReadResponseInput,
}
impl IcSnapshotTransferReadResponse {
pub fn new(
mut input: IcSnapshotTransferReadResponseInput,
) -> Result<Self, IcSnapshotTransferReadError> {
if input.mutation_attempt == 0 || input.mutation_attempt > MAX_OPERATION_ATTEMPTS {
return Err(IcSnapshotTransferReadError::InvalidAttempt);
}
if input.reply.len() > MAX_IC_SNAPSHOT_DATA_REPLY_BYTES {
return Err(IcSnapshotTransferReadError::ReplyTooLarge);
}
input.target = crate::model::principal::canonical_text(&input.target)
.ok_or(IcSnapshotTransferReadError::InvalidTarget)?;
Ok(Self { input })
}
#[must_use]
pub const fn input(&self) -> &IcSnapshotTransferReadResponseInput {
&self.input
}
}
impl fmt::Debug for IcSnapshotTransferReadResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IcSnapshotTransferReadResponse")
.field("authority", &self.input.authority)
.field("mutation_attempt", &self.input.mutation_attempt)
.field("target", &self.input.target)
.field("reply_bytes", &self.input.reply.len())
.finish_non_exhaustive()
}
}
#[derive(Debug, Error)]
pub enum IcSnapshotTransferReadError {
#[error("snapshot transfer read authority differs")]
AuthorityMismatch,
#[error("snapshot transfer read payload differs")]
PayloadMismatch,
#[error("snapshot transfer read requires a pending original update")]
NoPendingMutation,
#[error("snapshot transfer read original attempt differs")]
MutationMismatch,
#[error("snapshot transfer read recovery observation is pending")]
ObservationPending,
#[error("invalid snapshot transfer read attempt")]
InvalidAttempt,
#[error("snapshot transfer read reply too large")]
ReplyTooLarge,
#[error("invalid snapshot transfer read target")]
InvalidTarget,
#[error(transparent)]
Plan(#[from] OperationPlanError),
}