canic_host/protocol_binding/release/
mod.rs1#[cfg(test)]
8mod tests;
9
10use crate::{
11 protocol_binding::{
12 ProtocolBindingError, RegistryProtocolBinding, ResolvedProtocolBinding,
13 resolve_protocol_binding,
14 },
15 registry::RegistryEntry,
16 release_build::{ReleaseBuildPlanError, validate_finalized_release_build_manifest},
17 release_set::{
18 CanicInfrastructureArtifactPersistenceError, CurrentReleaseSetManifestError,
19 load_persisted_canic_infrastructure_artifact_manifest,
20 load_persisted_current_release_set_manifest,
21 },
22};
23use canic_core::ids::ReleaseBuildId;
24use std::path::{Path, PathBuf};
25use thiserror::Error;
26
27#[derive(Debug, Error)]
29pub enum ReleaseProtocolBindingError {
30 #[error("Canister {canister} does not match its selected release infrastructure artifact")]
31 ArtifactBindingMismatch { canister: String },
32
33 #[error(transparent)]
34 Binding(#[from] ProtocolBindingError),
35
36 #[error(transparent)]
37 Infrastructure(#[from] CanicInfrastructureArtifactPersistenceError),
38
39 #[error("selected release infrastructure manifest digest does not match finalization")]
40 ManifestDigestMismatch,
41
42 #[error(transparent)]
43 ReleaseBuild(#[from] ReleaseBuildPlanError),
44
45 #[error(transparent)]
46 ReleaseSet(#[from] CurrentReleaseSetManifestError),
47
48 #[error("selected release Candid sidecar is not a regular contained file: {}", path.display())]
49 UnsafeCandid { path: PathBuf },
50}
51
52pub fn resolve_release_registry_protocol_binding(
54 root: &Path,
55 release_build_id: ReleaseBuildId,
56 entry: &RegistryEntry,
57) -> Result<ResolvedProtocolBinding, ReleaseProtocolBindingError> {
58 let release = load_persisted_current_release_set_manifest(root, release_build_id)?;
59 validate_finalized_release_build_manifest(root, release_build_id, &release.path)?;
60 let infrastructure =
61 load_persisted_canic_infrastructure_artifact_manifest(root, release_build_id)?;
62 if infrastructure.digest != release.manifest.infrastructure_artifact_manifest_sha256 {
63 return Err(ReleaseProtocolBindingError::ManifestDigestMismatch);
64 }
65 let mismatch = || ReleaseProtocolBindingError::ArtifactBindingMismatch {
66 canister: entry.pid.clone(),
67 };
68 let binding = entry.protocol_binding.as_ref().ok_or_else(mismatch)?;
69 let artifact = infrastructure
70 .manifest
71 .entries
72 .iter()
73 .find(|artifact| entry.role.as_deref() == Some(artifact.protocol_role.as_str()))
74 .ok_or_else(mismatch)?;
75 let expected = RegistryProtocolBinding {
76 release_identity: artifact.protocol_release_identity.clone(),
77 role: artifact.protocol_role.clone(),
78 capabilities: artifact.protocol_capabilities.clone(),
79 candid_sha256: artifact.candid_sha256,
80 protocol_profile_digest: artifact.protocol_profile_digest,
81 };
82 if binding != &expected || entry.module_hash.as_deref() != Some(&artifact.wasm_sha256_hex) {
83 return Err(mismatch());
84 }
85 let candid_path = root
86 .join(&artifact.wasm_relative_path)
87 .with_extension("did");
88 require_contained_sidecar(root, &candid_path)?;
89 Ok(resolve_protocol_binding(&entry.pid, expected, candid_path)?)
90}
91
92fn require_contained_sidecar(root: &Path, path: &Path) -> Result<(), ReleaseProtocolBindingError> {
93 let unsafe_path = || ReleaseProtocolBindingError::UnsafeCandid {
94 path: path.to_path_buf(),
95 };
96 let canonical_root = root.canonicalize().map_err(|_| unsafe_path())?;
97 let parent = path.parent().ok_or_else(unsafe_path)?;
98 let canonical_parent = parent.canonicalize().map_err(|_| unsafe_path())?;
99 if !canonical_parent.starts_with(canonical_root) {
100 return Err(unsafe_path());
101 }
102 Ok(())
103}