use super::{
ComponentProvisioningPlanOpsError, MAX_FLEET_COMPONENT_PROVISIONING_PLAN_CANONICAL_BYTES,
MAX_FLEET_SUBNET_ROOT_PROVISIONING_BATCH_CANONICAL_BYTES,
};
use crate::{
cdk::types::Cycles,
config::{ComponentDeploymentLimits, ComponentDeploymentPurpose, FleetServiceMemberPurpose},
dto::{
component_provisioning::{
ComponentGroupPlacementPlan, ComponentGroupPlanEntry,
FleetComponentProvisioningOperation, FleetComponentProvisioningPlan,
FleetSubnetRootProvisioningBatch,
},
fleet_registry::FleetRegistryVersion,
},
ids::{ComponentGroupMemberPath, FleetRegistryAuthority, FleetSubnetRootLimits},
};
const PLAN_DOMAIN: &[u8] = b"canic/fleet-component-provisioning-plan/v1";
const ROOT_BATCH_DOMAIN: &[u8] = b"canic/fleet-subnet-root-provisioning-batch/v1";
const PLAN_SCHEMA_VERSION: u32 = 1;
pub(super) fn plan_bytes(
plan: &FleetComponentProvisioningPlan,
) -> Result<Vec<u8>, ComponentProvisioningPlanOpsError> {
let mut encoder = CanonicalEncoder::new();
encode_plan(&mut encoder, plan);
encoder.finish()
}
pub(super) fn root_batch_bytes(
batch: &FleetSubnetRootProvisioningBatch,
) -> Result<Vec<u8>, ComponentProvisioningPlanOpsError> {
let mut encoder = CanonicalEncoder::with_domain(ROOT_BATCH_DOMAIN);
encode_batch(&mut encoder, batch);
encoder.finish_with_bound(MAX_FLEET_SUBNET_ROOT_PROVISIONING_BATCH_CANONICAL_BYTES)
}
fn encode_plan(encoder: &mut CanonicalEncoder, plan: &FleetComponentProvisioningPlan) {
encode_fleet(encoder, &plan.fleet);
encode_registry_version(encoder, &plan.fleet_registry);
encoder.bytes(plan.configuration_digest.as_bytes());
encode_operation(encoder, &plan.operation);
encoder.u64(plan.directory_confirmation_roots.len() as u64);
for root in &plan.directory_confirmation_roots {
encoder.bytes(root.as_slice());
}
encoder.u64(plan.batches.len() as u64);
for batch in &plan.batches {
encode_batch(encoder, batch);
}
}
fn encode_fleet(encoder: &mut CanonicalEncoder, fleet: &crate::ids::FleetBinding) {
encoder.bytes(fleet.fleet.canonical_network_id.as_bytes());
encoder.bytes(fleet.fleet.fleet_id.as_bytes());
encoder.string(fleet.app.as_str());
}
fn encode_registry_version(encoder: &mut CanonicalEncoder, version: &FleetRegistryVersion) {
encode_authority(encoder, &version.authority);
encoder.u64(version.revision);
encoder.bytes(&version.content_hash);
}
fn encode_authority(encoder: &mut CanonicalEncoder, authority: &FleetRegistryAuthority) {
encode_fleet(encoder, &authority.binding.fleet);
encoder.bytes(
authority
.binding
.coordinator_subnet
.as_principal()
.as_slice(),
);
encoder.bytes(authority.binding.coordinator.as_slice());
encoder.u64(authority.epoch);
}
fn encode_operation(
encoder: &mut CanonicalEncoder,
operation: &FleetComponentProvisioningOperation,
) {
match operation {
FleetComponentProvisioningOperation::FreshInstall => encoder.u8(0),
FleetComponentProvisioningOperation::ScaleOut {
deployment,
previous_placements,
requested_placements,
} => {
encoder.u8(1);
encoder.string(deployment.as_str());
encoder.u32(*previous_placements);
encoder.u32(*requested_placements);
}
}
}
fn encode_batch(encoder: &mut CanonicalEncoder, batch: &FleetSubnetRootProvisioningBatch) {
encode_authority(encoder, &batch.root.authority);
encoder.bytes(batch.root.placement_subnet.as_principal().as_slice());
encoder.bytes(batch.root.fleet_subnet_root.as_slice());
encoder.u64(batch.root.component_admissions.len() as u64);
for admission in &batch.root.component_admissions {
encoder.string(admission.component_spec.as_str());
encoder.bytes(&admission.spec_hash);
encoder.u32(admission.maximum_root_instances);
}
encoder.bytes(batch.root.component_topology_digest.as_bytes());
encode_root_limits(encoder, &batch.root.limits);
encoder.bytes(batch.active_release_set.release_build_id.as_bytes());
encoder.bytes(batch.active_release_set.manifest_digest.as_bytes());
encoder.u64(batch.placements.len() as u64);
for placement in &batch.placements {
encode_placement(encoder, placement);
}
}
fn encode_root_limits(encoder: &mut CanonicalEncoder, limits: &FleetSubnetRootLimits) {
encoder.u32(limits.maximum_component_instances);
encoder.u64(limits.maximum_registry_bytes);
encoder.u64(limits.maximum_wasm_store_bytes);
encoder.u32(limits.canister_pool.minimum_size);
encoder.u32(limits.canister_pool.maximum_size);
encode_cycles(encoder, &limits.canister_pool.canister_cycles);
encoder.u64(limits.cycles_funding.window_secs);
encode_cycles(encoder, &limits.cycles_funding.maximum_cycles);
encoder.u32(limits.maximum_group_placements);
}
fn encode_cycles(encoder: &mut CanonicalEncoder, cycles: &Cycles) {
encoder.u128(cycles.to_u128());
}
fn encode_placement(encoder: &mut CanonicalEncoder, placement: &ComponentGroupPlacementPlan) {
encoder.string(placement.group_placement.deployment.as_str());
encoder.u32(placement.group_placement.ordinal);
encoder.string(placement.component_group.as_str());
encoder.u64(placement.entries.len() as u64);
for entry in &placement.entries {
encode_entry(encoder, entry);
}
}
fn encode_entry(encoder: &mut CanonicalEncoder, entry: &ComponentGroupPlanEntry) {
encode_member_path(encoder, &entry.member_path);
encoder.string(entry.component_spec.as_str());
encoder.bytes(&entry.spec_hash);
encode_purpose(encoder, &entry.purpose);
encoder.u64(entry.labels.len() as u64);
for label in &entry.labels {
encoder.string(label.key.as_str());
encoder.string(label.value.as_str());
}
encode_limits(encoder, &entry.limits);
}
fn encode_member_path(encoder: &mut CanonicalEncoder, path: &ComponentGroupMemberPath) {
encoder.u64(path.len() as u64);
for member in path.as_slice() {
encoder.string(member.as_str());
}
}
fn encode_purpose(encoder: &mut CanonicalEncoder, purpose: &ComponentDeploymentPurpose) {
match purpose {
ComponentDeploymentPurpose::Ordinary => encoder.u8(0),
ComponentDeploymentPurpose::FleetServiceMember {
service,
member_purpose,
} => {
encoder.u8(1);
encoder.string(service.as_str());
encoder.u8(match member_purpose {
FleetServiceMemberPurpose::Authority => 0,
FleetServiceMemberPurpose::Replica => 1,
FleetServiceMemberPurpose::PoolMember => 2,
});
}
}
}
fn encode_limits(encoder: &mut CanonicalEncoder, limits: &ComponentDeploymentLimits) {
encoder.u32(limits.maximum_descendants);
encoder.u64(limits.maximum_registry_bytes);
encoder.u64(limits.spawn_grant_reductions.len() as u64);
for grant in &limits.spawn_grant_reductions {
encoder.string(grant.parent_role.as_str());
encoder.string(grant.child_role.as_str());
encoder.u32(grant.maximum_instances_per_parent);
}
}
struct CanonicalEncoder {
bytes: Vec<u8>,
}
impl CanonicalEncoder {
fn new() -> Self {
Self::with_domain(PLAN_DOMAIN)
}
fn with_domain(domain: &[u8]) -> Self {
let mut encoder = Self { bytes: Vec::new() };
encoder.bytes(domain);
encoder.u32(PLAN_SCHEMA_VERSION);
encoder
}
fn bytes(&mut self, value: &[u8]) {
self.u64(value.len() as u64);
self.bytes.extend_from_slice(value);
}
fn string(&mut self, value: &str) {
self.bytes(value.as_bytes());
}
fn u8(&mut self, value: u8) {
self.bytes.push(value);
}
fn u32(&mut self, value: u32) {
self.bytes.extend_from_slice(&value.to_be_bytes());
}
fn u64(&mut self, value: u64) {
self.bytes.extend_from_slice(&value.to_be_bytes());
}
fn u128(&mut self, value: u128) {
self.bytes.extend_from_slice(&value.to_be_bytes());
}
fn finish(self) -> Result<Vec<u8>, ComponentProvisioningPlanOpsError> {
self.finish_with_bound(MAX_FLEET_COMPONENT_PROVISIONING_PLAN_CANONICAL_BYTES)
}
fn finish_with_bound(
self,
maximum_bytes: usize,
) -> Result<Vec<u8>, ComponentProvisioningPlanOpsError> {
if self.bytes.len() > maximum_bytes {
return Err(ComponentProvisioningPlanOpsError::CanonicalBytesExceeded {
actual_bytes: self.bytes.len(),
maximum_bytes,
});
}
Ok(self.bytes)
}
}