1#[cfg(test)]
8mod tests;
9
10use crate::node::{
11 stable_memory_key, validate_app_memory_id, validate_memory_id_in_range,
12 validate_memory_id_not_reserved, validate_stable_key, validate_stable_key_segment,
13};
14use crate::prelude::*;
15use std::collections::BTreeMap;
16
17#[derive(Clone, Debug, Serialize)]
22pub struct Canister {
23 def: Def,
24 memory_namespace: &'static str,
25 memory_min: u8,
26 memory_max: u8,
27 commit_memory_id: u8,
28 integrity_progress_memory_id: u8,
29}
30
31impl Canister {
32 #[must_use]
33 pub const fn new(
34 def: Def,
35 memory_namespace: &'static str,
36 memory_min: u8,
37 memory_max: u8,
38 commit_memory_id: u8,
39 integrity_progress_memory_id: u8,
40 ) -> Self {
41 Self {
42 def,
43 memory_namespace,
44 memory_min,
45 memory_max,
46 commit_memory_id,
47 integrity_progress_memory_id,
48 }
49 }
50
51 #[must_use]
52 pub const fn def(&self) -> &Def {
53 &self.def
54 }
55
56 #[must_use]
57 pub const fn memory_namespace(&self) -> &'static str {
58 self.memory_namespace
59 }
60
61 #[must_use]
62 pub const fn memory_min(&self) -> u8 {
63 self.memory_min
64 }
65
66 #[must_use]
67 pub const fn memory_max(&self) -> u8 {
68 self.memory_max
69 }
70
71 #[must_use]
72 pub const fn commit_memory_id(&self) -> u8 {
73 self.commit_memory_id
74 }
75
76 #[must_use]
77 pub const fn integrity_progress_memory_id(&self) -> u8 {
78 self.integrity_progress_memory_id
79 }
80
81 #[must_use]
82 pub fn commit_stable_key(&self) -> String {
83 stable_memory_key(self.memory_namespace(), "commit", "control")
84 }
85
86 #[must_use]
87 pub fn integrity_progress_stable_key(&self) -> String {
88 stable_memory_key(self.memory_namespace(), "integrity", "progress")
89 }
90
91 fn validate_declared_memory_contract(&self, errs: &mut ErrorTree) {
92 validate_stable_key_segment(errs, "canister memory_namespace", self.memory_namespace());
93 validate_memory_id_in_range(
94 errs,
95 "commit_memory_id",
96 self.commit_memory_id(),
97 self.memory_min(),
98 self.memory_max(),
99 );
100 validate_app_memory_id(errs, "commit_memory_id", self.commit_memory_id());
101 validate_memory_id_not_reserved(errs, "commit_memory_id", self.commit_memory_id());
102 validate_stable_key(errs, "commit stable key", &self.commit_stable_key());
103 validate_memory_id_in_range(
104 errs,
105 "integrity_progress_memory_id",
106 self.integrity_progress_memory_id(),
107 self.memory_min(),
108 self.memory_max(),
109 );
110 validate_app_memory_id(
111 errs,
112 "integrity_progress_memory_id",
113 self.integrity_progress_memory_id(),
114 );
115 validate_memory_id_not_reserved(
116 errs,
117 "integrity_progress_memory_id",
118 self.integrity_progress_memory_id(),
119 );
120 validate_stable_key(
121 errs,
122 "integrity progress stable key",
123 &self.integrity_progress_stable_key(),
124 );
125 }
126
127 fn register_store_allocations(
128 &self,
129 canister_path: &str,
130 seen_ids: &mut BTreeMap<u8, (String, String)>,
131 seen_keys: &mut BTreeMap<String, (u8, String)>,
132 errs: &mut ErrorTree,
133 ) {
134 let schema = schema_read();
135 for (path, store) in schema.filter_nodes::<Store>(|node| node.canister() == canister_path) {
136 if !matches!(store.storage(), StoreStorage::Journaled(_)) {
137 continue;
138 }
139 for (allocation, role) in [
140 (
141 store.stable_data_allocation(self.memory_namespace()),
142 "data",
143 ),
144 (
145 store.stable_index_allocation(self.memory_namespace()),
146 "index",
147 ),
148 (
149 store.stable_schema_allocation(self.memory_namespace()),
150 "schema",
151 ),
152 ] {
153 assert_unique_memory_allocation(
154 allocation.memory_id(),
155 allocation.stable_key().to_string(),
156 format!("Store `{path}`.{role}_memory"),
157 canister_path,
158 seen_ids,
159 seen_keys,
160 errs,
161 );
162 }
163 if store.is_journaled_storage() {
164 let allocation = store.journal_allocation(self.memory_namespace());
165 assert_unique_memory_allocation(
166 allocation.memory_id(),
167 allocation.stable_key().to_string(),
168 format!("Store `{path}`.journal_memory"),
169 canister_path,
170 seen_ids,
171 seen_keys,
172 errs,
173 );
174 }
175 }
176 }
177}
178
179impl MacroNode for Canister {
180 fn as_any(&self) -> &dyn std::any::Any {
181 self
182 }
183}
184
185impl ValidateNode for Canister {
186 fn validate(&self) -> Result<(), ErrorTree> {
187 let mut errs = ErrorTree::new();
188
189 let canister_path = self.def().path();
190 let mut seen_ids = BTreeMap::<u8, (String, String)>::new();
191 let mut seen_keys = BTreeMap::<String, (u8, String)>::new();
192
193 self.validate_declared_memory_contract(&mut errs);
194
195 assert_unique_memory_allocation(
196 self.commit_memory_id(),
197 self.commit_stable_key(),
198 format!("Canister `{}`.commit_memory", self.def().path()),
199 &canister_path,
200 &mut seen_ids,
201 &mut seen_keys,
202 &mut errs,
203 );
204 assert_unique_memory_allocation(
205 self.integrity_progress_memory_id(),
206 self.integrity_progress_stable_key(),
207 format!("Canister `{}`.integrity_progress_memory", self.def().path()),
208 &canister_path,
209 &mut seen_ids,
210 &mut seen_keys,
211 &mut errs,
212 );
213 self.register_store_allocations(&canister_path, &mut seen_ids, &mut seen_keys, &mut errs);
214
215 errs.result()
216 }
217}
218
219fn assert_unique_memory_allocation(
220 memory_id: u8,
221 stable_key: String,
222 slot: String,
223 canister_path: &str,
224 seen_ids: &mut BTreeMap<u8, (String, String)>,
225 seen_keys: &mut BTreeMap<String, (u8, String)>,
226 errs: &mut ErrorTree,
227) {
228 if let Some((existing_key, existing_slot)) = seen_ids.get(&memory_id) {
229 err!(
230 errs,
231 "duplicate memory_id `{}` used in canister `{}`: {} ({}) conflicts with {} ({})",
232 memory_id,
233 canister_path,
234 existing_slot,
235 existing_key,
236 slot,
237 stable_key,
238 );
239 } else {
240 seen_ids.insert(memory_id, (stable_key.clone(), slot.clone()));
241 }
242
243 if let Some((existing_id, existing_slot)) = seen_keys.get(&stable_key) {
244 err!(
245 errs,
246 "duplicate stable_key `{}` used in canister `{}`: {} ({}) conflicts with {} ({})",
247 stable_key,
248 canister_path,
249 existing_slot,
250 existing_id,
251 slot,
252 memory_id,
253 );
254 } else {
255 seen_keys.insert(stable_key, (memory_id, slot));
256 }
257}
258
259impl VisitableNode for Canister {
260 fn route_key(&self) -> String {
261 self.def().path()
262 }
263}