use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use liminal::protocol::DisconnectReason;
use crate::auth_lys::HotAction;
pub const MAX_LOG_IDENTITY_LEN: usize = 256;
pub const MAX_GRANT_ID_LEN: usize = 128;
pub const MAX_BOUND_RIGHTS: usize = 64;
pub const MAX_CHAINS_PER_RIGHT: usize = 8;
pub const MAX_CHAIN_LEN: usize = 16;
pub const MAX_RIGHT_PREFIX_LEN: usize = 256;
pub const MAX_DECISIONS_PER_EVENT: usize = 65_536;
pub const EVIDENCE_RETAINED: usize = 1_024;
pub const MAX_DIAGNOSTIC_EVENTS: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, thiserror::Error)]
pub enum RevocationRefusal {
#[error("grant_revoked")]
GrantRevoked,
#[error("revocation_binding_missing")]
BindingMissing,
#[error("revocation_revision_unavailable")]
RevisionUnavailable,
#[error("revocation_stream_unavailable")]
StreamUnavailable,
#[error("revocation_stream_gap")]
StreamGap,
#[error("revocation_log_reset")]
LogReset,
#[error("revocation_protocol_unsupported")]
ProtocolUnsupported,
}
impl RevocationRefusal {
pub const ALL: [Self; 7] = [
Self::GrantRevoked,
Self::BindingMissing,
Self::RevisionUnavailable,
Self::StreamUnavailable,
Self::StreamGap,
Self::LogReset,
Self::ProtocolUnsupported,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::GrantRevoked => "grant_revoked",
Self::BindingMissing => "revocation_binding_missing",
Self::RevisionUnavailable => "revocation_revision_unavailable",
Self::StreamUnavailable => "revocation_stream_unavailable",
Self::StreamGap => "revocation_stream_gap",
Self::LogReset => "revocation_log_reset",
Self::ProtocolUnsupported => "revocation_protocol_unsupported",
}
}
#[must_use]
pub const fn close_reason(self) -> Option<DisconnectReason> {
match self {
Self::GrantRevoked => Some(DisconnectReason::GrantRevoked),
Self::LogReset => Some(DisconnectReason::RevocationLogReset),
Self::BindingMissing
| Self::RevisionUnavailable
| Self::StreamUnavailable
| Self::StreamGap
| Self::ProtocolUnsupported => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[error("{what} is empty or longer than its bound")]
pub struct InvalidGrantInput {
pub what: &'static str,
}
fn bounded(value: &str, max: usize, what: &'static str) -> Result<String, InvalidGrantInput> {
if value.is_empty() || value.len() > max {
Err(InvalidGrantInput { what })
} else {
Ok(value.to_owned())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GrantLogId {
identity: String,
epoch: u64,
}
impl GrantLogId {
pub fn new(identity: &str, epoch: u64) -> Result<Self, InvalidGrantInput> {
Ok(Self {
identity: bounded(identity, MAX_LOG_IDENTITY_LEN, "grant log identity")?,
epoch,
})
}
#[must_use]
pub fn identity(&self) -> &str {
&self.identity
}
#[must_use]
pub const fn epoch(&self) -> u64 {
self.epoch
}
}
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GrantId(String);
impl GrantId {
pub fn new(id: &str) -> Result<Self, InvalidGrantInput> {
bounded(id, MAX_GRANT_ID_LEN, "grant id").map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Debug for GrantId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "GrantId({} bytes)", self.0.len())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Revision(u64);
impl Revision {
#[must_use]
pub const fn from_receipt_index(index: u64) -> Option<Self> {
match index.checked_add(1) {
Some(revision) => Some(Self(revision)),
None => None,
}
}
#[must_use]
pub const fn new(revision: u64) -> Self {
Self(revision)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
const fn next(self) -> Option<Self> {
match self.0.checked_add(1) {
Some(next) => Some(Self(next)),
None => None,
}
}
}
const fn missing_between(from: Revision, to: Revision) -> Option<u64> {
match to.0.checked_sub(from.0) {
Some(distance) => distance.checked_sub(1),
None => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RightAction {
Connect,
Hot(HotAction),
}
#[derive(Clone, PartialEq, Eq)]
pub struct BoundRightClaim {
pub action: RightAction,
pub prefix: String,
pub chains: Vec<Vec<GrantId>>,
}
impl fmt::Debug for BoundRightClaim {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("BoundRightClaim")
.field("action", &self.action)
.field("prefix", &format_args!("{} bytes", self.prefix.len()))
.field("chains", &self.chains.len())
.finish()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Digest32(pub [u8; 32]);
impl fmt::Debug for Digest32 {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Digest32(32 bytes)")
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct AuthenticatedBinding {
pub issuer: Digest32,
pub audience: String,
pub holder: Vec<u8>,
pub pass_digest: Digest32,
pub log: GrantLogId,
pub required: Revision,
pub rights: Vec<BoundRightClaim>,
}
impl fmt::Debug for AuthenticatedBinding {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AuthenticatedBinding")
.field("audience_bytes", &self.audience.len())
.field("holder_bytes", &self.holder.len())
.field("log", &self.log)
.field("required", &self.required)
.field("rights", &self.rights.len())
.finish_non_exhaustive()
}
}
pub trait GrantBindingSource: Send + Sync + fmt::Debug {
fn pass_digest(&self, credential: &[u8]) -> Result<Digest32, RevocationRefusal>;
fn binding_for(&self, credential: &[u8]) -> Result<AuthenticatedBinding, RevocationRefusal>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct UnpublishedGrantBinding;
impl GrantBindingSource for UnpublishedGrantBinding {
fn pass_digest(&self, _credential: &[u8]) -> Result<Digest32, RevocationRefusal> {
Err(RevocationRefusal::BindingMissing)
}
fn binding_for(&self, _credential: &[u8]) -> Result<AuthenticatedBinding, RevocationRefusal> {
Err(RevocationRefusal::BindingMissing)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DecisionReply {
pub log: GrantLogId,
pub revision: Revision,
pub decisions: Vec<(GrantId, bool)>,
}
pub trait GrantDecisionSource: Send + Sync + fmt::Debug {
fn decide(
&self,
log: &GrantLogId,
grants: &[GrantId],
at_least: Revision,
) -> Result<DecisionReply, RevocationRefusal>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct UnpublishedGrantDecisions;
impl GrantDecisionSource for UnpublishedGrantDecisions {
fn decide(
&self,
_log: &GrantLogId,
_grants: &[GrantId],
_at_least: Revision,
) -> Result<DecisionReply, RevocationRefusal> {
Err(RevocationRefusal::RevisionUnavailable)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RevocationPolicy {
pub log: GrantLogId,
pub audience: String,
pub issuer: Digest32,
}
#[derive(Clone, PartialEq, Eq)]
struct BoundRight {
action: RightAction,
prefix: String,
chains: Vec<Vec<GrantId>>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct GrantDecisionBinding {
log: GrantLogId,
required: Revision,
rights: Vec<BoundRight>,
admitting: usize,
}
impl GrantDecisionBinding {
#[must_use]
pub const fn log(&self) -> &GrantLogId {
&self.log
}
#[must_use]
pub const fn required(&self) -> Revision {
self.required
}
#[must_use]
pub fn right_count(&self) -> usize {
self.rights.len()
}
fn grants(&self) -> impl Iterator<Item = &GrantId> {
self.rights
.iter()
.flat_map(|right| right.chains.iter().flatten())
}
}
impl fmt::Debug for GrantDecisionBinding {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("GrantDecisionBinding")
.field("log", &self.log)
.field("required", &self.required)
.field("rights", &self.rights.len())
.finish_non_exhaustive()
}
}
pub fn verify_binding(
policy: &RevocationPolicy,
source: &dyn GrantBindingSource,
credential: &[u8],
holder: &[u8],
) -> Result<GrantDecisionBinding, RevocationRefusal> {
let claimed = source.binding_for(credential)?;
let digest = source.pass_digest(credential)?;
let mismatch = if claimed.issuer != policy.issuer {
Some("issuer")
} else if claimed.audience != policy.audience {
Some("audience")
} else if claimed.holder.as_slice() != holder {
Some("holder")
} else if claimed.pass_digest != digest {
Some("pass_digest")
} else {
None
};
if let Some(field) = mismatch {
tracing::warn!(
field,
"grant-decision binding does not match the presented credential; refused"
);
return Err(RevocationRefusal::BindingMissing);
}
bound_rights(claimed)
}
fn bound_rights(claimed: AuthenticatedBinding) -> Result<GrantDecisionBinding, RevocationRefusal> {
let refuse = |what: &'static str| {
tracing::warn!(
what,
"grant-decision binding is over its bound or incomplete"
);
RevocationRefusal::BindingMissing
};
if claimed.rights.is_empty() || claimed.rights.len() > MAX_BOUND_RIGHTS {
return Err(refuse("rights"));
}
let mut rights = Vec::with_capacity(claimed.rights.len());
for right in claimed.rights {
if right.prefix.len() > MAX_RIGHT_PREFIX_LEN {
return Err(refuse("prefix"));
}
if right.chains.is_empty() || right.chains.len() > MAX_CHAINS_PER_RIGHT {
return Err(refuse("chains"));
}
if right
.chains
.iter()
.any(|chain| chain.is_empty() || chain.len() > MAX_CHAIN_LEN)
{
return Err(refuse("chain"));
}
if matches!(right.action, RightAction::Hot(_)) && right.prefix.is_empty() {
return Err(refuse("hot right without a prefix"));
}
rights.push(BoundRight {
action: right.action,
prefix: right.prefix,
chains: right.chains,
});
}
let admitting = rights
.iter()
.position(|right| right.action == RightAction::Connect)
.ok_or_else(|| refuse("admitting right"))?;
Ok(GrantDecisionBinding {
log: claimed.log,
required: claimed.required,
rights,
admitting,
})
}
const CLOSE_NONE: u8 = 0;
const CLOSE_GRANT_REVOKED: u8 = 1;
const CLOSE_LOG_RESET: u8 = 2;
const fn close_code(reason: RevocationRefusal) -> u8 {
match reason {
RevocationRefusal::LogReset => CLOSE_LOG_RESET,
RevocationRefusal::GrantRevoked
| RevocationRefusal::BindingMissing
| RevocationRefusal::RevisionUnavailable
| RevocationRefusal::StreamUnavailable
| RevocationRefusal::StreamGap
| RevocationRefusal::ProtocolUnsupported => CLOSE_GRANT_REVOKED,
}
}
#[derive(Debug)]
struct LiveRights {
lost: Box<[AtomicBool]>,
close: AtomicU8,
changes: AtomicU64,
}
#[derive(Debug, Default)]
struct HotCounts {
allowed: AtomicU64,
refused: AtomicU64,
}
#[derive(Clone)]
pub struct ConnectionGrants {
pid: u64,
binding: Arc<GrantDecisionBinding>,
live: Arc<LiveRights>,
hot: Arc<HotCounts>,
}
impl fmt::Debug for ConnectionGrants {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ConnectionGrants")
.field("pid", &self.pid)
.field("binding", &self.binding)
.finish_non_exhaustive()
}
}
impl ConnectionGrants {
#[must_use]
pub const fn pid(&self) -> u64 {
self.pid
}
#[must_use]
pub fn binding(&self) -> &GrantDecisionBinding {
&self.binding
}
pub fn allows(&self, action: HotAction, subject: &str) -> Result<(), RevocationRefusal> {
let decided = self.decide(action, subject);
let counter = if decided.is_ok() {
&self.hot.allowed
} else {
&self.hot.refused
};
counter.fetch_add(1, Ordering::Relaxed);
decided
}
#[must_use]
pub fn permits(&self, action: HotAction, subject: &str) -> bool {
self.decide(action, subject).is_ok()
}
fn decide(&self, action: HotAction, subject: &str) -> Result<(), RevocationRefusal> {
if let Some(reason) = self.close_pending() {
return Err(reason);
}
let mut covered = false;
for (right, lost) in self.binding.rights.iter().zip(self.live.lost.iter()) {
if right.action == RightAction::Hot(action) && subject.starts_with(&right.prefix) {
covered = true;
if !lost.load(Ordering::Acquire) {
return Ok(());
}
}
}
Err(if covered {
RevocationRefusal::GrantRevoked
} else {
RevocationRefusal::BindingMissing
})
}
#[must_use]
pub fn close_pending(&self) -> Option<RevocationRefusal> {
match self.live.close.load(Ordering::Acquire) {
CLOSE_NONE => None,
CLOSE_LOG_RESET => Some(RevocationRefusal::LogReset),
_ => Some(RevocationRefusal::GrantRevoked),
}
}
#[must_use]
pub fn changes(&self) -> u64 {
self.live.changes.load(Ordering::Acquire)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Readiness {
NotReady,
Ready,
Gap,
Unavailable,
Reset,
Terminal,
}
impl Readiness {
const fn refusal(self) -> Option<RevocationRefusal> {
match self {
Self::Ready => None,
Self::NotReady | Self::Unavailable | Self::Terminal => {
Some(RevocationRefusal::StreamUnavailable)
}
Self::Gap => Some(RevocationRefusal::StreamGap),
Self::Reset => Some(RevocationRefusal::LogReset),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GrantFeedChange {
Issue(GrantId),
Revoke(GrantId),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GrantFeedEvent {
Baseline {
log: GrantLogId,
through: Revision,
decisions: Vec<(GrantId, bool)>,
},
Change {
log: GrantLogId,
revision: Revision,
change: GrantFeedChange,
evidence: Digest32,
},
Watermark {
log: GrantLogId,
through: Revision,
},
Ready {
log: GrantLogId,
through: Revision,
},
Lost,
Reset {
from: GrantLogId,
to: GrantLogId,
},
Invalid {
reason: &'static str,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RevocationEffect {
Withdraw {
pid: u64,
right: usize,
},
Close {
pid: u64,
reason: RevocationRefusal,
},
}
impl RevocationEffect {
#[must_use]
pub const fn pid(self) -> u64 {
match self {
Self::Withdraw { pid, .. } | Self::Close { pid, .. } => pid,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Applied {
Baseline,
Change,
Duplicate,
Watermark,
Gap,
Ready,
Lost,
Reset,
Refused(&'static str),
Terminal(&'static str),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ApplyOutcome {
pub applied: Applied,
pub effects: Vec<RevocationEffect>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CloseWrite {
Flushed,
Residue,
NotQueued,
TransportFailed,
NoStream,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct RevocationCounts {
pub observed_revocations: u64,
pub applied_revisions: u64,
pub duplicate_revisions: u64,
pub watermark_advances: u64,
pub refused_events: u64,
pub affected_visits: u64,
pub resume_rechecks: u64,
pub admissions_committed: u64,
pub admissions_refused: u64,
pub binding_replacements_refused: u64,
pub withdrawals: u64,
pub closes_decided: u64,
pub close_publication_refused: u64,
pub closes_attempted: u64,
pub closes_flushed: u64,
pub close_transport_failures: u64,
pub cleanups: u64,
pub decisions_pruned: u64,
pub gap_transitions: u64,
pub loss_transitions: u64,
pub reset_transitions: u64,
pub terminal_errors: u64,
pub decision_asks: u64,
pub stale_replies_refused: u64,
pub stale_overwrites_prevented: u64,
pub rows_read: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GapLength {
None,
Known(u64),
Unknown,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Transition {
pub readiness: Readiness,
pub reason: Option<RevocationRefusal>,
pub applied: Option<Revision>,
pub missing: GapLength,
pub refused_admissions: u64,
pub rechecks: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("revocation_authority_poisoned")]
pub struct AuthorityPoisoned;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RevocationDiagnostics {
pub log: GrantLogId,
pub readiness: Readiness,
pub reason: Option<RevocationRefusal>,
pub applied: Option<Revision>,
pub high_water: Option<Revision>,
pub missing: GapLength,
pub resume_generation: u64,
pub settled_generation: u64,
pub bound_connections: usize,
pub retained_decisions: usize,
pub index_entries: usize,
pub inflight_asks: u64,
pub counts: RevocationCounts,
pub hot_allowed: u64,
pub hot_refused: u64,
pub transitions: Vec<Transition>,
pub transitions_dropped: u64,
}
#[derive(Clone, Copy, Debug)]
struct Decision {
revoked: bool,
at: Revision,
baseline: bool,
}
#[derive(Debug)]
struct Bound {
binding: Arc<GrantDecisionBinding>,
live: Arc<LiveRights>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum RightState {
Allowed,
Lost,
Unknown,
}
#[derive(Debug)]
struct Inner {
log: GrantLogId,
readiness: Readiness,
applied: Option<Revision>,
high_water: Option<Revision>,
pending_resume: bool,
resume_generation: u64,
settled_generation: u64,
decisions: BTreeMap<GrantId, Decision>,
evidence: VecDeque<(Revision, Digest32)>,
connections: BTreeMap<u64, Bound>,
index: BTreeMap<GrantId, BTreeSet<u64>>,
orphans: BTreeSet<GrantId>,
inflight_asks: u64,
counts: RevocationCounts,
transitions: VecDeque<Transition>,
transitions_dropped: u64,
announce: Option<Transition>,
}
const fn bump(counter: &mut u64) {
*counter = counter.saturating_add(1);
}
impl Inner {
fn new(log: GrantLogId) -> Self {
Self {
log,
readiness: Readiness::NotReady,
applied: None,
high_water: None,
pending_resume: false,
resume_generation: 0,
settled_generation: 0,
decisions: BTreeMap::new(),
evidence: VecDeque::new(),
connections: BTreeMap::new(),
index: BTreeMap::new(),
orphans: BTreeSet::new(),
inflight_asks: 0,
counts: RevocationCounts::default(),
transitions: VecDeque::new(),
transitions_dropped: 0,
announce: None,
}
}
fn missing(&self) -> GapLength {
if self.readiness == Readiness::Ready {
return GapLength::None;
}
match (self.applied, self.high_water) {
(Some(applied), Some(high)) => match missing_between(applied, high) {
Some(missing) if missing > 0 => GapLength::Known(missing),
_ => GapLength::None,
},
_ => GapLength::Unknown,
}
}
fn transition(&mut self, readiness: Readiness) {
if self.readiness == readiness {
return;
}
self.readiness = readiness;
match readiness {
Readiness::Gap => bump(&mut self.counts.gap_transitions),
Readiness::Unavailable => bump(&mut self.counts.loss_transitions),
Readiness::Reset => bump(&mut self.counts.reset_transitions),
Readiness::Terminal => bump(&mut self.counts.terminal_errors),
Readiness::NotReady | Readiness::Ready => {}
}
let transition = Transition {
readiness,
reason: readiness.refusal(),
applied: self.applied,
missing: self.missing(),
refused_admissions: self.counts.admissions_refused,
rechecks: self.counts.resume_rechecks,
};
if self.transitions.len() >= MAX_DIAGNOSTIC_EVENTS {
self.transitions.pop_front();
bump(&mut self.transitions_dropped);
}
self.transitions.push_back(transition);
self.announce = Some(transition);
}
fn precheck(&self, binding: &GrantDecisionBinding) -> Result<(), RevocationRefusal> {
if let Some(refusal) = self.readiness.refusal() {
return Err(refusal);
}
if binding.log != self.log {
return Err(RevocationRefusal::LogReset);
}
if self
.applied
.is_none_or(|applied| applied < binding.required)
{
return Err(RevocationRefusal::RevisionUnavailable);
}
Ok(())
}
fn missing_grants(&self, binding: &GrantDecisionBinding) -> Vec<GrantId> {
let unknown: BTreeSet<&GrantId> = binding
.grants()
.filter(|grant| !self.decisions.contains_key(*grant))
.collect();
unknown.into_iter().cloned().collect()
}
fn right_state(&self, right: &BoundRight) -> RightState {
let mut unknown = false;
for chain in &right.chains {
let mut chain_state = RightState::Allowed;
for grant in chain {
match self.decisions.get(grant) {
Some(decision) if decision.revoked => {
chain_state = RightState::Lost;
break;
}
Some(_) => {}
None => chain_state = RightState::Unknown,
}
}
match chain_state {
RightState::Allowed => return RightState::Allowed,
RightState::Unknown => unknown = true,
RightState::Lost => {}
}
}
if unknown {
RightState::Unknown
} else {
RightState::Lost
}
}
fn merge_decision(&mut self, grant: &GrantId, revoked: bool, at: Revision, baseline: bool) {
match self.decisions.get_mut(grant) {
Some(existing) if existing.revoked => {
if !revoked {
bump(&mut self.counts.stale_overwrites_prevented);
}
existing.baseline |= baseline;
}
Some(existing) if existing.at >= at && !revoked => {
existing.baseline |= baseline;
}
Some(existing) => {
existing.revoked = revoked;
existing.at = at;
existing.baseline |= baseline;
}
None => {
self.decisions.insert(
grant.clone(),
Decision {
revoked,
at,
baseline,
},
);
if !baseline && !self.index.contains_key(grant) {
self.orphans.insert(grant.clone());
}
}
}
}
fn install_reply(
&mut self,
reply: &DecisionReply,
at_least: Revision,
) -> Result<(), RevocationRefusal> {
if reply.log != self.log {
return Err(RevocationRefusal::LogReset);
}
if reply.revision < at_least || reply.decisions.len() > MAX_DECISIONS_PER_EVENT {
bump(&mut self.counts.stale_replies_refused);
return Err(RevocationRefusal::RevisionUnavailable);
}
for (grant, revoked) in &reply.decisions {
bump(&mut self.counts.rows_read);
self.merge_decision(grant, *revoked, reply.revision, false);
}
Ok(())
}
fn bind(
&mut self,
pid: u64,
binding: &Arc<GrantDecisionBinding>,
hot: &Arc<HotCounts>,
) -> Result<ConnectionGrants, RevocationRefusal> {
self.precheck(binding)?;
if self.connections.contains_key(&pid) {
bump(&mut self.counts.binding_replacements_refused);
tracing::warn!(
connection_pid = pid,
"a second grant-decision binding was offered; the first is kept"
);
return Err(RevocationRefusal::BindingMissing);
}
let mut lost = Vec::with_capacity(binding.rights.len());
for (index, right) in binding.rights.iter().enumerate() {
match (self.right_state(right), index == binding.admitting) {
(RightState::Allowed, _) => lost.push(AtomicBool::new(false)),
(RightState::Lost, true) => return Err(RevocationRefusal::GrantRevoked),
(RightState::Lost, false) => lost.push(AtomicBool::new(true)),
(RightState::Unknown, _) => return Err(RevocationRefusal::RevisionUnavailable),
}
}
let live = Arc::new(LiveRights {
lost: lost.into_boxed_slice(),
close: AtomicU8::new(CLOSE_NONE),
changes: AtomicU64::new(0),
});
for grant in binding.grants() {
self.index.entry(grant.clone()).or_default().insert(pid);
self.orphans.remove(grant);
}
self.connections.insert(
pid,
Bound {
binding: Arc::clone(binding),
live: Arc::clone(&live),
},
);
bump(&mut self.counts.admissions_committed);
Ok(ConnectionGrants {
pid,
binding: Arc::clone(binding),
live,
hot: Arc::clone(hot),
})
}
fn evaluate(&mut self, pid: u64, effects: &mut Vec<RevocationEffect>) {
let Some(bound) = self.connections.get(&pid) else {
return;
};
let binding = Arc::clone(&bound.binding);
let live = Arc::clone(&bound.live);
if live.close.load(Ordering::Acquire) != CLOSE_NONE {
return;
}
if binding.log != self.log {
self.close(pid, &live, RevocationRefusal::LogReset, effects);
return;
}
for (index, (right, lost)) in binding.rights.iter().zip(live.lost.iter()).enumerate() {
if lost.load(Ordering::Acquire) || self.right_state(right) == RightState::Allowed {
continue;
}
lost.store(true, Ordering::Release);
live.changes.fetch_add(1, Ordering::AcqRel);
if index == binding.admitting {
self.close(pid, &live, RevocationRefusal::GrantRevoked, effects);
return;
}
bump(&mut self.counts.withdrawals);
effects.push(RevocationEffect::Withdraw { pid, right: index });
}
}
fn close(
&mut self,
pid: u64,
live: &LiveRights,
reason: RevocationRefusal,
effects: &mut Vec<RevocationEffect>,
) {
live.close.store(close_code(reason), Ordering::Release);
live.changes.fetch_add(1, Ordering::AcqRel);
bump(&mut self.counts.closes_decided);
effects.push(RevocationEffect::Close { pid, reason });
}
fn remember_evidence(&mut self, revision: Revision, evidence: Digest32) {
if self.evidence.len() >= EVIDENCE_RETAINED {
self.evidence.pop_front();
}
self.evidence.push_back((revision, evidence));
}
fn raise_high_water(&mut self, revision: Revision) {
if self.high_water.is_none_or(|high| high < revision) {
self.high_water = Some(revision);
}
}
fn apply_change(
&mut self,
revision: Revision,
change: &GrantFeedChange,
evidence: Digest32,
effects: &mut Vec<RevocationEffect>,
) -> Applied {
self.raise_high_water(revision);
let Some(applied) = self.applied else {
return Applied::Refused("change before baseline");
};
if revision <= applied {
let conflicting = self
.evidence
.iter()
.any(|(seen, digest)| *seen == revision && *digest != evidence);
if conflicting {
self.transition(Readiness::Terminal);
return Applied::Terminal("conflicting_evidence");
}
bump(&mut self.counts.duplicate_revisions);
return Applied::Duplicate;
}
if applied.next() != Some(revision) {
self.pending_resume = true;
self.transition(Readiness::Gap);
return Applied::Gap;
}
self.applied = Some(revision);
self.remember_evidence(revision, evidence);
bump(&mut self.counts.applied_revisions);
match change {
GrantFeedChange::Issue(grant) => {
if self.decisions.contains_key(grant) || self.index.contains_key(grant) {
self.merge_decision(grant, false, revision, false);
}
}
GrantFeedChange::Revoke(grant) => {
bump(&mut self.counts.observed_revocations);
let referenced = self.index.contains_key(grant);
if referenced || self.inflight_asks > 0 || self.decisions.contains_key(grant) {
self.merge_decision(grant, true, revision, false);
}
let affected: Vec<u64> = self
.index
.get(grant)
.map(|pids| pids.iter().copied().collect())
.unwrap_or_default();
for pid in affected {
bump(&mut self.counts.affected_visits);
self.evaluate(pid, effects);
}
}
}
Applied::Change
}
fn apply_ready(&mut self, through: Revision, effects: &mut Vec<RevocationEffect>) -> Applied {
if self.applied != Some(through) {
return Applied::Refused("ready does not match the applied cursor");
}
self.raise_high_water(through);
if self.readiness == Readiness::Ready && !self.pending_resume {
return Applied::Duplicate;
}
self.resume_generation = self.resume_generation.saturating_add(1);
let open: Vec<u64> = self.connections.keys().copied().collect();
for pid in open {
bump(&mut self.counts.resume_rechecks);
self.evaluate(pid, effects);
}
self.pending_resume = false;
self.settled_generation = self.resume_generation;
self.transition(Readiness::Ready);
Applied::Ready
}
fn apply(&mut self, event: &GrantFeedEvent, effects: &mut Vec<RevocationEffect>) -> Applied {
if self.readiness == Readiness::Terminal {
return Applied::Refused("stream is terminal");
}
match event {
GrantFeedEvent::Baseline {
log,
through,
decisions,
} => self.apply_baseline(log, *through, decisions),
GrantFeedEvent::Change {
log,
revision,
change,
evidence,
} => {
if *log != self.log {
return Applied::Refused("change from another log");
}
self.apply_change(*revision, change, *evidence, effects)
}
GrantFeedEvent::Watermark { log, through } => {
if *log != self.log {
return Applied::Refused("watermark from another log");
}
self.raise_high_water(*through);
match self.applied {
None => Applied::Refused("watermark before baseline"),
Some(applied) if *through <= applied => {
bump(&mut self.counts.duplicate_revisions);
Applied::Duplicate
}
Some(_) => {
self.applied = Some(*through);
bump(&mut self.counts.watermark_advances);
Applied::Watermark
}
}
}
GrantFeedEvent::Ready { log, through } => {
if *log != self.log {
return Applied::Refused("ready from another log");
}
self.apply_ready(*through, effects)
}
GrantFeedEvent::Lost => {
self.high_water = None;
self.pending_resume = true;
self.transition(Readiness::Unavailable);
Applied::Lost
}
GrantFeedEvent::Reset { from, to } => self.apply_reset(from, to),
GrantFeedEvent::Invalid { reason } => {
self.transition(Readiness::Terminal);
Applied::Terminal(reason)
}
}
}
fn apply_baseline(
&mut self,
log: &GrantLogId,
through: Revision,
decisions: &[(GrantId, bool)],
) -> Applied {
if *log != self.log {
return Applied::Refused("baseline from another log");
}
if decisions.len() > MAX_DECISIONS_PER_EVENT {
return Applied::Refused("baseline over its bound");
}
if self.applied.is_some_and(|applied| through < applied) {
return Applied::Refused("baseline older than the applied cursor");
}
for (grant, revoked) in decisions {
bump(&mut self.counts.rows_read);
self.merge_decision(grant, *revoked, through, true);
}
self.applied = Some(through);
self.raise_high_water(through);
Applied::Baseline
}
fn apply_reset(&mut self, from: &GrantLogId, to: &GrantLogId) -> Applied {
if *from != self.log {
return Applied::Refused("reset from a log not consumed here");
}
self.log = to.clone();
self.decisions.clear();
self.evidence.clear();
self.orphans.clear();
self.applied = None;
self.high_water = None;
self.pending_resume = true;
self.transition(Readiness::Reset);
Applied::Reset
}
fn prune_orphans(&mut self) {
if self.inflight_asks > 0 {
return;
}
for grant in std::mem::take(&mut self.orphans) {
if self.index.contains_key(&grant) {
continue;
}
if self
.decisions
.get(&grant)
.is_some_and(|decision| !decision.baseline)
{
self.decisions.remove(&grant);
bump(&mut self.counts.decisions_pruned);
}
}
}
fn finish_ask(&mut self) {
self.inflight_asks = self.inflight_asks.saturating_sub(1);
self.prune_orphans();
}
fn release(&mut self, pid: u64) -> bool {
let Some(bound) = self.connections.remove(&pid) else {
return false;
};
for grant in bound.binding.grants() {
if let Some(pids) = self.index.get_mut(grant) {
pids.remove(&pid);
if pids.is_empty() {
self.index.remove(grant);
self.orphans.insert(grant.clone());
}
}
}
bump(&mut self.counts.cleanups);
self.prune_orphans();
true
}
}
pub struct StagedAdmission {
binding: Arc<GrantDecisionBinding>,
inner: Arc<Mutex<Inner>>,
registered: bool,
}
impl fmt::Debug for StagedAdmission {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("StagedAdmission")
.field("binding", &self.binding)
.finish_non_exhaustive()
}
}
impl Drop for StagedAdmission {
fn drop(&mut self) {
if self.registered
&& let Ok(mut inner) = self.inner.lock()
{
inner.finish_ask();
}
}
}
pub struct RevocationAuthority {
policy: RevocationPolicy,
bindings: Arc<dyn GrantBindingSource>,
decisions: Arc<dyn GrantDecisionSource>,
inner: Arc<Mutex<Inner>>,
hot: Arc<HotCounts>,
}
impl fmt::Debug for RevocationAuthority {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("RevocationAuthority")
.field("log", &self.policy.log)
.finish_non_exhaustive()
}
}
impl RevocationAuthority {
#[must_use]
pub fn new(
policy: RevocationPolicy,
bindings: Arc<dyn GrantBindingSource>,
decisions: Arc<dyn GrantDecisionSource>,
) -> Self {
let inner = Arc::new(Mutex::new(Inner::new(policy.log.clone())));
Self {
policy,
bindings,
decisions,
inner,
hot: Arc::new(HotCounts::default()),
}
}
#[must_use]
pub fn unpublished(policy: RevocationPolicy) -> Self {
Self::new(
policy,
Arc::new(UnpublishedGrantBinding),
Arc::new(UnpublishedGrantDecisions),
)
}
fn lock(&self) -> Result<MutexGuard<'_, Inner>, RevocationRefusal> {
self.inner
.lock()
.map_err(|_| RevocationRefusal::StreamUnavailable)
}
fn refused(&self, refusal: RevocationRefusal) -> RevocationRefusal {
if let Ok(mut inner) = self.inner.lock() {
bump(&mut inner.counts.admissions_refused);
}
refusal
}
pub fn stage(
&self,
credential: &[u8],
holder: &[u8],
) -> Result<StagedAdmission, RevocationRefusal> {
let binding = verify_binding(&self.policy, self.bindings.as_ref(), credential, holder)
.map_err(|refusal| self.refused(refusal))?;
let binding = Arc::new(binding);
let (needed, at_least) = {
let mut inner = self.lock()?;
if let Err(refusal) = inner.precheck(&binding) {
bump(&mut inner.counts.admissions_refused);
return Err(refusal);
}
inner.inflight_asks = inner.inflight_asks.saturating_add(1);
let needed = inner.missing_grants(&binding);
let at_least = inner.applied;
drop(inner);
(needed, at_least)
};
let staged = StagedAdmission {
binding,
inner: Arc::clone(&self.inner),
registered: true,
};
if needed.is_empty() {
return Ok(staged);
}
let at_least =
at_least.ok_or_else(|| self.refused(RevocationRefusal::RevisionUnavailable))?;
let reply = self
.decisions
.decide(&staged.binding.log, &needed, at_least);
let installed = {
let mut inner = self.lock()?;
bump(&mut inner.counts.decision_asks);
let installed = reply.and_then(|reply| inner.install_reply(&reply, at_least));
if installed.is_err() {
bump(&mut inner.counts.admissions_refused);
}
drop(inner);
installed
};
installed.map(|()| staged)
}
pub fn commit(
&self,
pid: u64,
mut staged: StagedAdmission,
) -> Result<ConnectionGrants, RevocationRefusal> {
if !Arc::ptr_eq(&staged.inner, &self.inner) {
return Err(self.refused(RevocationRefusal::BindingMissing));
}
staged.registered = false;
let mut inner = self.lock()?;
let bound = inner.bind(pid, &staged.binding, &self.hot);
if bound.is_err() {
bump(&mut inner.counts.admissions_refused);
}
inner.finish_ask();
drop(inner);
bound
}
pub fn admit(
&self,
pid: u64,
credential: &[u8],
holder: &[u8],
) -> Result<ConnectionGrants, RevocationRefusal> {
let staged = self.stage(credential, holder)?;
self.commit(pid, staged)
}
#[must_use]
pub fn apply(&self, event: &GrantFeedEvent) -> ApplyOutcome {
let mut effects = Vec::new();
let Ok(mut inner) = self.inner.lock() else {
return ApplyOutcome {
applied: Applied::Terminal("revocation authority lock poisoned"),
effects,
};
};
let before = inner.transitions_dropped;
inner.announce = None;
let applied = inner.apply(event, &mut effects);
if matches!(applied, Applied::Refused(_)) {
bump(&mut inner.counts.refused_events);
}
let announce = inner.announce.take();
let dropped = inner.transitions_dropped > before;
drop(inner);
announce_outcome(applied, announce, dropped);
ApplyOutcome { applied, effects }
}
#[must_use]
pub fn release(&self, pid: u64) -> bool {
let Ok(mut inner) = self.inner.lock() else {
tracing::error!(
pid,
"revocation authority lock poisoned; the binding is not released"
);
return false;
};
inner.release(pid)
}
pub fn record_close_publication_refused(&self, pid: u64) {
if let Ok(mut inner) = self.inner.lock() {
bump(&mut inner.counts.close_publication_refused);
}
tracing::warn!(
connection_pid = pid,
"revocation close control could not be published; the connection's wake delivers it"
);
}
pub fn record_close_write(&self, pid: u64, write: CloseWrite) {
if let Ok(mut inner) = self.inner.lock() {
bump(&mut inner.counts.closes_attempted);
match write {
CloseWrite::Flushed => bump(&mut inner.counts.closes_flushed),
CloseWrite::TransportFailed | CloseWrite::NoStream => {
bump(&mut inner.counts.close_transport_failures);
}
CloseWrite::Residue | CloseWrite::NotQueued => {}
}
}
if matches!(write, CloseWrite::TransportFailed | CloseWrite::NoStream) {
tracing::warn!(
connection_pid = pid,
write = ?write,
"revocation close could not be written; the denied state is kept and cleanup finishes"
);
}
}
pub fn prune_unreferenced(&self) {
let Ok(mut inner) = self.inner.lock() else {
return;
};
if inner.inflight_asks > 0 {
return;
}
let unreferenced: Vec<GrantId> = inner
.decisions
.keys()
.filter(|grant| !inner.index.contains_key(*grant))
.cloned()
.collect();
for grant in unreferenced {
inner.decisions.remove(&grant);
bump(&mut inner.counts.decisions_pruned);
}
inner.orphans.clear();
}
pub fn diagnostics(&self) -> Result<RevocationDiagnostics, AuthorityPoisoned> {
let inner = self.inner.lock().map_err(|_| AuthorityPoisoned)?;
let snapshot = RevocationDiagnostics {
log: inner.log.clone(),
readiness: inner.readiness,
reason: inner.readiness.refusal(),
applied: inner.applied,
high_water: inner.high_water,
missing: inner.missing(),
resume_generation: inner.resume_generation,
settled_generation: inner.settled_generation,
bound_connections: inner.connections.len(),
retained_decisions: inner.decisions.len(),
index_entries: inner.index.values().map(BTreeSet::len).sum(),
inflight_asks: inner.inflight_asks,
counts: inner.counts,
hot_allowed: self.hot.allowed.load(Ordering::Relaxed),
hot_refused: self.hot.refused.load(Ordering::Relaxed),
transitions: inner.transitions.iter().copied().collect(),
transitions_dropped: inner.transitions_dropped,
};
drop(inner);
Ok(snapshot)
}
}
fn announce_outcome(applied: Applied, transition: Option<Transition>, dropped: bool) {
if let Some(transition) = transition {
match transition.readiness {
Readiness::Ready => tracing::info!(
applied = ?transition.applied.map(Revision::get),
rechecks = transition.rechecks,
"revocation stream ready"
),
Readiness::NotReady
| Readiness::Gap
| Readiness::Unavailable
| Readiness::Reset
| Readiness::Terminal => tracing::warn!(
readiness = ?transition.readiness,
reason = ?transition.reason.map(RevocationRefusal::name),
applied = ?transition.applied.map(Revision::get),
missing = ?transition.missing,
refused_admissions = transition.refused_admissions,
rechecks = transition.rechecks,
"revocation stream lost readiness; new admissions are refused by name"
),
}
}
if dropped {
tracing::warn!("revocation diagnostic transition ring overflowed; the oldest was dropped");
}
match applied {
Applied::Refused(reason) => {
tracing::warn!(reason, "revocation stream event refused");
}
Applied::Terminal(reason) => {
tracing::error!(reason, "revocation stream ended on a terminal error");
}
Applied::Baseline
| Applied::Change
| Applied::Duplicate
| Applied::Watermark
| Applied::Gap
| Applied::Ready
| Applied::Lost
| Applied::Reset => {}
}
}