use candid::Principal;
use canic_contracts::ids::{
CallerComponentInstallation, CallerInstallation, CallerReceiverAuthority, ComponentInstanceId,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum CallerAdmissionError {
#[error("managed caller authority is unavailable")]
AuthorityUnavailable,
#[error("managed caller authority is inconsistent with this installation")]
AuthorityConflict,
#[error("managed caller authority is fenced")]
Fenced,
#[error("managed caller permission is not granted")]
PermissionDenied,
#[error("managed caller admission ticket no longer covers the current authority")]
TicketExpired,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum CallerChangeRecord {
StageSource(CallerInstallation),
Grant(CallerInstallation),
DenySource(CallerInstallation),
DenyComponent(CallerComponentInstallation),
OpenReceiver,
RetireReceiver,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CallerPublicationRecord {
pub operation_id: [u8; 32],
pub authority: CallerReceiverAuthority,
pub previous_generation: u64,
pub generation: u64,
pub change: CallerChangeRecord,
pub content_hash: [u8; 32],
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum CallerReceiptPhase {
Prepared,
Committed,
Complete,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CallerReceiptRecord {
pub publication: CallerPublicationRecord,
pub phase: CallerReceiptPhase,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CallerReceiverRecord {
pub authority: CallerReceiverAuthority,
pub generation: u64,
pub open: bool,
pub retired: bool,
#[serde(deserialize_with = "crate::cdk::serialize::required_option")]
pub pending_operation: Option<[u8; 32]>,
#[serde(deserialize_with = "crate::cdk::serialize::required_option")]
pub cleanup_cursor: Option<CallerRowKey>,
pub entries: u32,
pub reserved_bytes: u32,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CallerSourceRecord {
pub installation: CallerInstallation,
pub generation: u64,
pub open: bool,
pub denied: bool,
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum CallerPublicationError {
#[error("caller publication authority does not match this installation")]
AuthorityConflict,
#[error("caller publication capacity cannot cover the complete reservation")]
Capacity,
#[error("another caller publication remains unfinished")]
InProgress,
#[error("caller publication identity or digest is invalid")]
InvalidPublication,
#[error("caller publication generation does not follow the retained authority")]
GenerationConflict,
#[error("caller publication receipt does not cover the requested phase")]
PhaseConflict,
#[error("caller publication replay differs from its original operation")]
ReplayConflict,
#[error("retired receiver or source cannot be reopened")]
Retired,
}
impl CallerReceiverRecord {
#[must_use]
pub const fn new(authority: CallerReceiverAuthority) -> Self {
Self {
authority,
generation: 0,
open: false,
retired: false,
pending_operation: None,
cleanup_cursor: None,
entries: 0,
reserved_bytes: crate::config::caller_authority::CALLER_HEADER_BYTES,
}
}
pub fn reserve(
&self,
publication: &CallerPublicationRecord,
reservation: CallerReservation,
) -> Result<Self, CallerPublicationError> {
self.validate_publication(publication)?;
if self.pending_operation.is_some() {
return Err(CallerPublicationError::InProgress);
}
let entries = self
.entries
.checked_add(reservation.entries)
.filter(|entries| *entries <= reservation.maximum_entries)
.ok_or(CallerPublicationError::Capacity)?;
let reserved_bytes = self
.reserved_bytes
.checked_add(reservation.bytes)
.filter(|bytes| *bytes <= reservation.maximum_bytes)
.ok_or(CallerPublicationError::Capacity)?;
let mut successor = self.clone();
successor.entries = entries;
successor.reserved_bytes = reserved_bytes;
successor.pending_operation = Some(publication.operation_id);
successor.cleanup_cursor = None;
if matches!(publication.change, CallerChangeRecord::RetireReceiver) {
successor.open = false;
}
Ok(successor)
}
pub fn finish(&self, receipt: &CallerReceiptRecord) -> Result<Self, CallerPublicationError> {
if receipt.phase != CallerReceiptPhase::Committed {
return Err(CallerPublicationError::PhaseConflict);
}
self.validate_publication(&receipt.publication)?;
if self.pending_operation != Some(receipt.publication.operation_id) {
return Err(CallerPublicationError::ReplayConflict);
}
let mut successor = self.clone();
successor.generation = receipt.publication.generation;
successor.pending_operation = None;
successor.cleanup_cursor = None;
match receipt.publication.change {
CallerChangeRecord::OpenReceiver => successor.open = true,
CallerChangeRecord::RetireReceiver => {
successor.open = false;
successor.retired = true;
}
CallerChangeRecord::StageSource(_)
| CallerChangeRecord::Grant(_)
| CallerChangeRecord::DenySource(_)
| CallerChangeRecord::DenyComponent(_) => {}
}
Ok(successor)
}
fn validate_publication(
&self,
publication: &CallerPublicationRecord,
) -> Result<(), CallerPublicationError> {
if self.retired {
return Err(CallerPublicationError::Retired);
}
if self.authority != publication.authority {
return Err(CallerPublicationError::AuthorityConflict);
}
if publication.operation_id == [0; 32] || publication.content_hash == [0; 32] {
return Err(CallerPublicationError::InvalidPublication);
}
if publication.previous_generation != self.generation
|| self.generation.checked_add(1) != Some(publication.generation)
{
return Err(CallerPublicationError::GenerationConflict);
}
Ok(())
}
}
pub struct CallerReservation {
pub entries: u32,
pub bytes: u32,
pub maximum_entries: u32,
pub maximum_bytes: u32,
}
impl CallerReceiptRecord {
pub fn advance(
&self,
publication: &CallerPublicationRecord,
requested: CallerReceiptPhase,
) -> Result<Self, CallerPublicationError> {
if self.publication != *publication {
return Err(CallerPublicationError::ReplayConflict);
}
let permitted = !matches!(
(self.phase, requested),
(CallerReceiptPhase::Prepared, CallerReceiptPhase::Complete)
);
if !permitted {
return Err(CallerPublicationError::PhaseConflict);
}
let phase = match (self.phase, requested) {
(CallerReceiptPhase::Complete, _) => CallerReceiptPhase::Complete,
(CallerReceiptPhase::Committed, CallerReceiptPhase::Prepared) => {
CallerReceiptPhase::Committed
}
(_, requested) => requested,
};
Ok(Self {
publication: self.publication.clone(),
phase,
})
}
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct CallerRowKey(Vec<u8>);
impl CallerRowKey {
pub(crate) fn source(caller: Principal) -> Self {
Self::tagged(0, caller.as_slice())
}
pub(crate) fn component(component: ComponentInstanceId, installation: [u8; 32]) -> Self {
let mut key = Self::tagged(1, component.as_bytes());
key.0.extend_from_slice(&installation);
key
}
pub(crate) fn receipt(operation: [u8; 32]) -> Self {
Self::tagged(2, &operation)
}
fn tagged(tag: u8, identity: &[u8]) -> Self {
let mut key = Vec::with_capacity(identity.len() + 1);
key.push(tag);
key.extend_from_slice(identity);
Self(key)
}
}