use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
ic_lifecycle_reply::{IcLifecycleReply, IcLifecycleReplyError},
ic_observation::{
IcObservationRequest, IcObservationRequestError, IcObservationResponse,
IcObservationResponseInput,
},
ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
ic_snapshot_reply::{IcSnapshotReply, IcSnapshotReplyError},
operation_plan::PlanContextRecord,
};
use thiserror::Error;
#[derive(Debug)]
pub enum IcObservationReplyView<'a> {
Inventory(IcSnapshotReply<'a>),
Status(IcLifecycleReply<'a>),
}
#[derive(Debug)]
pub struct IcObservationResponseView<'a> {
response: &'a IcObservationResponse,
reply: IcObservationReplyView<'a>,
}
impl<'a> IcObservationResponseView<'a> {
#[must_use]
pub const fn response(&self) -> &'a IcObservationResponse {
self.response
}
#[must_use]
pub const fn reply(&self) -> &IcObservationReplyView<'a> {
&self.reply
}
}
pub fn validate_response<'a>(
request: &IcObservationRequest<'a>,
journal: &AttemptJournalRecord,
response: &'a IcObservationResponse,
) -> Result<IcObservationResponseView<'a>, IcObservationAssociationError> {
request.validate_journal(journal)?;
validate_association(
&ObservationAssociation {
authority: request.authority(),
mutation_attempt: request.mutation_attempt(),
observation_attempt: request.observation_attempt(),
payload: request.payload(),
context: request.plan().context(),
},
response,
)
}
pub(crate) struct ObservationAssociation<'binding, 'payload> {
pub authority: &'binding AttemptAuthorityRecord,
pub mutation_attempt: u32,
pub observation_attempt: u32,
pub payload: &'payload IcManagementRequestRecord,
pub context: &'binding PlanContextRecord,
}
pub(crate) fn validate_association<'a>(
binding: &ObservationAssociation<'_, 'a>,
response: &'a IcObservationResponse,
) -> Result<IcObservationResponseView<'a>, IcObservationAssociationError> {
validate_claims(
&ObservationClaims {
authority: binding.authority,
mutation_attempt: binding.mutation_attempt,
observation_attempt: binding.observation_attempt,
request: binding.payload.digest(),
context: binding.context,
target: binding.payload.target(),
},
response.input(),
)?;
let reply = match binding.payload.method() {
IcManagementMethodRecord::ListCanisterSnapshots => IcObservationReplyView::Inventory(
IcSnapshotReply::decode(binding.payload, &response.input().reply)?,
),
_ => IcObservationReplyView::Status(IcLifecycleReply::decode(
binding.payload,
&response.input().reply,
)?),
};
Ok(IcObservationResponseView { response, reply })
}
pub(crate) struct ObservationClaims<'a> {
pub authority: &'a AttemptAuthorityRecord,
pub mutation_attempt: u32,
pub observation_attempt: u32,
pub request: ArtifactChecksumRecord,
pub context: &'a PlanContextRecord,
pub target: &'a str,
}
pub(crate) fn validate_claims(
binding: &ObservationClaims<'_>,
input: &IcObservationResponseInput,
) -> Result<(), IcObservationAssociationError> {
if input.authority != binding.authority.digest() {
return Err(IcObservationAssociationError::AuthorityMismatch);
}
if input.mutation_attempt != binding.mutation_attempt
|| input.observation_attempt != binding.observation_attempt
{
return Err(IcObservationAssociationError::AttemptMismatch);
}
if input.request != binding.request {
return Err(IcObservationAssociationError::RequestMismatch);
}
if &input.context != binding.context {
return Err(IcObservationAssociationError::ContextMismatch);
}
if input.target != binding.target {
return Err(IcObservationAssociationError::TargetMismatch);
}
Ok(())
}
#[derive(Debug, Error)]
pub enum IcObservationAssociationError {
#[error(transparent)]
Reservation(#[from] IcObservationRequestError),
#[error("IC observation response authority mismatch")]
AuthorityMismatch,
#[error("IC observation response attempt mismatch")]
AttemptMismatch,
#[error("IC observation response request mismatch")]
RequestMismatch,
#[error("IC observation response context mismatch")]
ContextMismatch,
#[error("IC observation response target mismatch")]
TargetMismatch,
#[error(transparent)]
Inventory(#[from] IcSnapshotReplyError),
#[error(transparent)]
Status(#[from] IcLifecycleReplyError),
}
mod capture_settlement;
pub use capture_settlement::{
IcCaptureSettlementError, IcCaptureSettlementView, validate_capture_settlement,
};
mod settlement;
pub use settlement::{
IcLifecycleSettlementError, IcLifecycleSettlementView, validate_lifecycle_settlement,
};
#[cfg(test)]
mod tests;