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