use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
ic_lifecycle_reply::MAX_IC_LIFECYCLE_REPLY_BYTES,
ic_request::{IcManagementRequestRecord, IcRequestError},
ic_snapshot_reply::MAX_IC_SNAPSHOT_REPLY_BYTES,
operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
};
use std::fmt;
use thiserror::Error;
pub const MAX_IC_MUTATION_REPLY_BYTES: usize =
if MAX_IC_SNAPSHOT_REPLY_BYTES < MAX_IC_LIFECYCLE_REPLY_BYTES {
MAX_IC_SNAPSHOT_REPLY_BYTES
} else {
MAX_IC_LIFECYCLE_REPLY_BYTES
};
#[derive(Debug)]
pub struct IcMutationRequest<'a> {
plan: &'a OperationPlanRecord,
payload: &'a IcManagementRequestRecord,
authority: AttemptAuthorityRecord,
mutation_attempt: u32,
}
impl<'a> IcMutationRequest<'a> {
pub fn new(
plan: &'a OperationPlanRecord,
operation_sequence: u64,
journal: &AttemptJournalRecord,
payload: &'a IcManagementRequestRecord,
) -> Result<Self, IcMutationRequestError> {
let authority = plan.attempt_authority(operation_sequence)?;
if journal.authority() != &authority {
return Err(IcMutationRequestError::AuthorityMismatch);
}
payload.validate_mutation_binding(authority.binding())?;
let mutation_attempt = journal
.view()
.pending_mutation
.ok_or(IcMutationRequestError::NoPendingMutation)?;
let request = Self {
plan,
payload,
authority,
mutation_attempt,
};
request.validate_journal(journal)?;
Ok(request)
}
#[must_use]
pub const fn plan(&self) -> &OperationPlanRecord {
self.plan
}
#[must_use]
pub const fn payload(&self) -> &'a IcManagementRequestRecord {
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<(), IcMutationRequestError> {
if journal.authority() != &self.authority {
return Err(IcMutationRequestError::AuthorityMismatch);
}
let current = journal.view();
if current.pending_mutation != Some(self.mutation_attempt) {
return Err(IcMutationRequestError::MutationMismatch);
}
if current.pending_observation.is_some() {
return Err(IcMutationRequestError::ObservationPending);
}
Ok(())
}
}
#[derive(Clone)]
pub struct IcMutationAcknowledgementInput {
pub authority: ArtifactChecksumRecord,
pub mutation_attempt: u32,
pub context: PlanContextRecord,
pub target: String,
pub reply: Vec<u8>,
pub evidence: ArtifactChecksumRecord,
}
#[derive(Clone)]
pub struct IcMutationAcknowledgement {
input: IcMutationAcknowledgementInput,
}
impl IcMutationAcknowledgement {
pub fn new(
mut input: IcMutationAcknowledgementInput,
) -> Result<Self, IcMutationAcknowledgementError> {
if input.mutation_attempt == 0 || input.mutation_attempt > MAX_OPERATION_ATTEMPTS {
return Err(IcMutationAcknowledgementError::InvalidAttempt);
}
if input.reply.len() > MAX_IC_MUTATION_REPLY_BYTES {
return Err(IcMutationAcknowledgementError::ReplyTooLarge);
}
input.target = crate::model::principal::canonical_text(&input.target)
.ok_or(IcMutationAcknowledgementError::InvalidTarget)?;
Ok(Self { input })
}
#[must_use]
pub const fn input(&self) -> &IcMutationAcknowledgementInput {
&self.input
}
}
impl fmt::Debug for IcMutationAcknowledgement {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("IcMutationAcknowledgement")
.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 IcMutationRequestError {
#[error("IC mutation original authority mismatch")]
AuthorityMismatch,
#[error("IC mutation requires a pending original mutation")]
NoPendingMutation,
#[error("IC mutation original attempt mismatch")]
MutationMismatch,
#[error("IC mutation has a pending recovery observation")]
ObservationPending,
#[error(transparent)]
Payload(#[from] IcRequestError),
#[error(transparent)]
Plan(#[from] OperationPlanError),
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum IcMutationAcknowledgementError {
#[error("invalid IC mutation acknowledgement attempt")]
InvalidAttempt,
#[error("IC mutation acknowledgement reply too large")]
ReplyTooLarge,
#[error("invalid IC mutation acknowledgement target")]
InvalidTarget,
}
#[cfg(test)]
mod tests;