canic_host/canic_metadata/
mod.rs1#[cfg(test)]
8mod tests;
9
10use crate::{
11 icp::{IcpCli, IcpCommandError, IcpJsonResponseError, decode_json_result_response},
12 protocol_binding::ResolvedProtocolBinding,
13};
14use canic_contracts::dto::{
15 role::{RoleCapability, RoleOverviewResponse},
16 wire::projection::overview::RoleStatusResponse,
17};
18use canic_core::role_contract::RoleCapabilityKey;
19use thiserror::Error as ThisError;
20
21const ICP_JSON_OUTPUT: &str = "json";
22
23#[derive(Debug, ThisError)]
28pub enum CanicMetadataQueryError {
29 #[error(transparent)]
30 Icp(#[from] IcpCommandError),
31
32 #[error(transparent)]
33 Response(#[from] IcpJsonResponseError),
34
35 #[error("role Overview conflicts with the exact preselected protocol binding: {0}")]
36 Binding(String),
37}
38
39pub fn query_canic_metadata_version(
41 icp: &IcpCli,
42 canister_id: &str,
43 binding: &ResolvedProtocolBinding,
44) -> Result<String, CanicMetadataQueryError> {
45 let output = icp.canister_query_arg_output_with_candid(
46 canister_id,
47 canic_contracts::protocol::CANIC_PUBLIC_STATUS,
48 "(variant { Overview })",
49 Some(ICP_JSON_OUTPUT),
50 Some(binding.candid_path.as_path()),
51 )?;
52 let overview = parse_canic_metadata_response(&output)?;
53 verify_overview_binding(&overview, binding)?;
54 Ok(overview.metadata.canic_version)
55}
56
57#[cfg(test)]
58fn parse_canic_metadata_version_response(output: &str) -> Result<String, IcpJsonResponseError> {
59 Ok(parse_canic_metadata_response(output)?
60 .metadata
61 .canic_version)
62}
63
64fn parse_canic_metadata_response(
65 output: &str,
66) -> Result<RoleOverviewResponse, IcpJsonResponseError> {
67 let response = decode_json_result_response::<RoleStatusResponse>(output)?;
68 let RoleStatusResponse::Overview(overview) = response;
69 Ok(overview)
70}
71
72fn verify_overview_binding(
73 overview: &RoleOverviewResponse,
74 selected: &ResolvedProtocolBinding,
75) -> Result<(), CanicMetadataQueryError> {
76 let binding = &selected.binding;
77 if overview.role != binding.role {
78 return Err(CanicMetadataQueryError::Binding(
79 "role mismatch".to_string(),
80 ));
81 }
82 if overview.metadata.canic_version != binding.release_identity {
83 return Err(CanicMetadataQueryError::Binding(
84 "release identity mismatch".to_string(),
85 ));
86 }
87 let capabilities = overview
88 .capabilities
89 .iter()
90 .copied()
91 .map(role_capability_name)
92 .collect::<Vec<_>>();
93 let expected = binding
94 .capabilities
95 .iter()
96 .filter(|capability| **capability != RoleCapabilityKey::LocalApplicationAuthorization)
97 .map(|capability| capability.manifest_name())
98 .collect::<Vec<_>>();
99 if capabilities != expected {
100 return Err(CanicMetadataQueryError::Binding(
101 "ordered capability mismatch".to_string(),
102 ));
103 }
104 if overview.protocol_profile_digest != binding.protocol_profile_digest.into_bytes() {
105 return Err(CanicMetadataQueryError::Binding(
106 "profile digest mismatch".to_string(),
107 ));
108 }
109 Ok(())
110}
111
112const fn role_capability_name(capability: RoleCapability) -> &'static str {
113 match capability {
114 RoleCapability::AutomaticTopup => "AutomaticTopup",
115 RoleCapability::ChildProvisioning => "ChildProvisioning",
116 RoleCapability::CallerAuthority => "CallerAuthority",
117 RoleCapability::DelegatedTokenIssuer => "DelegatedTokenIssuer",
118 RoleCapability::DelegatedTokenVerifier => "DelegatedTokenVerifier",
119 RoleCapability::FleetAdmissionProjection => "FleetAdmissionProjection",
120 RoleCapability::FleetCoordinator => "FleetCoordinator",
121 RoleCapability::Icrc21 => "Icrc21",
122 RoleCapability::Index => "Index",
123 RoleCapability::ObservabilityDiagnostics => "ObservabilityDiagnostics",
124 RoleCapability::ObservabilityHistory => "ObservabilityHistory",
125 RoleCapability::ObservabilityLogs => "ObservabilityLogs",
126 RoleCapability::ObservabilityMetrics => "ObservabilityMetrics",
127 RoleCapability::RoleAttestationSigner => "RoleAttestationSigner",
128 RoleCapability::RoleAttestationVerifier => "RoleAttestationVerifier",
129 RoleCapability::Root => "Root",
130 RoleCapability::RootControlPlane => "RootControlPlane",
131 RoleCapability::RootDelegation => "RootDelegation",
132 RoleCapability::Runtime => "Runtime",
133 RoleCapability::Scaling => "Scaling",
134 RoleCapability::Sharding => "Sharding",
135 RoleCapability::WasmStore => "WasmStore",
136 }
137}