use sim_expr_tree_core::{CodecPolicyPatch, EffectiveCodecPolicy};
use sim_kernel::{CapabilityName, CapabilitySet};
use sim_table_core::TablePath;
use super::CalcLimits;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum CalcTrigger {
#[default]
Automatic,
OnDemand,
Manual,
Frozen,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum ErrorMode {
#[default]
Continue,
FailFast,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum CycleMode {
#[default]
Fail,
Block,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct CalcPolicyPatch {
pub trigger: Option<CalcTrigger>,
pub error_mode: Option<ErrorMode>,
pub cycle_mode: Option<CycleMode>,
pub budget: Option<CalcLimits>,
pub priority: Option<i16>,
pub debounce_ms: Option<u32>,
}
impl CalcPolicyPatch {
pub fn apply_to(&self, effective: &mut EffectiveCalcPolicy) {
if let Some(trigger) = self.trigger {
effective.trigger = trigger;
}
if let Some(error_mode) = self.error_mode {
effective.error_mode = error_mode;
}
if let Some(cycle_mode) = self.cycle_mode {
effective.cycle_mode = cycle_mode;
}
if let Some(budget) = self.budget {
effective.budget = budget;
}
if let Some(priority) = self.priority {
effective.priority = priority;
}
if let Some(debounce_ms) = self.debounce_ms {
effective.debounce_ms = debounce_ms;
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EffectiveCalcPolicy {
pub trigger: CalcTrigger,
pub error_mode: ErrorMode,
pub cycle_mode: CycleMode,
pub budget: CalcLimits,
pub priority: i16,
pub debounce_ms: u32,
}
impl Default for EffectiveCalcPolicy {
fn default() -> Self {
Self {
trigger: CalcTrigger::Automatic,
error_mode: ErrorMode::Continue,
cycle_mode: CycleMode::Fail,
budget: CalcLimits::default(),
priority: 0,
debounce_ms: 0,
}
}
}
impl EffectiveCalcPolicy {
#[must_use]
pub fn digest(self) -> PolicyDigest {
let mut digest = StableDigest::new();
digest.write(match self.trigger {
CalcTrigger::Automatic => b"automatic",
CalcTrigger::OnDemand => b"on-demand",
CalcTrigger::Manual => b"manual",
CalcTrigger::Frozen => b"frozen",
});
digest.write(match self.error_mode {
ErrorMode::Continue => b"continue",
ErrorMode::FailFast => b"fail-fast",
});
digest.write(match self.cycle_mode {
CycleMode::Fail => b"cycle-fail",
CycleMode::Block => b"cycle-block",
});
digest.write(&self.budget.max_work.to_le_bytes());
digest.write(&self.budget.max_observations.to_le_bytes());
digest.write(&self.budget.max_query_depth.to_le_bytes());
digest.write(&self.budget.max_output.to_le_bytes());
digest.write(&self.priority.to_le_bytes());
digest.write(&self.debounce_ms.to_le_bytes());
PolicyDigest(digest.finish())
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AuthorityPolicyPatch {
pub allow: Option<CapabilitySet>,
pub deny: CapabilitySet,
pub required: CapabilitySet,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EffectiveAuthority {
capabilities: CapabilitySet,
required: CapabilitySet,
denied: CapabilitySet,
digest: AuthorityDigest,
}
impl EffectiveAuthority {
#[must_use]
pub fn derive(
ceiling: &CapabilitySet,
patches: impl IntoIterator<Item = AuthorityPolicyPatch>,
) -> Self {
let mut capabilities = ceiling.clone();
let mut required = CapabilitySet::new();
let mut denied = CapabilitySet::new();
for patch in patches {
if let Some(allow) = patch.allow {
capabilities = capabilities.intersect(&allow);
}
for capability in patch.deny.iter().cloned() {
denied.insert(capability);
}
for capability in patch.required.iter().cloned() {
required.insert(capability);
}
capabilities = without_denied(&capabilities, &denied);
}
let digest = authority_digest(&capabilities);
Self {
capabilities,
required,
denied,
digest,
}
}
#[must_use]
pub fn capabilities(&self) -> &CapabilitySet {
&self.capabilities
}
#[must_use]
pub fn required(&self) -> &CapabilitySet {
&self.required
}
#[must_use]
pub fn denied(&self) -> &CapabilitySet {
&self.denied
}
#[must_use]
pub const fn digest(&self) -> AuthorityDigest {
self.digest
}
#[must_use]
pub fn first_missing_requirement(&self) -> Option<CapabilityName> {
self.required
.iter()
.find(|capability| !self.capabilities.contains(capability))
.cloned()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PolicyDigest(u64);
impl PolicyDigest {
pub(super) const fn from_persisted(value: u64) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AuthorityDigest(u64);
impl AuthorityDigest {
pub(super) const fn from_persisted(value: u64) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
pub(super) fn effective_calc_policy(
tree: &CalcPolicyPatch,
directories: &std::collections::BTreeMap<String, CalcPolicyPatch>,
cells: &std::collections::BTreeMap<String, CalcPolicyPatch>,
cell: &str,
) -> EffectiveCalcPolicy {
let mut effective = EffectiveCalcPolicy::default();
tree.apply_to(&mut effective);
for ancestor in ancestor_directories(cell) {
if let Some(patch) = directories.get(&ancestor) {
patch.apply_to(&mut effective);
}
}
if let Some(patch) = cells.get(cell) {
patch.apply_to(&mut effective);
}
effective
}
pub(super) fn effective_authority(
ceiling: &CapabilitySet,
tree: &AuthorityPolicyPatch,
directories: &std::collections::BTreeMap<String, AuthorityPolicyPatch>,
cells: &std::collections::BTreeMap<String, AuthorityPolicyPatch>,
cell: &str,
) -> EffectiveAuthority {
let mut patches = vec![tree.clone()];
patches.extend(
ancestor_directories(cell)
.into_iter()
.filter_map(|ancestor| directories.get(&ancestor).cloned()),
);
if let Some(patch) = cells.get(cell) {
patches.push(patch.clone());
}
EffectiveAuthority::derive(ceiling, patches)
}
pub(super) fn effective_codec_policy(
tree: &CodecPolicyPatch,
directories: &std::collections::BTreeMap<String, CodecPolicyPatch>,
cells: &std::collections::BTreeMap<String, CodecPolicyPatch>,
cell: &str,
) -> EffectiveCodecPolicy {
let mut patches = vec![tree.clone()];
patches.extend(
ancestor_directories(cell)
.into_iter()
.filter_map(|ancestor| directories.get(&ancestor).cloned()),
);
if let Some(patch) = cells.get(cell) {
patches.push(patch.clone());
}
EffectiveCodecPolicy::derive(patches)
}
pub(super) fn is_descendant_or_same(directory: &TablePath, cell: &TablePath) -> bool {
let directory_segments = directory.segments();
let cell_segments = cell.segments();
directory_segments.len() <= cell_segments.len()
&& directory_segments
.iter()
.zip(cell_segments)
.all(|(left, right)| left == right)
}
fn ancestor_directories(cell: &str) -> Vec<String> {
let mut ancestors = vec!["/".to_owned()];
let mut segments = cell
.trim_start_matches('/')
.split('/')
.filter(|segment| !segment.is_empty())
.collect::<Vec<_>>();
segments.pop();
let mut current = String::new();
for segment in segments {
current.push('/');
current.push_str(segment);
ancestors.push(current.clone());
}
ancestors
}
fn without_denied(capabilities: &CapabilitySet, denied: &CapabilitySet) -> CapabilitySet {
capabilities
.iter()
.filter(|capability| !denied.contains(capability))
.cloned()
.fold(CapabilitySet::new(), CapabilitySet::grant)
}
fn authority_digest(capabilities: &CapabilitySet) -> AuthorityDigest {
let mut digest = StableDigest::new();
for capability in capabilities.iter() {
digest.write(capability.as_str().as_bytes());
digest.write(&[0]);
}
AuthorityDigest(digest.finish())
}
struct StableDigest(u64);
impl StableDigest {
const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0000_0100_0000_01b3;
fn new() -> Self {
Self(Self::OFFSET)
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 ^= u64::from(*byte);
self.0 = self.0.wrapping_mul(Self::PRIME);
}
}
fn finish(self) -> u64 {
self.0
}
}