canic_core/ops/runtime/
env.rs1use crate::{
2 Error, ThisError,
3 cdk::{api::canister_self, types::Principal},
4 dto::{env::EnvView, subnet::SubnetIdentity},
5 ids::{CanisterRole, SubnetRole},
6 infra::ic::{Network, build_network},
7 model::memory::Env,
8 ops::{
9 adapter::env::{env_data_from_view, env_data_to_view},
10 runtime::RuntimeOpsError,
11 },
12};
13
14use crate::model::memory::env::EnvData;
15
16#[derive(Debug, ThisError)]
21pub enum EnvOpsError {
22 #[error("operation must be called from the root canister")]
24 NotRoot,
25
26 #[error("operation cannot be called from the root canister")]
28 IsRoot,
29
30 #[error("failed to determine current canister role")]
31 CanisterRoleUnavailable,
32
33 #[error("env import missing required fields: {0}")]
34 MissingFields(String),
35
36 #[error("failed to determine current parent principal")]
37 ParentPidUnavailable,
38
39 #[error("failed to determine current root principal")]
40 RootPidUnavailable,
41
42 #[error("failed to determine current subnet principal")]
43 SubnetPidUnavailable,
44
45 #[error("failed to determine current subnet role")]
46 SubnetRoleUnavailable,
47}
48
49impl EnvOpsError {}
50
51impl From<EnvOpsError> for Error {
52 fn from(err: EnvOpsError) -> Self {
53 RuntimeOpsError::from(err).into()
54 }
55}
56
57pub struct EnvOps;
68
69impl EnvOps {
70 pub fn init_root(identity: SubnetIdentity) -> Result<(), Error> {
78 let self_pid = canister_self();
79
80 let (subnet_pid, subnet_role, prime_root_pid) = match identity {
81 SubnetIdentity::Prime => {
82 (self_pid, SubnetRole::PRIME, self_pid)
84 }
85
86 SubnetIdentity::Standard(params) => {
87 (self_pid, params.subnet_type, params.prime_root_pid)
89 }
90
91 SubnetIdentity::Manual(pid) => {
92 (pid, SubnetRole::MANUAL, pid)
94 }
95 };
96
97 let env = EnvData {
98 prime_root_pid: Some(prime_root_pid),
99 root_pid: Some(self_pid),
100 subnet_pid: Some(subnet_pid),
101 subnet_role: Some(subnet_role),
102 canister_role: Some(CanisterRole::ROOT),
103 parent_pid: Some(prime_root_pid),
104 };
105
106 Self::import_data(env)
107 }
108
109 pub fn init(env: EnvView, role: CanisterRole) -> Result<(), Error> {
113 let mut env = env_data_from_view(env);
114 env.canister_role = Some(role.clone());
116 env = ensure_nonroot_env(role, env)?;
117
118 Self::import_data(env)
120 }
121
122 pub fn import(env: EnvView) -> Result<(), Error> {
123 let env = env_data_from_view(env);
124 Self::import_data(env)
125 }
126
127 fn import_data(env: EnvData) -> Result<(), Error> {
128 let mut missing = Vec::new();
129 if env.prime_root_pid.is_none() {
130 missing.push("prime_root_pid");
131 }
132 if env.subnet_role.is_none() {
133 missing.push("subnet_role");
134 }
135 if env.subnet_pid.is_none() {
136 missing.push("subnet_pid");
137 }
138 if env.root_pid.is_none() {
139 missing.push("root_pid");
140 }
141 if env.canister_role.is_none() {
142 missing.push("canister_role");
143 }
144 if env.parent_pid.is_none() {
145 missing.push("parent_pid");
146 }
147
148 if !missing.is_empty() {
149 return Err(EnvOpsError::MissingFields(missing.join(", ")).into());
150 }
151
152 Env::import(env);
153 Ok(())
154 }
155
156 pub fn set_prime_root_pid(pid: Principal) {
157 Env::set_prime_root_pid(pid);
158 }
159
160 pub fn set_subnet_role(role: SubnetRole) {
161 Env::set_subnet_role(role);
162 }
163
164 pub fn set_subnet_pid(pid: Principal) {
165 Env::set_subnet_pid(pid);
166 }
167
168 pub fn set_root_pid(pid: Principal) {
169 Env::set_root_pid(pid);
170 }
171
172 pub fn set_canister_role(role: CanisterRole) {
173 Env::set_canister_role(role);
174 }
175
176 #[must_use]
181 pub fn is_prime_root() -> bool {
182 let Some(prime_root) = Env::get_prime_root_pid() else {
183 return false;
184 };
185 let Some(root_pid) = Env::get_root_pid() else {
186 return false;
187 };
188 prime_root == root_pid
189 }
190
191 #[must_use]
192 pub fn is_prime_subnet() -> bool {
193 Env::get_subnet_role().is_some_and(|role| role.is_prime())
194 }
195
196 #[must_use]
197 pub fn is_root() -> bool {
198 Env::get_root_pid().is_some_and(|pid| pid == canister_self())
199 }
200
201 pub fn subnet_role() -> Result<SubnetRole, Error> {
207 Env::get_subnet_role().ok_or_else(|| EnvOpsError::SubnetRoleUnavailable.into())
208 }
209
210 pub fn canister_role() -> Result<CanisterRole, Error> {
211 Env::get_canister_role().ok_or_else(|| EnvOpsError::CanisterRoleUnavailable.into())
212 }
213
214 pub fn subnet_pid() -> Result<Principal, Error> {
215 Env::get_subnet_pid().ok_or_else(|| EnvOpsError::SubnetPidUnavailable.into())
216 }
217
218 pub fn root_pid() -> Result<Principal, Error> {
219 Env::get_root_pid().ok_or_else(|| EnvOpsError::RootPidUnavailable.into())
220 }
221
222 pub fn prime_root_pid() -> Result<Principal, Error> {
223 Env::get_prime_root_pid().ok_or_else(|| EnvOpsError::RootPidUnavailable.into())
224 }
225
226 pub fn parent_pid() -> Result<Principal, Error> {
227 Env::get_parent_pid().ok_or_else(|| EnvOpsError::ParentPidUnavailable.into())
228 }
229
230 pub fn require_root() -> Result<(), Error> {
236 let root_pid = Self::root_pid()?;
237
238 if root_pid == canister_self() {
239 Ok(())
240 } else {
241 Err(EnvOpsError::NotRoot.into())
242 }
243 }
244
245 pub fn deny_root() -> Result<(), Error> {
247 let root_pid = Self::root_pid()?; if root_pid == canister_self() {
250 Err(EnvOpsError::IsRoot.into())
251 } else {
252 Ok(())
253 }
254 }
255
256 pub fn restore_root() -> Result<(), Error> {
268 assert_initialized()?;
270
271 Env::set_canister_role(CanisterRole::ROOT);
273 Ok(())
274 }
275
276 pub fn restore_role(role: CanisterRole) -> Result<(), Error> {
281 assert_initialized()?;
283
284 Env::set_canister_role(role);
286 Ok(())
287 }
288
289 #[must_use]
295 pub fn export() -> EnvView {
296 env_data_to_view(Env::export())
297 }
298}
299
300fn assert_initialized() -> Result<(), Error> {
301 let mut missing = Vec::new();
302 if Env::get_root_pid().is_none() {
303 missing.push("root_pid");
304 }
305 if Env::get_subnet_pid().is_none() {
306 missing.push("subnet_pid");
307 }
308 if Env::get_prime_root_pid().is_none() {
309 missing.push("prime_root_pid");
310 }
311
312 if missing.is_empty() {
313 Ok(())
314 } else {
315 Err(EnvOpsError::MissingFields(missing.join(", ")).into())
316 }
317}
318
319fn ensure_nonroot_env(canister_role: CanisterRole, mut env: EnvData) -> Result<EnvData, Error> {
320 let mut missing = Vec::new();
321 if env.prime_root_pid.is_none() {
322 missing.push("prime_root_pid");
323 }
324 if env.subnet_role.is_none() {
325 missing.push("subnet_role");
326 }
327 if env.subnet_pid.is_none() {
328 missing.push("subnet_pid");
329 }
330 if env.root_pid.is_none() {
331 missing.push("root_pid");
332 }
333 if env.canister_role.is_none() {
334 missing.push("canister_role");
335 }
336 if env.parent_pid.is_none() {
337 missing.push("parent_pid");
338 }
339
340 if missing.is_empty() {
341 return Ok(env);
342 }
343
344 if build_network() == Some(Network::Ic) {
345 return Err(EnvOpsError::MissingFields(missing.join(", ")).into());
346 }
347
348 let root_pid = Principal::from_slice(&[0xBB; 29]);
349 let subnet_pid = Principal::from_slice(&[0xAA; 29]);
350
351 env.prime_root_pid.get_or_insert(root_pid);
352 env.subnet_role.get_or_insert(SubnetRole::PRIME);
353 env.subnet_pid.get_or_insert(subnet_pid);
354 env.root_pid.get_or_insert(root_pid);
355 env.canister_role.get_or_insert(canister_role);
356 env.parent_pid.get_or_insert(root_pid);
357
358 Ok(env)
359}