#[cfg(test)]
mod tests;
use crate::{
protocol_binding::{
ProtocolBindingError, RegistryProtocolBinding, ResolvedProtocolBinding,
resolve_protocol_binding,
},
registry::RegistryEntry,
release_build::{ReleaseBuildPlanError, validate_finalized_release_build_manifest},
release_set::{
CanicInfrastructureArtifactPersistenceError, CurrentReleaseSetManifestError,
load_persisted_canic_infrastructure_artifact_manifest,
load_persisted_current_release_set_manifest,
},
};
use canic_core::ids::ReleaseBuildId;
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReleaseProtocolBindingError {
#[error("Canister {canister} does not match its selected release infrastructure artifact")]
ArtifactBindingMismatch { canister: String },
#[error(transparent)]
Binding(#[from] ProtocolBindingError),
#[error(transparent)]
Infrastructure(#[from] CanicInfrastructureArtifactPersistenceError),
#[error("selected release infrastructure manifest digest does not match finalization")]
ManifestDigestMismatch,
#[error(transparent)]
ReleaseBuild(#[from] ReleaseBuildPlanError),
#[error(transparent)]
ReleaseSet(#[from] CurrentReleaseSetManifestError),
#[error("selected release Candid sidecar is not a regular contained file: {}", path.display())]
UnsafeCandid { path: PathBuf },
}
pub fn resolve_release_registry_protocol_binding(
root: &Path,
release_build_id: ReleaseBuildId,
entry: &RegistryEntry,
) -> Result<ResolvedProtocolBinding, ReleaseProtocolBindingError> {
let release = load_persisted_current_release_set_manifest(root, release_build_id)?;
validate_finalized_release_build_manifest(root, release_build_id, &release.path)?;
let infrastructure =
load_persisted_canic_infrastructure_artifact_manifest(root, release_build_id)?;
if infrastructure.digest != release.manifest.infrastructure_artifact_manifest_sha256 {
return Err(ReleaseProtocolBindingError::ManifestDigestMismatch);
}
let mismatch = || ReleaseProtocolBindingError::ArtifactBindingMismatch {
canister: entry.pid.clone(),
};
let artifact = infrastructure
.manifest
.entries
.iter()
.find(|artifact| entry.role.as_deref() == Some(artifact.protocol_role.as_str()))
.ok_or_else(mismatch)?;
resolve_infrastructure_registry_protocol_binding(root, artifact, entry)
}
pub fn resolve_infrastructure_registry_protocol_binding(
root: &Path,
artifact: &crate::release_set::CanicInfrastructureArtifactEntry,
entry: &RegistryEntry,
) -> Result<ResolvedProtocolBinding, ReleaseProtocolBindingError> {
let mismatch = || ReleaseProtocolBindingError::ArtifactBindingMismatch {
canister: entry.pid.clone(),
};
let binding = entry.protocol_binding.as_ref().ok_or_else(mismatch)?;
let expected = RegistryProtocolBinding {
release_identity: artifact.protocol_release_identity.clone(),
role: artifact.protocol_role.clone(),
capabilities: artifact.protocol_capabilities.clone(),
candid_sha256: artifact.candid_sha256,
protocol_profile_digest: artifact.protocol_profile_digest,
};
if binding != &expected
|| entry.role.as_deref() != Some(artifact.protocol_role.as_str())
|| entry.module_hash.as_deref() != Some(&artifact.wasm_sha256_hex)
{
return Err(mismatch());
}
let candid_path = root
.join(&artifact.wasm_relative_path)
.with_extension("did");
require_contained_sidecar(root, &candid_path)?;
Ok(resolve_protocol_binding(&entry.pid, expected, candid_path)?)
}
pub fn require_contained_sidecar(
root: &Path,
path: &Path,
) -> Result<(), ReleaseProtocolBindingError> {
let unsafe_path = || ReleaseProtocolBindingError::UnsafeCandid {
path: path.to_path_buf(),
};
let canonical_root = root.canonicalize().map_err(|_| unsafe_path())?;
let parent = path.parent().ok_or_else(unsafe_path)?;
let canonical_parent = parent.canonicalize().map_err(|_| unsafe_path())?;
if !canonical_parent.starts_with(canonical_root) {
return Err(unsafe_path());
}
Ok(())
}