1use sim_expr_tree_core::{CodecPolicyPatch, EffectiveCodecPolicy};
2use sim_kernel::{CapabilityName, CapabilitySet};
3use sim_table_core::TablePath;
4
5use super::CalcLimits;
6
7#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
9pub enum CalcTrigger {
10 #[default]
12 Automatic,
13 OnDemand,
15 Manual,
17 Frozen,
19}
20
21#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
23pub enum ErrorMode {
24 #[default]
26 Continue,
27 FailFast,
29}
30
31#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
33pub enum CycleMode {
34 #[default]
36 Fail,
37 Block,
39}
40
41#[derive(Clone, Debug, Default, Eq, PartialEq)]
43pub struct CalcPolicyPatch {
44 pub trigger: Option<CalcTrigger>,
46 pub error_mode: Option<ErrorMode>,
48 pub cycle_mode: Option<CycleMode>,
50 pub budget: Option<CalcLimits>,
52 pub priority: Option<i16>,
54 pub debounce_ms: Option<u32>,
56}
57
58impl CalcPolicyPatch {
59 pub fn apply_to(&self, effective: &mut EffectiveCalcPolicy) {
61 if let Some(trigger) = self.trigger {
62 effective.trigger = trigger;
63 }
64 if let Some(error_mode) = self.error_mode {
65 effective.error_mode = error_mode;
66 }
67 if let Some(cycle_mode) = self.cycle_mode {
68 effective.cycle_mode = cycle_mode;
69 }
70 if let Some(budget) = self.budget {
71 effective.budget = budget;
72 }
73 if let Some(priority) = self.priority {
74 effective.priority = priority;
75 }
76 if let Some(debounce_ms) = self.debounce_ms {
77 effective.debounce_ms = debounce_ms;
78 }
79 }
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub struct EffectiveCalcPolicy {
85 pub trigger: CalcTrigger,
87 pub error_mode: ErrorMode,
89 pub cycle_mode: CycleMode,
91 pub budget: CalcLimits,
93 pub priority: i16,
95 pub debounce_ms: u32,
97}
98
99impl Default for EffectiveCalcPolicy {
100 fn default() -> Self {
101 Self {
102 trigger: CalcTrigger::Automatic,
103 error_mode: ErrorMode::Continue,
104 cycle_mode: CycleMode::Fail,
105 budget: CalcLimits::default(),
106 priority: 0,
107 debounce_ms: 0,
108 }
109 }
110}
111
112impl EffectiveCalcPolicy {
113 #[must_use]
115 pub fn digest(self) -> PolicyDigest {
116 let mut digest = StableDigest::new();
117 digest.write(match self.trigger {
118 CalcTrigger::Automatic => b"automatic",
119 CalcTrigger::OnDemand => b"on-demand",
120 CalcTrigger::Manual => b"manual",
121 CalcTrigger::Frozen => b"frozen",
122 });
123 digest.write(match self.error_mode {
124 ErrorMode::Continue => b"continue",
125 ErrorMode::FailFast => b"fail-fast",
126 });
127 digest.write(match self.cycle_mode {
128 CycleMode::Fail => b"cycle-fail",
129 CycleMode::Block => b"cycle-block",
130 });
131 digest.write(&self.budget.max_work.to_le_bytes());
132 digest.write(&self.budget.max_observations.to_le_bytes());
133 digest.write(&self.budget.max_query_depth.to_le_bytes());
134 digest.write(&self.budget.max_output.to_le_bytes());
135 digest.write(&self.priority.to_le_bytes());
136 digest.write(&self.debounce_ms.to_le_bytes());
137 PolicyDigest(digest.finish())
138 }
139}
140
141#[derive(Clone, Debug, Default, Eq, PartialEq)]
147pub struct AuthorityPolicyPatch {
148 pub allow: Option<CapabilitySet>,
150 pub deny: CapabilitySet,
152 pub required: CapabilitySet,
154}
155
156#[derive(Clone, Debug, Eq, PartialEq)]
158pub struct EffectiveAuthority {
159 capabilities: CapabilitySet,
160 required: CapabilitySet,
161 denied: CapabilitySet,
162 digest: AuthorityDigest,
163}
164
165impl EffectiveAuthority {
166 #[must_use]
168 pub fn derive(
169 ceiling: &CapabilitySet,
170 patches: impl IntoIterator<Item = AuthorityPolicyPatch>,
171 ) -> Self {
172 let mut capabilities = ceiling.clone();
173 let mut required = CapabilitySet::new();
174 let mut denied = CapabilitySet::new();
175 for patch in patches {
176 if let Some(allow) = patch.allow {
177 capabilities = capabilities.intersect(&allow);
178 }
179 for capability in patch.deny.iter().cloned() {
180 denied.insert(capability);
181 }
182 for capability in patch.required.iter().cloned() {
183 required.insert(capability);
184 }
185 capabilities = without_denied(&capabilities, &denied);
186 }
187 let digest = authority_digest(&capabilities);
188 Self {
189 capabilities,
190 required,
191 denied,
192 digest,
193 }
194 }
195
196 #[must_use]
198 pub fn capabilities(&self) -> &CapabilitySet {
199 &self.capabilities
200 }
201
202 #[must_use]
204 pub fn required(&self) -> &CapabilitySet {
205 &self.required
206 }
207
208 #[must_use]
210 pub fn denied(&self) -> &CapabilitySet {
211 &self.denied
212 }
213
214 #[must_use]
216 pub const fn digest(&self) -> AuthorityDigest {
217 self.digest
218 }
219
220 #[must_use]
222 pub fn first_missing_requirement(&self) -> Option<CapabilityName> {
223 self.required
224 .iter()
225 .find(|capability| !self.capabilities.contains(capability))
226 .cloned()
227 }
228}
229
230#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
232pub struct PolicyDigest(u64);
233
234impl PolicyDigest {
235 pub(super) const fn from_persisted(value: u64) -> Self {
236 Self(value)
237 }
238
239 #[must_use]
241 pub const fn get(self) -> u64 {
242 self.0
243 }
244}
245
246#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
248pub struct AuthorityDigest(u64);
249
250impl AuthorityDigest {
251 pub(super) const fn from_persisted(value: u64) -> Self {
252 Self(value)
253 }
254
255 #[must_use]
257 pub const fn get(self) -> u64 {
258 self.0
259 }
260}
261
262pub(super) fn effective_calc_policy(
263 tree: &CalcPolicyPatch,
264 directories: &std::collections::BTreeMap<String, CalcPolicyPatch>,
265 cells: &std::collections::BTreeMap<String, CalcPolicyPatch>,
266 cell: &str,
267) -> EffectiveCalcPolicy {
268 let mut effective = EffectiveCalcPolicy::default();
269 tree.apply_to(&mut effective);
270 for ancestor in ancestor_directories(cell) {
271 if let Some(patch) = directories.get(&ancestor) {
272 patch.apply_to(&mut effective);
273 }
274 }
275 if let Some(patch) = cells.get(cell) {
276 patch.apply_to(&mut effective);
277 }
278 effective
279}
280
281pub(super) fn effective_authority(
282 ceiling: &CapabilitySet,
283 tree: &AuthorityPolicyPatch,
284 directories: &std::collections::BTreeMap<String, AuthorityPolicyPatch>,
285 cells: &std::collections::BTreeMap<String, AuthorityPolicyPatch>,
286 cell: &str,
287) -> EffectiveAuthority {
288 let mut patches = vec![tree.clone()];
289 patches.extend(
290 ancestor_directories(cell)
291 .into_iter()
292 .filter_map(|ancestor| directories.get(&ancestor).cloned()),
293 );
294 if let Some(patch) = cells.get(cell) {
295 patches.push(patch.clone());
296 }
297 EffectiveAuthority::derive(ceiling, patches)
298}
299
300pub(super) fn effective_codec_policy(
301 tree: &CodecPolicyPatch,
302 directories: &std::collections::BTreeMap<String, CodecPolicyPatch>,
303 cells: &std::collections::BTreeMap<String, CodecPolicyPatch>,
304 cell: &str,
305) -> EffectiveCodecPolicy {
306 let mut patches = vec![tree.clone()];
307 patches.extend(
308 ancestor_directories(cell)
309 .into_iter()
310 .filter_map(|ancestor| directories.get(&ancestor).cloned()),
311 );
312 if let Some(patch) = cells.get(cell) {
313 patches.push(patch.clone());
314 }
315 EffectiveCodecPolicy::derive(patches)
316}
317
318pub(super) fn is_descendant_or_same(directory: &TablePath, cell: &TablePath) -> bool {
319 let directory_segments = directory.segments();
320 let cell_segments = cell.segments();
321 directory_segments.len() <= cell_segments.len()
322 && directory_segments
323 .iter()
324 .zip(cell_segments)
325 .all(|(left, right)| left == right)
326}
327
328fn ancestor_directories(cell: &str) -> Vec<String> {
329 let mut ancestors = vec!["/".to_owned()];
330 let mut segments = cell
331 .trim_start_matches('/')
332 .split('/')
333 .filter(|segment| !segment.is_empty())
334 .collect::<Vec<_>>();
335 segments.pop();
336 let mut current = String::new();
337 for segment in segments {
338 current.push('/');
339 current.push_str(segment);
340 ancestors.push(current.clone());
341 }
342 ancestors
343}
344
345fn without_denied(capabilities: &CapabilitySet, denied: &CapabilitySet) -> CapabilitySet {
346 capabilities
347 .iter()
348 .filter(|capability| !denied.contains(capability))
349 .cloned()
350 .fold(CapabilitySet::new(), CapabilitySet::grant)
351}
352
353fn authority_digest(capabilities: &CapabilitySet) -> AuthorityDigest {
354 let mut digest = StableDigest::new();
355 for capability in capabilities.iter() {
356 digest.write(capability.as_str().as_bytes());
357 digest.write(&[0]);
358 }
359 AuthorityDigest(digest.finish())
360}
361
362struct StableDigest(u64);
363
364impl StableDigest {
365 const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
366 const PRIME: u64 = 0x0000_0100_0000_01b3;
367
368 fn new() -> Self {
369 Self(Self::OFFSET)
370 }
371
372 fn write(&mut self, bytes: &[u8]) {
373 for byte in bytes {
374 self.0 ^= u64::from(*byte);
375 self.0 = self.0.wrapping_mul(Self::PRIME);
376 }
377 }
378
379 fn finish(self) -> u64 {
380 self.0
381 }
382}