use super::AttemptJournalRecordError;
use crate::model::artifacts::ArtifactChecksumRecord;
use serde::{Deserialize, Serialize};
pub const MAX_OPERATION_ATTEMPTS: u32 = 1024;
#[derive(Clone, Debug)]
pub struct OperationBindingRequest {
pub intent: String,
pub operation_sequence: u64,
pub network: String,
pub caller: String,
pub target: String,
pub release: String,
pub request: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(try_from = "BindingFields")]
pub struct OperationBindingRecord {
intent: String,
operation_sequence: u64,
network: String,
caller: String,
target: String,
release: String,
request: String,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct BindingFields {
intent: String,
operation_sequence: u64,
network: String,
caller: String,
target: String,
release: String,
request: String,
}
impl TryFrom<BindingFields> for OperationBindingRecord {
type Error = AttemptJournalRecordError;
fn try_from(fields: BindingFields) -> Result<Self, Self::Error> {
Self::new(&OperationBindingRequest {
intent: fields.intent,
operation_sequence: fields.operation_sequence,
network: fields.network,
caller: fields.caller,
target: fields.target,
release: fields.release,
request: fields.request,
})
}
}
impl OperationBindingRecord {
pub fn new(request: &OperationBindingRequest) -> Result<Self, AttemptJournalRecordError> {
Ok(Self {
intent: super::canonical_hash(&request.intent)?,
operation_sequence: request.operation_sequence,
network: super::canonical_hash(&request.network)?,
caller: crate::model::principal::canonical_text(&request.caller)
.ok_or(AttemptJournalRecordError::InvalidPrincipal)?,
target: crate::model::principal::canonical_text(&request.target)
.ok_or(AttemptJournalRecordError::InvalidPrincipal)?,
release: super::canonical_hash(&request.release)?,
request: super::canonical_hash(&request.request)?,
})
}
#[must_use]
pub fn intent(&self) -> &str {
&self.intent
}
#[must_use]
pub const fn operation_sequence(&self) -> u64 {
self.operation_sequence
}
#[must_use]
pub fn network(&self) -> &str {
&self.network
}
#[must_use]
pub fn caller(&self) -> &str {
&self.caller
}
#[must_use]
pub fn target(&self) -> &str {
&self.target
}
#[must_use]
pub fn release(&self) -> &str {
&self.release
}
#[must_use]
pub fn request(&self) -> &str {
&self.request
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(try_from = "BudgetFields")]
pub struct AttemptBudgetRecord {
mutations: u32,
observations: u32,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct BudgetFields {
mutations: u32,
observations: u32,
}
impl TryFrom<BudgetFields> for AttemptBudgetRecord {
type Error = AttemptJournalRecordError;
fn try_from(fields: BudgetFields) -> Result<Self, Self::Error> {
Self::new(fields.mutations, fields.observations)
}
}
impl AttemptBudgetRecord {
pub fn new(mutations: u32, observations: u32) -> Result<Self, AttemptJournalRecordError> {
if mutations
.checked_add(observations)
.is_none_or(|total| total > MAX_OPERATION_ATTEMPTS)
{
return Err(AttemptJournalRecordError::BudgetTooLarge);
}
Ok(Self {
mutations,
observations,
})
}
#[must_use]
pub const fn mutations(&self) -> u32 {
self.mutations
}
#[must_use]
pub const fn observations(&self) -> u32 {
self.observations
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AttemptAuthorityRecord {
binding: OperationBindingRecord,
budget: AttemptBudgetRecord,
}
impl AttemptAuthorityRecord {
#[must_use]
pub const fn new(binding: OperationBindingRecord, budget: AttemptBudgetRecord) -> Self {
Self { binding, budget }
}
#[must_use]
pub const fn binding(&self) -> &OperationBindingRecord {
&self.binding
}
#[must_use]
pub const fn budget(&self) -> &AttemptBudgetRecord {
&self.budget
}
#[must_use]
pub fn digest(&self) -> ArtifactChecksumRecord {
let mut bytes = b"ic-backup/attempt-authority/v1\0".to_vec();
for digest in [
&self.binding.intent,
&self.binding.network,
&self.binding.release,
&self.binding.request,
] {
bytes.extend_from_slice(digest.as_bytes());
}
bytes.extend_from_slice(&self.binding.operation_sequence.to_be_bytes());
for principal in [&self.binding.caller, &self.binding.target] {
bytes.push(principal.len().to_le_bytes()[0]);
bytes.extend_from_slice(principal.as_bytes());
}
bytes.extend_from_slice(&self.budget.mutations.to_be_bytes());
bytes.extend_from_slice(&self.budget.observations.to_be_bytes());
ArtifactChecksumRecord::from_bytes(&bytes)
}
}