use super::store::{CheckoutGrantStore, Held};
use super::{
AdmissionPosture, BindState, BindingExtraCheck, CheckoutGrant, GRANT_SKEW_SECS, LicenceError,
LicenceEvent, MAX_GRANT_SLOTS, Outcome, SignedBinding, SignedGrant, verify_binding_member,
verify_grant,
};
use crate::workload_pkg::codec::hex_decode_exact;
impl CheckoutGrantStore {
pub fn accept_binding(
&self,
signed: &SignedBinding,
posture: AdmissionPosture,
extra: &dyn BindingExtraCheck,
) -> Result<Outcome, LicenceError> {
self.run(|inner, ev| {
if let Some(p) = &inner.poisoned {
return Err(LicenceError::Poisoned(p.clone()));
}
if let Err(e) = posture.check() {
ev.push(LicenceEvent::BindingRefused(e.to_string()));
return Err(e);
}
let local = self.local.get().ok_or(LicenceError::NoLocalMesh)?;
let rec = verify_binding_member(signed, &self.anchors, &local)?;
extra.check(&rec)?;
let mut next = inner.clone();
if let Some(cur) = &inner.binding {
if rec.seq < cur.body.seq {
return Ok(Outcome::Ignored);
}
if rec.seq == cur.body.seq {
if cur.signed.payload == signed.payload {
return Ok(Outcome::Duplicate);
}
ev.push(LicenceEvent::BindingConflict(rec.seq));
return Err(LicenceError::Conflict(rec.seq));
}
if cur.body.grant_pubkey != rec.grant_pubkey {
next.slots.clear(); }
}
let key = rec.grant_pubkey.clone();
next.floors.retain(|k, _| *k == key); let restrictive = rec.state == BindState::Unbound;
next.binding = Some(Held { signed: signed.clone(), body: rec });
next.orphan_reported = false;
if restrictive {
return Ok(self.commit_restrictive(inner, next));
}
self.commit(inner, next)?;
self.note_bound();
Ok(Outcome::Applied)
})
}
pub fn accept_grant(&self, signed: &SignedGrant) -> Result<Outcome, LicenceError> {
self.run(|inner, ev| {
let b = self.binding_in_effect(inner, ev)?;
let pk = hex_decode_exact::<32>(&b.grant_pubkey)
.ok_or_else(|| LicenceError::Malformed("bound grant key".into()))?;
if self.key_revoked(&b.grant_pubkey) {
return Err(LicenceError::KeyRevoked);
}
let local = self.local.get().ok_or(LicenceError::NoLocalMesh)?;
let g = verify_grant(signed, &pk, &local)?;
let eff = self.eff_now(inner, &b.grant_pubkey);
if g.issued_at > eff.saturating_add(GRANT_SKEW_SECS) {
return Err(LicenceError::NotYetValid);
}
let key = (g.cog_id.clone(), g.version.clone());
if !inner.slots.contains_key(&key) && inner.slots.len() >= MAX_GRANT_SLOTS {
return Err(LicenceError::Full);
}
let held = Held { signed: signed.clone(), body: g };
let seq = held.body.seq;
let (cur, conflicted) = match inner.slots.get(&key) {
Some(s) => (s.current.clone(), s.conflicted.contains(&seq)),
None => (None, false),
};
if conflicted {
return Err(LicenceError::Conflict(seq));
}
let mut next = inner.clone();
if let Some(cur) = &cur {
if seq < cur.body.seq {
return Ok(Outcome::Ignored);
}
if seq == cur.body.seq {
if cur.signed.payload == held.signed.payload {
return Ok(Outcome::Duplicate);
}
let wd = [cur, &held].into_iter().find(|h| h.body.is_withdrawal()).cloned();
let slot = next.slots.entry(key).or_default();
slot.conflicted.push(seq);
match wd {
Some(w) => {
slot.current = Some(w);
slot.previous = None;
}
None => slot.current = slot.previous.take(),
}
ev.push(LicenceEvent::GrantConflict {
cog_id: held.body.cog_id,
version: held.body.version,
seq,
});
let _ = self.commit_restrictive(inner, next);
return Err(LicenceError::Conflict(seq));
}
check_union(&cur.body, &held.body)?;
}
let issued = held.body.issued_at;
let restrictive = held.body.is_withdrawal();
let slot = next.slots.entry(key).or_default();
slot.previous = slot.current.take();
slot.current = Some(held);
next.floors.entry(b.grant_pubkey).or_default().note_issued(issued);
if restrictive {
return Ok(self.commit_restrictive(inner, next));
}
self.commit(inner, next)?;
Ok(Outcome::Applied)
})
}
}
fn check_union(cur: &CheckoutGrant, new: &CheckoutGrant) -> Result<(), LicenceError> {
for a in &cur.artifacts {
match new.artifact(&a.arch) {
None => return Err(LicenceError::DropsArch(a.arch.clone())),
Some(n) if n != a => return Err(LicenceError::ChangesArtifact(a.arch.clone())),
Some(_) => {}
}
}
Ok(())
}