use super::bitmap::RemoveBitmap;
use super::indexed::{IndexedAuthorityMap, KvTuple};
use crate::error::RejectReason;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default)]
pub struct Attenuations {
pub identity_context: Option<RemoveBitmap>,
pub invariants: Option<RemoveBitmap>,
pub execution_contract_additions: Vec<KvTuple>,
}
impl Attenuations {
pub fn is_empty(&self) -> bool {
self.identity_context.is_none()
&& self.invariants.is_none()
&& self.execution_contract_additions.is_empty()
}
}
fn apply_removal<T: Clone>(
section: &BTreeMap<u32, T>,
bitmap: Option<&RemoveBitmap>,
section_name: &'static str,
) -> Result<BTreeMap<u32, T>, RejectReason> {
match bitmap {
None => Ok(section.clone()),
Some(bm) => {
bm.validate_against(section.len() as u32, section_name)?;
let removed = bm.indices();
let survivors = section
.iter()
.filter(|(i, _)| !removed.contains(i))
.map(|(_, t)| t.clone());
Ok(survivors.enumerate().map(|(i, t)| (i as u32, t)).collect())
}
}
}
fn apply_additions(
section: &BTreeMap<u32, KvTuple>,
additions: &[KvTuple],
) -> Result<BTreeMap<u32, KvTuple>, RejectReason> {
let mut out = section.clone();
if additions.is_empty() {
return Ok(out);
}
let mut accepted: Vec<KvTuple> = Vec::with_capacity(additions.len());
for (key, value) in additions {
value.validate(key)?;
accepted.push((key.clone(), value.clone()));
}
accepted.sort_by(|a, b| a.0.cmp(&b.0));
for pair in accepted.windows(2) {
if pair[0].0 == pair[1].0 {
return Err(RejectReason::DuplicateAdditionKey(pair[0].0.clone()));
}
}
let base = out.len() as u32;
for (offset, tuple) in accepted.into_iter().enumerate() {
out.insert(base + offset as u32, tuple);
}
Ok(out)
}
pub fn materialize(
predecessor: &IndexedAuthorityMap,
attenuations: &Attenuations,
) -> Result<IndexedAuthorityMap, RejectReason> {
let identity_context = match &predecessor.identity_context {
None => {
if attenuations.identity_context.is_some() {
return Err(RejectReason::BitmapIndexOutOfRange("identity_context"));
}
None
}
Some(section) => {
let applied = apply_removal(
section,
attenuations.identity_context.as_ref(),
"identity_context",
)?;
if applied.is_empty() {
None
} else {
Some(applied)
}
}
};
let invariants = apply_removal(
&predecessor.invariants,
attenuations.invariants.as_ref(),
"invariants",
)?;
let execution_contract = apply_additions(
&predecessor.execution_contract,
&attenuations.execution_contract_additions,
)?;
Ok(IndexedAuthorityMap {
identity_context,
invariants,
execution_contract,
})
}
pub trait AttenuationOrder {
fn attenuates(&self, current: &IndexedAuthorityMap, predecessor: &IndexedAuthorityMap) -> bool;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReferenceProfile;
impl AttenuationOrder for ReferenceProfile {
fn attenuates(&self, current: &IndexedAuthorityMap, predecessor: &IndexedAuthorityMap) -> bool {
let invariants_ok = current
.invariants
.values()
.all(|t| predecessor.contains_invariant(t));
let identity_ok = match (¤t.identity_context, &predecessor.identity_context) {
(None, _) => true,
(Some(_), None) => false,
(Some(cur), Some(pred)) => cur.values().all(|t| pred.values().any(|p| p == t)),
};
let contract_ok = predecessor
.execution_contract
.values()
.all(|t| current.contains_contract_entry(t));
invariants_ok && identity_ok && contract_ok
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::authority::indexed::{InvariantTuple, TupleValue};
use crate::authority::{AuthorityValue, Invariant, LogicalAuthority};
fn walkthrough_pca0() -> IndexedAuthorityMap {
let mut contract = std::collections::BTreeMap::new();
contract.insert("corporation".into(), AuthorityValue::One("ACME".into()));
contract.insert(
"department".into(),
AuthorityValue::One("sensitive-documents".into()),
);
let logical = LogicalAuthority::new(
None,
vec![
Invariant::new(
"documents:read:document-42",
"read",
"documents",
"document-42",
),
Invariant::new("storage:save", "save", "storage", "*"),
],
contract,
);
IndexedAuthorityMap::from_logical(&logical).unwrap()
}
#[test]
fn walkthrough_two_hops_reindex() {
let pca0 = walkthrough_pca0();
assert_eq!(pca0.invariants[&0].0, "documents:read:document-42");
assert_eq!(pca0.invariants[&1].0, "storage:save");
let att1 = Attenuations {
invariants: RemoveBitmap::from_indices(&[0]),
..Default::default()
};
assert_eq!(att1.invariants.as_ref().unwrap().bytes(), &[0x01]);
let pca1 = materialize(&pca0, &att1).unwrap();
assert_eq!(pca1.invariants.len(), 1);
assert_eq!(pca1.invariants[&0].0, "storage:save");
let att2 = Attenuations {
invariants: RemoveBitmap::from_indices(&[0]),
..Default::default()
};
let pca2 = materialize(&pca1, &att2).unwrap();
assert!(pca2.invariants.is_empty());
assert_eq!(pca2.execution_contract.len(), 2);
}
#[test]
fn dropped_authority_cannot_reappear() {
let pca0 = walkthrough_pca0();
let att = Attenuations {
invariants: RemoveBitmap::from_indices(&[0]),
..Default::default()
};
let pca1 = materialize(&pca0, &att).unwrap();
let dropped = InvariantTuple(
"documents:read:document-42".into(),
"read".into(),
"documents".into(),
"document-42".into(),
);
assert!(!pca1.contains_invariant(&dropped));
}
#[test]
fn contract_additions_sorted_appended_and_deduplicated() {
let pca0 = walkthrough_pca0();
let att = Attenuations {
execution_contract_additions: vec![
("region".into(), TupleValue::Text("EU".into())),
("audit".into(), TupleValue::Text("required".into())),
],
..Default::default()
};
let next = materialize(&pca0, &att).unwrap();
assert_eq!(next.execution_contract[&0].0, "corporation");
assert_eq!(next.execution_contract[&1].0, "department");
assert_eq!(next.execution_contract[&2].0, "audit");
assert_eq!(next.execution_contract[&3].0, "region");
let dup = Attenuations {
execution_contract_additions: vec![
("region".into(), TupleValue::Text("EU".into())),
("region".into(), TupleValue::Text("US".into())),
],
..Default::default()
};
assert_eq!(
materialize(&pca0, &dup).unwrap_err(),
RejectReason::DuplicateAdditionKey("region".into())
);
}
#[test]
fn bitmap_out_of_range_rejected() {
let pca0 = walkthrough_pca0();
let att = Attenuations {
invariants: RemoveBitmap::from_indices(&[2]),
..Default::default()
};
assert_eq!(
materialize(&pca0, &att).unwrap_err(),
RejectReason::BitmapIndexOutOfRange("invariants")
);
}
#[test]
fn reference_profile_non_expansion() {
let pca0 = walkthrough_pca0();
let att = Attenuations {
invariants: RemoveBitmap::from_indices(&[0]),
..Default::default()
};
let pca1 = materialize(&pca0, &att).unwrap();
let order = ReferenceProfile;
assert!(order.attenuates(&pca1, &pca0));
assert!(!order.attenuates(&pca0, &pca1));
}
}