use g_math::fixed_point::FixedPoint;
use crate::format::*;
#[derive(Debug, Clone, Copy)]
pub struct AccessBand {
pub lo: FixedPoint,
pub hi: FixedPoint,
}
impl AccessBand {
pub fn open() -> Self {
Self {
lo: FixedPoint::from_int(0),
hi: FixedPoint::from_int(1),
}
}
pub fn closed() -> Self {
Self {
lo: FixedPoint::from_int(1),
hi: FixedPoint::from_int(0),
}
}
pub fn new(lo: FixedPoint, hi: FixedPoint) -> Self {
Self { lo, hi }
}
pub fn from_f64(lo: f64, hi: f64) -> Self {
Self::new(FixedPoint::from_f64(lo), FixedPoint::from_f64(hi))
}
pub fn permits(&self, credential: FixedPoint) -> bool {
credential >= self.lo && credential <= self.hi
}
pub fn narrow(&self, parent: &AccessBand) -> Self {
Self {
lo: if self.lo > parent.lo { self.lo } else { parent.lo },
hi: if self.hi < parent.hi { self.hi } else { parent.hi },
}
}
}
#[derive(Debug, Clone)]
pub struct Credentials {
pub read: FixedPoint,
pub write: FixedPoint,
pub exec: FixedPoint,
pub domain: FixedPoint,
pub classification: FixedPoint,
pub identity: FixedPoint,
}
impl Credentials {
pub fn root() -> Self {
let one = FixedPoint::from_int(1);
Self {
read: one,
write: one,
exec: one,
domain: one,
classification: one,
identity: one,
}
}
pub fn from_groups(groups: &[Credentials]) -> Self {
let mut result = Self {
read: FixedPoint::from_int(0),
write: FixedPoint::from_int(0),
exec: FixedPoint::from_int(0),
domain: FixedPoint::from_int(0),
classification: FixedPoint::from_int(0),
identity: FixedPoint::from_int(0),
};
for g in groups {
if g.read > result.read { result.read = g.read; }
if g.write > result.write { result.write = g.write; }
if g.exec > result.exec { result.exec = g.exec; }
if g.domain > result.domain { result.domain = g.domain; }
if g.classification > result.classification { result.classification = g.classification; }
if g.identity > result.identity { result.identity = g.identity; }
}
result
}
pub fn can_access(&self, bands: &NodeAccessBands) -> bool {
bands.read.permits(self.read)
&& bands.write.permits(self.write)
&& bands.exec.permits(self.exec)
&& bands.domain.permits(self.domain)
&& bands.classification.permits(self.classification)
&& bands.identity.permits(self.identity)
}
pub fn can_read(&self, bands: &NodeAccessBands) -> bool {
bands.read.permits(self.read)
&& bands.domain.permits(self.domain)
&& bands.classification.permits(self.classification)
&& bands.identity.permits(self.identity)
}
}
#[derive(Debug, Clone)]
pub struct NodeAccessBands {
pub read: AccessBand,
pub write: AccessBand,
pub exec: AccessBand,
pub domain: AccessBand,
pub classification: AccessBand,
pub identity: AccessBand,
}
impl NodeAccessBands {
pub fn public() -> Self {
Self {
read: AccessBand::open(),
write: AccessBand::open(),
exec: AccessBand::open(),
domain: AccessBand::open(),
classification: AccessBand::open(),
identity: AccessBand::open(),
}
}
pub fn from_semantic_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 12 * 16 {
return None;
}
Some(Self {
read: AccessBand {
lo: fp_from_bytes(&bytes[DIM_READ_LO * 16..]),
hi: fp_from_bytes(&bytes[DIM_READ_HI * 16..]),
},
write: AccessBand {
lo: fp_from_bytes(&bytes[DIM_WRITE_LO * 16..]),
hi: fp_from_bytes(&bytes[DIM_WRITE_HI * 16..]),
},
exec: AccessBand {
lo: fp_from_bytes(&bytes[DIM_EXEC_LO * 16..]),
hi: fp_from_bytes(&bytes[DIM_EXEC_HI * 16..]),
},
domain: AccessBand {
lo: fp_from_bytes(&bytes[DIM_DOMAIN_LO * 16..]),
hi: fp_from_bytes(&bytes[DIM_DOMAIN_HI * 16..]),
},
classification: AccessBand {
lo: fp_from_bytes(&bytes[DIM_CLASS_LO * 16..]),
hi: fp_from_bytes(&bytes[DIM_CLASS_HI * 16..]),
},
identity: AccessBand {
lo: fp_from_bytes(&bytes[DIM_IDENTITY_LO * 16..]),
hi: fp_from_bytes(&bytes[DIM_IDENTITY_HI * 16..]),
},
})
}
pub fn to_semantic_bytes(&self, total_semantic_dims: usize) -> Vec<u8> {
let mut bytes = vec![0u8; total_semantic_dims * 16];
if total_semantic_dims < 12 {
return bytes;
}
fp_to_bytes(&mut bytes[DIM_READ_LO * 16..], self.read.lo);
fp_to_bytes(&mut bytes[DIM_READ_HI * 16..], self.read.hi);
fp_to_bytes(&mut bytes[DIM_WRITE_LO * 16..], self.write.lo);
fp_to_bytes(&mut bytes[DIM_WRITE_HI * 16..], self.write.hi);
fp_to_bytes(&mut bytes[DIM_EXEC_LO * 16..], self.exec.lo);
fp_to_bytes(&mut bytes[DIM_EXEC_HI * 16..], self.exec.hi);
fp_to_bytes(&mut bytes[DIM_DOMAIN_LO * 16..], self.domain.lo);
fp_to_bytes(&mut bytes[DIM_DOMAIN_HI * 16..], self.domain.hi);
fp_to_bytes(&mut bytes[DIM_CLASS_LO * 16..], self.classification.lo);
fp_to_bytes(&mut bytes[DIM_CLASS_HI * 16..], self.classification.hi);
fp_to_bytes(&mut bytes[DIM_IDENTITY_LO * 16..], self.identity.lo);
fp_to_bytes(&mut bytes[DIM_IDENTITY_HI * 16..], self.identity.hi);
bytes
}
pub fn narrow(&self, parent: &NodeAccessBands) -> Self {
Self {
read: self.read.narrow(&parent.read),
write: self.write.narrow(&parent.write),
exec: self.exec.narrow(&parent.exec),
domain: self.domain.narrow(&parent.domain),
classification: self.classification.narrow(&parent.classification),
identity: self.identity.narrow(&parent.identity),
}
}
}
fn fp_from_bytes(bytes: &[u8]) -> FixedPoint {
let raw = i128::from_le_bytes(bytes[..16].try_into().unwrap());
FixedPoint::from_raw(raw)
}
fn fp_to_bytes(dest: &mut [u8], fp: FixedPoint) {
let raw = fp.raw();
dest[..16].copy_from_slice(&raw.to_le_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_access_band_permits() {
let band = AccessBand::from_f64(0.5, 1.0);
assert!(band.permits(FixedPoint::from_f64(0.7)));
assert!(band.permits(FixedPoint::from_f64(0.5)));
assert!(band.permits(FixedPoint::from_f64(1.0)));
assert!(!band.permits(FixedPoint::from_f64(0.3)));
}
#[test]
fn test_open_band_permits_all() {
let band = AccessBand::open();
assert!(band.permits(FixedPoint::from_f64(0.0)));
assert!(band.permits(FixedPoint::from_f64(0.5)));
assert!(band.permits(FixedPoint::from_f64(1.0)));
}
#[test]
fn test_closed_band_permits_none() {
let band = AccessBand::closed();
assert!(!band.permits(FixedPoint::from_f64(0.5)));
assert!(!band.permits(FixedPoint::from_f64(1.0)));
assert!(!band.permits(FixedPoint::from_f64(0.0)));
}
#[test]
fn test_closed_band_survives_narrow_and_roundtrip() {
let closed = AccessBand::closed();
let narrowed = AccessBand::open().narrow(&closed);
assert!(!narrowed.permits(FixedPoint::from_f64(0.0)));
assert!(!narrowed.permits(FixedPoint::from_f64(0.5)));
let mut node = NodeAccessBands::public();
node.read = AccessBand::closed();
let bytes = node.to_semantic_bytes(12);
let restored = NodeAccessBands::from_semantic_bytes(&bytes).unwrap();
assert!(!restored.read.permits(FixedPoint::from_f64(0.0)));
assert!(!restored.read.permits(FixedPoint::from_f64(1.0)));
}
#[test]
fn test_narrow_restricts() {
let parent = AccessBand::from_f64(0.3, 0.9);
let child = AccessBand::from_f64(0.1, 1.0); let narrowed = child.narrow(&parent);
assert!(!narrowed.permits(FixedPoint::from_f64(0.2)));
assert!(narrowed.permits(FixedPoint::from_f64(0.5)));
assert!(!narrowed.permits(FixedPoint::from_f64(0.95)));
}
#[test]
fn test_credentials_root_accesses_everything() {
let creds = Credentials::root();
let bands = NodeAccessBands::public();
assert!(creds.can_access(&bands));
}
#[test]
fn test_credentials_from_groups() {
let eng = Credentials {
read: FixedPoint::from_f64(0.5),
write: FixedPoint::from_f64(0.3),
exec: FixedPoint::from_f64(0.5),
domain: FixedPoint::from_f64(0.5),
classification: FixedPoint::from_f64(0.3),
identity: FixedPoint::from_f64(0.42),
};
let security = Credentials {
read: FixedPoint::from_f64(0.7),
write: FixedPoint::from_f64(0.7),
exec: FixedPoint::from_f64(0.7),
domain: FixedPoint::from_f64(0.3),
classification: FixedPoint::from_f64(0.8),
identity: FixedPoint::from_f64(0.42),
};
let combined = Credentials::from_groups(&[eng, security]);
assert_eq!(combined.read.to_f64(), FixedPoint::from_f64(0.7).to_f64());
assert_eq!(combined.domain.to_f64(), FixedPoint::from_f64(0.5).to_f64());
assert_eq!(combined.classification.to_f64(), FixedPoint::from_f64(0.8).to_f64());
}
#[test]
fn test_node_access_bands_inheritance() {
let parent = NodeAccessBands {
read: AccessBand::from_f64(0.5, 1.0),
write: AccessBand::from_f64(0.8, 1.0),
exec: AccessBand::open(),
domain: AccessBand::from_f64(0.3, 0.7),
classification: AccessBand::from_f64(0.6, 1.0),
identity: AccessBand::open(),
};
let child = NodeAccessBands::public();
let effective = child.narrow(&parent);
assert!(!effective.read.permits(FixedPoint::from_f64(0.3)));
assert!(effective.read.permits(FixedPoint::from_f64(0.7)));
}
}