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