1use crate::{StableKey, text::validate_diagnostic_text};
2use serde::Serialize;
3#[cfg(any(test, feature = "test-support"))]
4use serde::{Deserialize, Deserializer, de::Error as _};
5
6#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
18pub struct MemoryAuthority {
19 authority: String,
20 key_prefix: String,
21}
22
23impl MemoryAuthority {
24 pub fn new(
26 authority: impl Into<String>,
27 key_prefix: impl Into<String>,
28 ) -> Result<Self, MemoryAllocationPoolError> {
29 let authority = authority.into();
30 validate_diagnostic_text(&authority).map_err(|error| {
31 MemoryAllocationPoolError::InvalidAuthority {
32 reason: error.reason(),
33 }
34 })?;
35 if authority == crate::IC_MEMORY_AUTHORITY_OWNER {
36 return Err(MemoryAllocationPoolError::ReservedAuthority);
37 }
38 let key_prefix = key_prefix.into();
39 if !crate::key::is_key_prefix(&key_prefix)
40 || key_prefix.starts_with(crate::IC_MEMORY_STABLE_KEY_PREFIX)
41 {
42 return Err(MemoryAllocationPoolError::InvalidKeyPrefix { key_prefix });
43 }
44 Ok(Self {
45 authority,
46 key_prefix,
47 })
48 }
49
50 #[must_use]
52 pub fn authority(&self) -> &str {
53 &self.authority
54 }
55
56 #[must_use]
58 pub fn key_prefix(&self) -> &str {
59 &self.key_prefix
60 }
61}
62
63#[cfg(any(test, feature = "test-support"))]
64impl<'de> Deserialize<'de> for MemoryAuthority {
65 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
66 #[derive(Deserialize)]
67 #[serde(deny_unknown_fields)]
68 struct Grant {
69 authority: String,
70 key_prefix: String,
71 }
72 let grant = Grant::deserialize(deserializer)?;
73 Self::new(grant.authority, grant.key_prefix).map_err(D::Error::custom)
74 }
75}
76
77#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
93pub struct MemoryAllocationPool {
94 authorities: Vec<MemoryAuthority>,
95 #[serde(rename = "excluded_ids", serialize_with = "serialize_excluded_ids")]
96 excluded: [u64; 4],
97}
98
99impl MemoryAllocationPool {
100 pub fn new(
103 mut authorities: Vec<MemoryAuthority>,
104 exclusions: Vec<u8>,
105 ) -> Result<Self, MemoryAllocationPoolError> {
106 if authorities.len() > crate::constants::MAX_EXTERNAL_DECLARATIONS
107 || exclusions.len() > crate::constants::MAX_ALLOCATIONS
108 {
109 return Err(MemoryAllocationPoolError::TooManyEntries);
110 }
111 crate::sort::sort_unstable_by(&mut authorities, |a, b| a.key_prefix.cmp(&b.key_prefix));
112 for pair in authorities.windows(2) {
113 if pair[1].key_prefix.starts_with(&pair[0].key_prefix) {
114 return Err(MemoryAllocationPoolError::OverlappingNamespaces {
115 existing_prefix: pair[0].key_prefix.clone(),
116 candidate_prefix: pair[1].key_prefix.clone(),
117 });
118 }
119 }
120 let mut excluded = [0; 4];
121 for id in exclusions
122 .into_iter()
123 .chain(0..=crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID)
124 {
125 crate::validate_memory_manager_id(id)?;
126 excluded[usize::from(id / 64)] |= 1 << (id % 64);
127 }
128 Ok(Self {
129 authorities,
130 excluded,
131 })
132 }
133
134 #[must_use]
136 pub fn authorities(&self) -> &[MemoryAuthority] {
137 &self.authorities
138 }
139
140 pub fn excluded_ids(&self) -> impl Iterator<Item = u8> + '_ {
142 (0..crate::MEMORY_MANAGER_INVALID_ID).filter(|id| is_excluded(&self.excluded, *id))
143 }
144
145 #[must_use]
147 pub fn contains(&self, id: u8) -> bool {
148 id != crate::MEMORY_MANAGER_INVALID_ID && !is_excluded(&self.excluded, id)
149 }
150
151 pub fn validate_authority(
153 &self,
154 key: &StableKey,
155 authority: &str,
156 ) -> Result<(), MemoryAllocationPoolError> {
157 let admitted_authority =
158 self.authority_for_key(key)
159 .ok_or_else(|| MemoryAllocationPoolError::UnclaimedKey {
160 stable_key: key.clone(),
161 })?;
162 if admitted_authority != authority {
163 return Err(MemoryAllocationPoolError::AuthorityMismatch {
164 stable_key: key.clone(),
165 requested_authority: authority.to_string(),
166 admitted_authority: admitted_authority.to_string(),
167 });
168 }
169 Ok(())
170 }
171
172 #[must_use]
175 pub fn authority_for_key(&self, key: &StableKey) -> Option<&str> {
176 self.authorities
177 .iter()
178 .find(|grant| key.as_str().starts_with(&grant.key_prefix))
179 .map(MemoryAuthority::authority)
180 }
181
182 pub fn validate_id(&self, id: u8) -> Result<(), MemoryAllocationPoolError> {
184 crate::validate_memory_manager_id(id)?;
185 if !self.contains(id) {
186 return Err(MemoryAllocationPoolError::ExcludedSlot { id });
187 }
188 Ok(())
189 }
190}
191
192#[cfg(any(test, feature = "test-support"))]
193impl<'de> Deserialize<'de> for MemoryAllocationPool {
194 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
195 #[derive(Deserialize)]
196 #[serde(deny_unknown_fields)]
197 struct Pool {
198 authorities: Vec<MemoryAuthority>,
199 #[serde(deserialize_with = "crate::encoding::deserialize_records")]
200 excluded_ids: Vec<u8>,
201 }
202 let pool = Pool::deserialize(deserializer)?;
203 Self::new(pool.authorities, pool.excluded_ids).map_err(D::Error::custom)
204 }
205}
206
207fn is_excluded(excluded: &[u64; 4], id: u8) -> bool {
208 excluded[usize::from(id / 64)] & (1 << (id % 64)) != 0
209}
210
211fn serialize_excluded_ids<S: serde::Serializer>(
212 excluded: &[u64; 4],
213 serializer: S,
214) -> Result<S::Ok, S::Error> {
215 use serde::ser::SerializeSeq as _;
216 let count = excluded.iter().map(|word| word.count_ones() as usize).sum();
217 let mut seq = serializer.serialize_seq(Some(count))?;
218 for id in (0..crate::MEMORY_MANAGER_INVALID_ID).filter(|id| is_excluded(excluded, *id)) {
219 seq.serialize_element(&id)?;
220 }
221 seq.end()
222}
223
224#[non_exhaustive]
231#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
232pub enum MemoryAllocationPoolError {
233 #[error("host allocation policy exceeds the bounded slot domain")]
234 TooManyEntries,
235 #[error("authority {reason}")]
236 InvalidAuthority { reason: &'static str },
237 #[error("ic-memory governance authority cannot be granted externally")]
238 ReservedAuthority,
239 #[error("invalid application key namespace prefix {key_prefix}")]
240 InvalidKeyPrefix { key_prefix: String },
241 #[error("key namespaces {existing_prefix} and {candidate_prefix} overlap")]
242 OverlappingNamespaces {
243 existing_prefix: String,
244 candidate_prefix: String,
245 },
246 #[error("stable key {stable_key} has no host namespace grant")]
247 UnclaimedKey { stable_key: StableKey },
248 #[error(
249 "stable key {stable_key} is granted to {admitted_authority}, not {requested_authority}"
250 )]
251 AuthorityMismatch {
252 stable_key: StableKey,
253 requested_authority: String,
254 admitted_authority: String,
255 },
256 #[error("MemoryManager ID {id} is excluded from the application pool")]
257 ExcludedSlot { id: u8 },
258 #[error(transparent)]
259 Slot(#[from] crate::MemoryManagerSlotError),
260}
261
262#[cfg(test)]
263mod tests {
264 #[test]
265 fn namespace_admission_checks_segments_and_complete_key_length() {
266 for prefix in ["a.", "app.a_0.", "app.v0.", "app.v1."] {
267 let grant = super::MemoryAuthority::new("app", prefix).unwrap();
268 assert_eq!(grant.key_prefix(), prefix);
269 }
270 for prefix in ["", ".", "app", "app..", "app.0.", "App.", "app/a."] {
271 assert!(matches!(
272 super::MemoryAuthority::new("app", prefix),
273 Err(super::MemoryAllocationPoolError::InvalidKeyPrefix { key_prefix })
274 if key_prefix == prefix
275 ));
276 }
277 for byte in 0..=u8::MAX {
278 let prefix = format!("app.a{}.", char::from(byte));
279 let allowed = byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_';
280 assert_eq!(super::MemoryAuthority::new("app", prefix).is_ok(), allowed);
281 }
282 assert!(super::MemoryAuthority::new("app", format!("{}.", "a".repeat(125))).is_ok());
283 assert!(matches!(
284 super::MemoryAuthority::new("app", format!("{}.", "a".repeat(126))),
285 Err(super::MemoryAllocationPoolError::InvalidKeyPrefix { .. })
286 ));
287 }
288
289 use super::*;
290
291 #[test]
292 fn canonical_policy_has_one_physical_pool_and_disjoint_namespaces() {
293 let grant = |owner, prefix| MemoryAuthority::new(owner, prefix).unwrap();
294 let pool = MemoryAllocationPool::new(
295 vec![grant("db", "db."), grant("app", "app.")],
296 (20..=25).chain(23..=30).chain(31..=31).collect(),
297 )
298 .unwrap();
299 assert_eq!(pool.authorities()[0].authority(), "app");
300 assert_eq!(
301 pool.excluded_ids().collect::<Vec<_>>(),
302 (0..=9).chain(20..=31).collect::<Vec<_>>()
303 );
304 for id in [0, 9, 20, 31, 255] {
305 assert!(!pool.contains(id));
306 }
307 for id in [10, 19, 32, 254] {
308 assert!(pool.contains(id));
309 }
310 assert!(matches!(
311 MemoryAllocationPool::new(vec![grant("app", "app."), grant("db", "app.db.")], vec![]),
312 Err(MemoryAllocationPoolError::OverlappingNamespaces { .. })
313 ));
314 assert!(
315 pool.validate_authority(&StableKey::parse("application.rows.v1").unwrap(), "app")
316 .is_err()
317 );
318 assert!(
319 pool.validate_authority(&StableKey::parse("app.rows.v1").unwrap(), "db")
320 .is_err()
321 );
322 }
323
324 #[test]
325 fn decoding_cannot_bypass_namespace_or_physical_exclusion_admission() {
326 let pool: MemoryAllocationPool = serde_json::from_str(
327 r#"{
328 "authorities":[{"authority":"app","key_prefix":"app."}],
329 "excluded_ids":[10]
330 }"#,
331 )
332 .unwrap();
333 assert!(!pool.contains(0));
334 assert!(!pool.contains(10));
335 assert!(pool.contains(11));
336 for json in [
337 r#"{"authorities":[{"authority":"ic-memory","key_prefix":"app."}],"excluded_ids":[]}"#,
338 r#"{"authorities":[{"authority":"app","key_prefix":"ic_memory."}],"excluded_ids":[]}"#,
339 r#"{"authorities":[],"excluded_ids":[255]}"#,
340 r#"{"authorities":[],"excluded_ids":[],"unknown":true}"#,
341 ] {
342 assert!(serde_json::from_str::<MemoryAllocationPool>(json).is_err());
343 }
344 assert_eq!(
345 serde_json::from_str::<MemoryAllocationPool>(&serde_json::to_string(&pool).unwrap())
346 .unwrap(),
347 pool
348 );
349 }
350 #[test]
351 fn exclusion_membership_and_wire_projection_cover_every_usable_id() {
352 for ids in [
353 vec![0, 9, 10, 63, 64, 127, 128, 191, 192, 254],
354 (10..=254).step_by(2).collect(),
355 (0..=254).collect(),
356 ] {
357 let pool = MemoryAllocationPool::new(vec![], ids.clone()).unwrap();
358 let reversed =
359 MemoryAllocationPool::new(vec![], ids.iter().rev().copied().collect()).unwrap();
360 assert_eq!(pool, reversed);
361 for id in 0..=u8::MAX {
362 assert_eq!(
363 pool.contains(id),
364 id > crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID
365 && id != u8::MAX
366 && !ids.contains(&id)
367 );
368 }
369 let projected: Vec<_> = (0..255).filter(|id| !pool.contains(*id)).collect();
370 assert_eq!(pool.excluded_ids().collect::<Vec<_>>(), projected);
371 let json = serde_json::to_value(&pool).unwrap();
372 assert_eq!(json["excluded_ids"], serde_json::json!(projected));
373 assert_eq!(
374 serde_json::from_value::<MemoryAllocationPool>(json).unwrap(),
375 pool
376 );
377 let bytes = crate::test_wire::to_vec(&pool).unwrap();
378 assert_eq!(
379 crate::test_wire::from_slice::<MemoryAllocationPool>(&bytes).unwrap(),
380 pool
381 );
382 }
383 assert!(matches!(
384 MemoryAllocationPool::new(vec![], vec![255]),
385 Err(MemoryAllocationPoolError::Slot(_))
386 ));
387 assert!(matches!(
388 MemoryAllocationPool::new(vec![], vec![10; 256]),
389 Err(MemoryAllocationPoolError::TooManyEntries)
390 ));
391 assert_eq!(
392 MemoryAllocationPool::new(vec![], vec![63, 64, 63]).unwrap(),
393 MemoryAllocationPool::new(vec![], vec![64, 63]).unwrap()
394 );
395 }
396}