1use crate::{MemoryManagerIdRange, 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 !key_prefix.ends_with('.') || StableKey::parse(format!("{key_prefix}v1")).is_err() {
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 excluded_ranges: Vec<MemoryManagerIdRange>,
88}
89
90impl MemoryAllocationPool {
91 pub fn new(
97 mut authorities: Vec<MemoryAuthority>,
98 exclusions: Vec<MemoryManagerIdRange>,
99 ) -> Result<Self, MemoryAllocationPoolError> {
100 if authorities.len() > 254 || exclusions.len() > 255 {
101 return Err(MemoryAllocationPoolError::TooManyEntries);
102 }
103 authorities.sort_unstable_by(|a, b| a.key_prefix.cmp(&b.key_prefix));
104 for pair in authorities.windows(2) {
105 if pair[1].key_prefix.starts_with(&pair[0].key_prefix) {
106 return Err(MemoryAllocationPoolError::OverlappingNamespaces {
107 existing_prefix: pair[0].key_prefix.clone(),
108 candidate_prefix: pair[1].key_prefix.clone(),
109 });
110 }
111 }
112 let mut excluded = [false; 255];
113 for range in exclusions
114 .into_iter()
115 .chain(std::iter::once(crate::memory_manager_governance_range()))
116 {
117 for id in range.start()..=range.end() {
118 excluded[usize::from(id)] = true;
119 }
120 }
121 let mut excluded_ranges = Vec::new();
122 let mut id = 0;
123 while id < 255 {
124 if !excluded[id] {
125 id += 1;
126 continue;
127 }
128 let start = id;
129 while id + 1 < 255 && excluded[id + 1] {
130 id += 1;
131 }
132 excluded_ranges.push(
133 MemoryManagerIdRange::new(
134 u8::try_from(start).expect("usable start"),
135 u8::try_from(id).expect("usable end"),
136 )
137 .expect("ordered usable range"),
138 );
139 id += 1;
140 }
141 Ok(Self {
142 authorities,
143 excluded_ranges,
144 })
145 }
146
147 #[must_use]
149 pub fn authorities(&self) -> &[MemoryAuthority] {
150 &self.authorities
151 }
152
153 #[must_use]
155 pub fn excluded_ranges(&self) -> &[MemoryManagerIdRange] {
156 &self.excluded_ranges
157 }
158
159 #[must_use]
161 pub fn contains(&self, id: u8) -> bool {
162 id != crate::MEMORY_MANAGER_INVALID_ID
163 && !self.excluded_ranges.iter().any(|range| range.contains(id))
164 }
165
166 pub fn validate_authority(
168 &self,
169 key: &StableKey,
170 authority: &str,
171 ) -> Result<(), MemoryAllocationPoolError> {
172 let admitted_authority =
173 self.authority_for_key(key)
174 .ok_or_else(|| MemoryAllocationPoolError::UnclaimedKey {
175 stable_key: key.clone(),
176 })?;
177 if admitted_authority != authority {
178 return Err(MemoryAllocationPoolError::AuthorityMismatch {
179 stable_key: key.clone(),
180 requested_authority: authority.to_string(),
181 admitted_authority: admitted_authority.to_string(),
182 });
183 }
184 Ok(())
185 }
186
187 pub(crate) fn authority_for_key(&self, key: &StableKey) -> Option<&str> {
190 self.authorities
191 .iter()
192 .find(|grant| key.as_str().starts_with(&grant.key_prefix))
193 .map(MemoryAuthority::authority)
194 }
195
196 pub fn validate_id(&self, id: u8) -> Result<(), MemoryAllocationPoolError> {
198 crate::validate_memory_manager_id(id)?;
199 if !self.contains(id) {
200 return Err(MemoryAllocationPoolError::ExcludedSlot { id });
201 }
202 Ok(())
203 }
204}
205
206impl<'de> Deserialize<'de> for MemoryAllocationPool {
207 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
208 #[derive(Deserialize)]
209 #[serde(deny_unknown_fields)]
210 struct Pool {
211 authorities: Vec<MemoryAuthority>,
212 excluded_ranges: Vec<MemoryManagerIdRange>,
213 }
214 let pool = Pool::deserialize(deserializer)?;
215 Self::new(pool.authorities, pool.excluded_ranges).map_err(D::Error::custom)
216 }
217}
218
219#[non_exhaustive]
226#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
227pub enum MemoryAllocationPoolError {
228 #[error("host allocation policy exceeds the bounded slot domain")]
229 TooManyEntries,
230 #[error("authority {reason}")]
231 InvalidAuthority { reason: &'static str },
232 #[error("ic-memory governance authority cannot be granted externally")]
233 ReservedAuthority,
234 #[error("invalid application key namespace prefix {key_prefix}")]
235 InvalidKeyPrefix { key_prefix: String },
236 #[error("key namespaces {existing_prefix} and {candidate_prefix} overlap")]
237 OverlappingNamespaces {
238 existing_prefix: String,
239 candidate_prefix: String,
240 },
241 #[error("stable key {stable_key} has no host namespace grant")]
242 UnclaimedKey { stable_key: StableKey },
243 #[error(
244 "stable key {stable_key} is granted to {admitted_authority}, not {requested_authority}"
245 )]
246 AuthorityMismatch {
247 stable_key: StableKey,
248 requested_authority: String,
249 admitted_authority: String,
250 },
251 #[error("MemoryManager ID {id} is excluded from the application pool")]
252 ExcludedSlot { id: u8 },
253 #[error(transparent)]
254 Slot(#[from] crate::MemoryManagerSlotError),
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn canonical_policy_has_one_physical_pool_and_disjoint_namespaces() {
263 let grant = |owner, prefix| MemoryAuthority::new(owner, prefix).unwrap();
264 let pool = MemoryAllocationPool::new(
265 vec![grant("db", "db."), grant("app", "app.")],
266 vec![
267 MemoryManagerIdRange::new(20, 25).unwrap(),
268 MemoryManagerIdRange::new(23, 30).unwrap(),
269 MemoryManagerIdRange::new(31, 31).unwrap(),
270 ],
271 )
272 .unwrap();
273 assert_eq!(pool.authorities()[0].authority(), "app");
274 assert_eq!(
275 pool.excluded_ranges(),
276 &[
277 MemoryManagerIdRange::new(0, 9).unwrap(),
278 MemoryManagerIdRange::new(20, 31).unwrap()
279 ]
280 );
281 for id in [0, 9, 20, 31, 255] {
282 assert!(!pool.contains(id));
283 }
284 for id in [10, 19, 32, 254] {
285 assert!(pool.contains(id));
286 }
287 assert!(matches!(
288 MemoryAllocationPool::new(vec![grant("app", "app."), grant("db", "app.db.")], vec![]),
289 Err(MemoryAllocationPoolError::OverlappingNamespaces { .. })
290 ));
291 assert!(
292 pool.validate_authority(&StableKey::parse("application.rows.v1").unwrap(), "app")
293 .is_err()
294 );
295 assert!(
296 pool.validate_authority(&StableKey::parse("app.rows.v1").unwrap(), "db")
297 .is_err()
298 );
299 }
300
301 #[test]
302 fn decoding_cannot_bypass_namespace_or_physical_exclusion_admission() {
303 let pool: MemoryAllocationPool = serde_json::from_str(
304 r#"{
305 "authorities":[{"authority":"app","key_prefix":"app."}],
306 "excluded_ranges":[{"start":10,"end":10}]
307 }"#,
308 )
309 .unwrap();
310 assert!(!pool.contains(0));
311 assert!(!pool.contains(10));
312 assert!(pool.contains(11));
313 for json in [
314 r#"{"authorities":[{"authority":"ic-memory","key_prefix":"app."}],"excluded_ranges":[]}"#,
315 r#"{"authorities":[{"authority":"app","key_prefix":"ic_memory."}],"excluded_ranges":[]}"#,
316 r#"{"authorities":[],"excluded_ranges":[{"start":254,"end":255}]}"#,
317 r#"{"authorities":[],"excluded_ranges":[],"unknown":true}"#,
318 ] {
319 assert!(serde_json::from_str::<MemoryAllocationPool>(json).is_err());
320 }
321 assert_eq!(
322 serde_json::from_str::<MemoryAllocationPool>(&serde_json::to_string(&pool).unwrap())
323 .unwrap(),
324 pool
325 );
326 }
327}