use crate::model::ic_update_response::IcUpdateResponseInput;
use crate::model::{
attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
ic_lifecycle_reply::MAX_IC_LIFECYCLE_REPLY_BYTES,
ic_request::{IcManagementRequestRecord, IcRequestError},
ic_snapshot_reply::MAX_IC_SNAPSHOT_REPLY_BYTES,
operation_plan::{OperationPlanError, OperationPlanRecord},
};
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 IcMutationAcknowledgement {
input: IcUpdateResponseInput,
}
impl IcMutationAcknowledgement {
pub fn new(mut input: IcUpdateResponseInput) -> Result<Self, IcMutationAcknowledgementError> {
input.admit(
MAX_IC_MUTATION_REPLY_BYTES,
IcMutationAcknowledgementError::InvalidAttempt,
IcMutationAcknowledgementError::ReplyTooLarge,
IcMutationAcknowledgementError::InvalidTarget,
)?;
Ok(Self { input })
}
#[must_use]
pub const fn input(&self) -> &IcUpdateResponseInput {
&self.input
}
}
impl fmt::Debug for IcMutationAcknowledgement {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.input
.fmt_redacted(formatter, "IcMutationAcknowledgement")
}
}
#[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;