mod wire;
use super::{
artifacts::ArtifactChecksumRecord,
control_authority::{ControlObservationError, ControllerSet},
ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
};
use ic_management_canister_types::CanisterStatusType;
use thiserror::Error;
pub const MAX_IC_LIFECYCLE_REPLY_BYTES: usize = 1024 * 1024;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcCanisterStatusInfo {
status: CanisterStatusType,
controllers: ControllerSet,
}
impl IcCanisterStatusInfo {
#[must_use]
pub const fn status(&self) -> CanisterStatusType {
self.status
}
#[must_use]
pub const fn controllers(&self) -> &ControllerSet {
&self.controllers
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum IcLifecycleReplyKind {
Status(IcCanisterStatusInfo),
Acknowledgement,
}
#[derive(Debug)]
pub struct IcLifecycleReply<'request> {
request: &'request IcManagementRequestRecord,
kind: IcLifecycleReplyKind,
payload_checksum: ArtifactChecksumRecord,
}
impl<'request> IcLifecycleReply<'request> {
pub fn decode(
request: &'request IcManagementRequestRecord,
bytes: &[u8],
) -> Result<Self, IcLifecycleReplyError> {
let method = request.method();
if !matches!(
method,
IcManagementMethodRecord::CanisterStatus
| IcManagementMethodRecord::StopCanister
| IcManagementMethodRecord::StartCanister
| IcManagementMethodRecord::LoadCanisterSnapshot
) {
return Err(IcLifecycleReplyError::UnsupportedMethod { method });
}
if bytes.len() > MAX_IC_LIFECYCLE_REPLY_BYTES {
return Err(IcLifecycleReplyError::ReplyTooLarge);
}
let kind = if method == IcManagementMethodRecord::CanisterStatus {
IcLifecycleReplyKind::Status(wire::status(bytes)?)
} else {
if bytes != b"DIDL\0\0" {
return Err(IcLifecycleReplyError::InvalidReply);
}
IcLifecycleReplyKind::Acknowledgement
};
Ok(Self {
request,
kind,
payload_checksum: ArtifactChecksumRecord::from_bytes(bytes),
})
}
#[must_use]
pub const fn request(&self) -> &'request IcManagementRequestRecord {
self.request
}
#[must_use]
pub const fn kind(&self) -> &IcLifecycleReplyKind {
&self.kind
}
#[must_use]
pub const fn payload_checksum(&self) -> &ArtifactChecksumRecord {
&self.payload_checksum
}
#[must_use]
pub fn digest(&self) -> ArtifactChecksumRecord {
let mut bytes = b"ic-backup/ic-lifecycle-reply/v1\0".to_vec();
bytes.extend_from_slice(self.request.digest().hash().as_bytes());
bytes.extend_from_slice(self.payload_checksum.hash().as_bytes());
ArtifactChecksumRecord::from_bytes(&bytes)
}
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum IcLifecycleReplyError {
#[error("lifecycle reply codec does not support {method:?}")]
UnsupportedMethod {
method: IcManagementMethodRecord,
},
#[error("lifecycle reply exceeds {MAX_IC_LIFECYCLE_REPLY_BYTES} bytes")]
ReplyTooLarge,
#[error("invalid or unbounded lifecycle Candid reply")]
InvalidReply,
#[error(transparent)]
Controllers(#[from] ControlObservationError),
}
#[cfg(test)]
mod tests;