1use std::collections::{BTreeMap, BTreeSet, VecDeque};
47use std::fmt;
48use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
49use std::sync::{Arc, Mutex, MutexGuard};
50
51use liminal::protocol::DisconnectReason;
52
53use crate::auth_lys::HotAction;
54
55pub const MAX_LOG_IDENTITY_LEN: usize = 256;
57pub const MAX_GRANT_ID_LEN: usize = 128;
59pub const MAX_BOUND_RIGHTS: usize = 64;
61pub const MAX_CHAINS_PER_RIGHT: usize = 8;
63pub const MAX_CHAIN_LEN: usize = 16;
65pub const MAX_RIGHT_PREFIX_LEN: usize = 256;
67pub const MAX_DECISIONS_PER_EVENT: usize = 65_536;
69pub const EVIDENCE_RETAINED: usize = 1_024;
72pub const MAX_DIAGNOSTIC_EVENTS: usize = 64;
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, thiserror::Error)]
83pub enum RevocationRefusal {
84 #[error("grant_revoked")]
87 GrantRevoked,
88 #[error("revocation_binding_missing")]
91 BindingMissing,
92 #[error("revocation_revision_unavailable")]
95 RevisionUnavailable,
96 #[error("revocation_stream_unavailable")]
99 StreamUnavailable,
100 #[error("revocation_stream_gap")]
102 StreamGap,
103 #[error("revocation_log_reset")]
106 LogReset,
107 #[error("revocation_protocol_unsupported")]
110 ProtocolUnsupported,
111}
112
113impl RevocationRefusal {
114 pub const ALL: [Self; 7] = [
116 Self::GrantRevoked,
117 Self::BindingMissing,
118 Self::RevisionUnavailable,
119 Self::StreamUnavailable,
120 Self::StreamGap,
121 Self::LogReset,
122 Self::ProtocolUnsupported,
123 ];
124
125 #[must_use]
127 pub const fn name(self) -> &'static str {
128 match self {
129 Self::GrantRevoked => "grant_revoked",
130 Self::BindingMissing => "revocation_binding_missing",
131 Self::RevisionUnavailable => "revocation_revision_unavailable",
132 Self::StreamUnavailable => "revocation_stream_unavailable",
133 Self::StreamGap => "revocation_stream_gap",
134 Self::LogReset => "revocation_log_reset",
135 Self::ProtocolUnsupported => "revocation_protocol_unsupported",
136 }
137 }
138
139 #[must_use]
142 pub const fn close_reason(self) -> Option<DisconnectReason> {
143 match self {
144 Self::GrantRevoked => Some(DisconnectReason::GrantRevoked),
145 Self::LogReset => Some(DisconnectReason::RevocationLogReset),
146 Self::BindingMissing
147 | Self::RevisionUnavailable
148 | Self::StreamUnavailable
149 | Self::StreamGap
150 | Self::ProtocolUnsupported => None,
151 }
152 }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
158#[error("{what} is empty or longer than its bound")]
159pub struct InvalidGrantInput {
160 pub what: &'static str,
162}
163
164fn bounded(value: &str, max: usize, what: &'static str) -> Result<String, InvalidGrantInput> {
165 if value.is_empty() || value.len() > max {
166 Err(InvalidGrantInput { what })
167 } else {
168 Ok(value.to_owned())
169 }
170}
171
172#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
176pub struct GrantLogId {
177 identity: String,
178 epoch: u64,
179}
180
181impl GrantLogId {
182 pub fn new(identity: &str, epoch: u64) -> Result<Self, InvalidGrantInput> {
188 Ok(Self {
189 identity: bounded(identity, MAX_LOG_IDENTITY_LEN, "grant log identity")?,
190 epoch,
191 })
192 }
193
194 #[must_use]
196 pub fn identity(&self) -> &str {
197 &self.identity
198 }
199
200 #[must_use]
202 pub const fn epoch(&self) -> u64 {
203 self.epoch
204 }
205}
206
207#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
210pub struct GrantId(String);
211
212impl GrantId {
213 pub fn new(id: &str) -> Result<Self, InvalidGrantInput> {
219 bounded(id, MAX_GRANT_ID_LEN, "grant id").map(Self)
220 }
221
222 #[must_use]
224 pub fn as_str(&self) -> &str {
225 &self.0
226 }
227}
228
229impl fmt::Debug for GrantId {
230 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(formatter, "GrantId({} bytes)", self.0.len())
232 }
233}
234
235#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
238pub struct Revision(u64);
239
240impl Revision {
241 #[must_use]
244 pub const fn from_receipt_index(index: u64) -> Option<Self> {
245 match index.checked_add(1) {
246 Some(revision) => Some(Self(revision)),
247 None => None,
248 }
249 }
250
251 #[must_use]
253 pub const fn new(revision: u64) -> Self {
254 Self(revision)
255 }
256
257 #[must_use]
259 pub const fn get(self) -> u64 {
260 self.0
261 }
262
263 const fn next(self) -> Option<Self> {
264 match self.0.checked_add(1) {
265 Some(next) => Some(Self(next)),
266 None => None,
267 }
268 }
269}
270
271const fn missing_between(from: Revision, to: Revision) -> Option<u64> {
274 match to.0.checked_sub(from.0) {
275 Some(distance) => distance.checked_sub(1),
276 None => None,
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq)]
282pub enum RightAction {
283 Connect,
285 Hot(HotAction),
287}
288
289#[derive(Clone, PartialEq, Eq)]
296pub struct BoundRightClaim {
297 pub action: RightAction,
299 pub prefix: String,
301 pub chains: Vec<Vec<GrantId>>,
303}
304
305impl fmt::Debug for BoundRightClaim {
306 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
307 formatter
308 .debug_struct("BoundRightClaim")
309 .field("action", &self.action)
310 .field("prefix", &format_args!("{} bytes", self.prefix.len()))
311 .field("chains", &self.chains.len())
312 .finish()
313 }
314}
315
316#[derive(Clone, Copy, PartialEq, Eq)]
318pub struct Digest32(pub [u8; 32]);
319
320impl fmt::Debug for Digest32 {
321 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
322 formatter.write_str("Digest32(32 bytes)")
323 }
324}
325
326#[derive(Clone, PartialEq, Eq)]
335pub struct AuthenticatedBinding {
336 pub issuer: Digest32,
338 pub audience: String,
340 pub holder: Vec<u8>,
342 pub pass_digest: Digest32,
344 pub log: GrantLogId,
346 pub required: Revision,
348 pub rights: Vec<BoundRightClaim>,
350}
351
352impl fmt::Debug for AuthenticatedBinding {
353 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
354 formatter
355 .debug_struct("AuthenticatedBinding")
356 .field("audience_bytes", &self.audience.len())
357 .field("holder_bytes", &self.holder.len())
358 .field("log", &self.log)
359 .field("required", &self.required)
360 .field("rights", &self.rights.len())
361 .finish_non_exhaustive()
362 }
363}
364
365pub trait GrantBindingSource: Send + Sync + fmt::Debug {
372 fn pass_digest(&self, credential: &[u8]) -> Result<Digest32, RevocationRefusal>;
378
379 fn binding_for(&self, credential: &[u8]) -> Result<AuthenticatedBinding, RevocationRefusal>;
385}
386
387#[derive(Clone, Copy, Debug, Default)]
390pub struct UnpublishedGrantBinding;
391
392impl GrantBindingSource for UnpublishedGrantBinding {
393 fn pass_digest(&self, _credential: &[u8]) -> Result<Digest32, RevocationRefusal> {
394 Err(RevocationRefusal::BindingMissing)
395 }
396
397 fn binding_for(&self, _credential: &[u8]) -> Result<AuthenticatedBinding, RevocationRefusal> {
398 Err(RevocationRefusal::BindingMissing)
399 }
400}
401
402#[derive(Clone, Debug, PartialEq, Eq)]
405pub struct DecisionReply {
406 pub log: GrantLogId,
408 pub revision: Revision,
410 pub decisions: Vec<(GrantId, bool)>,
412}
413
414pub trait GrantDecisionSource: Send + Sync + fmt::Debug {
418 fn decide(
423 &self,
424 log: &GrantLogId,
425 grants: &[GrantId],
426 at_least: Revision,
427 ) -> Result<DecisionReply, RevocationRefusal>;
428}
429
430#[derive(Clone, Copy, Debug, Default)]
433pub struct UnpublishedGrantDecisions;
434
435impl GrantDecisionSource for UnpublishedGrantDecisions {
436 fn decide(
437 &self,
438 _log: &GrantLogId,
439 _grants: &[GrantId],
440 _at_least: Revision,
441 ) -> Result<DecisionReply, RevocationRefusal> {
442 Err(RevocationRefusal::RevisionUnavailable)
443 }
444}
445
446#[derive(Clone, Debug, PartialEq, Eq)]
449pub struct RevocationPolicy {
450 pub log: GrantLogId,
452 pub audience: String,
454 pub issuer: Digest32,
456}
457
458#[derive(Clone, PartialEq, Eq)]
459struct BoundRight {
460 action: RightAction,
461 prefix: String,
462 chains: Vec<Vec<GrantId>>,
463}
464
465#[derive(Clone, PartialEq, Eq)]
468pub struct GrantDecisionBinding {
469 log: GrantLogId,
470 required: Revision,
471 rights: Vec<BoundRight>,
472 admitting: usize,
473}
474
475impl GrantDecisionBinding {
476 #[must_use]
478 pub const fn log(&self) -> &GrantLogId {
479 &self.log
480 }
481
482 #[must_use]
484 pub const fn required(&self) -> Revision {
485 self.required
486 }
487
488 #[must_use]
490 pub fn right_count(&self) -> usize {
491 self.rights.len()
492 }
493
494 fn grants(&self) -> impl Iterator<Item = &GrantId> {
495 self.rights
496 .iter()
497 .flat_map(|right| right.chains.iter().flatten())
498 }
499}
500
501impl fmt::Debug for GrantDecisionBinding {
502 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
503 formatter
504 .debug_struct("GrantDecisionBinding")
505 .field("log", &self.log)
506 .field("required", &self.required)
507 .field("rights", &self.rights.len())
508 .finish_non_exhaustive()
509 }
510}
511
512pub fn verify_binding(
524 policy: &RevocationPolicy,
525 source: &dyn GrantBindingSource,
526 credential: &[u8],
527 holder: &[u8],
528) -> Result<GrantDecisionBinding, RevocationRefusal> {
529 let claimed = source.binding_for(credential)?;
530 let digest = source.pass_digest(credential)?;
531 let mismatch = if claimed.issuer != policy.issuer {
532 Some("issuer")
533 } else if claimed.audience != policy.audience {
534 Some("audience")
535 } else if claimed.holder.as_slice() != holder {
536 Some("holder")
537 } else if claimed.pass_digest != digest {
538 Some("pass_digest")
539 } else {
540 None
541 };
542 if let Some(field) = mismatch {
543 tracing::warn!(
544 field,
545 "grant-decision binding does not match the presented credential; refused"
546 );
547 return Err(RevocationRefusal::BindingMissing);
548 }
549 bound_rights(claimed)
550}
551
552fn bound_rights(claimed: AuthenticatedBinding) -> Result<GrantDecisionBinding, RevocationRefusal> {
553 let refuse = |what: &'static str| {
554 tracing::warn!(
555 what,
556 "grant-decision binding is over its bound or incomplete"
557 );
558 RevocationRefusal::BindingMissing
559 };
560 if claimed.rights.is_empty() || claimed.rights.len() > MAX_BOUND_RIGHTS {
561 return Err(refuse("rights"));
562 }
563 let mut rights = Vec::with_capacity(claimed.rights.len());
564 for right in claimed.rights {
565 if right.prefix.len() > MAX_RIGHT_PREFIX_LEN {
566 return Err(refuse("prefix"));
567 }
568 if right.chains.is_empty() || right.chains.len() > MAX_CHAINS_PER_RIGHT {
569 return Err(refuse("chains"));
570 }
571 if right
572 .chains
573 .iter()
574 .any(|chain| chain.is_empty() || chain.len() > MAX_CHAIN_LEN)
575 {
576 return Err(refuse("chain"));
577 }
578 if matches!(right.action, RightAction::Hot(_)) && right.prefix.is_empty() {
579 return Err(refuse("hot right without a prefix"));
580 }
581 rights.push(BoundRight {
582 action: right.action,
583 prefix: right.prefix,
584 chains: right.chains,
585 });
586 }
587 let admitting = rights
588 .iter()
589 .position(|right| right.action == RightAction::Connect)
590 .ok_or_else(|| refuse("admitting right"))?;
591 Ok(GrantDecisionBinding {
592 log: claimed.log,
593 required: claimed.required,
594 rights,
595 admitting,
596 })
597}
598
599const CLOSE_NONE: u8 = 0;
600const CLOSE_GRANT_REVOKED: u8 = 1;
601const CLOSE_LOG_RESET: u8 = 2;
602
603const fn close_code(reason: RevocationRefusal) -> u8 {
604 match reason {
605 RevocationRefusal::LogReset => CLOSE_LOG_RESET,
606 RevocationRefusal::GrantRevoked
607 | RevocationRefusal::BindingMissing
608 | RevocationRefusal::RevisionUnavailable
609 | RevocationRefusal::StreamUnavailable
610 | RevocationRefusal::StreamGap
611 | RevocationRefusal::ProtocolUnsupported => CLOSE_GRANT_REVOKED,
612 }
613}
614
615#[derive(Debug)]
619struct LiveRights {
620 lost: Box<[AtomicBool]>,
621 close: AtomicU8,
622 changes: AtomicU64,
623}
624
625#[derive(Debug, Default)]
626struct HotCounts {
627 allowed: AtomicU64,
628 refused: AtomicU64,
629}
630
631#[derive(Clone)]
638pub struct ConnectionGrants {
639 pid: u64,
640 binding: Arc<GrantDecisionBinding>,
641 live: Arc<LiveRights>,
642 hot: Arc<HotCounts>,
643}
644
645impl fmt::Debug for ConnectionGrants {
646 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
647 formatter
648 .debug_struct("ConnectionGrants")
649 .field("pid", &self.pid)
650 .field("binding", &self.binding)
651 .finish_non_exhaustive()
652 }
653}
654
655impl ConnectionGrants {
656 #[must_use]
658 pub const fn pid(&self) -> u64 {
659 self.pid
660 }
661
662 #[must_use]
664 pub fn binding(&self) -> &GrantDecisionBinding {
665 &self.binding
666 }
667
668 pub fn allows(&self, action: HotAction, subject: &str) -> Result<(), RevocationRefusal> {
676 let decided = self.decide(action, subject);
677 let counter = if decided.is_ok() {
678 &self.hot.allowed
679 } else {
680 &self.hot.refused
681 };
682 counter.fetch_add(1, Ordering::Relaxed);
683 decided
684 }
685
686 #[must_use]
690 pub fn permits(&self, action: HotAction, subject: &str) -> bool {
691 self.decide(action, subject).is_ok()
692 }
693
694 fn decide(&self, action: HotAction, subject: &str) -> Result<(), RevocationRefusal> {
695 if let Some(reason) = self.close_pending() {
696 return Err(reason);
697 }
698 let mut covered = false;
699 for (right, lost) in self.binding.rights.iter().zip(self.live.lost.iter()) {
700 if right.action == RightAction::Hot(action) && subject.starts_with(&right.prefix) {
701 covered = true;
702 if !lost.load(Ordering::Acquire) {
703 return Ok(());
704 }
705 }
706 }
707 Err(if covered {
708 RevocationRefusal::GrantRevoked
709 } else {
710 RevocationRefusal::BindingMissing
711 })
712 }
713
714 #[must_use]
717 pub fn close_pending(&self) -> Option<RevocationRefusal> {
718 match self.live.close.load(Ordering::Acquire) {
719 CLOSE_NONE => None,
720 CLOSE_LOG_RESET => Some(RevocationRefusal::LogReset),
721 _ => Some(RevocationRefusal::GrantRevoked),
722 }
723 }
724
725 #[must_use]
729 pub fn changes(&self) -> u64 {
730 self.live.changes.load(Ordering::Acquire)
731 }
732}
733
734#[derive(Clone, Copy, Debug, PartialEq, Eq)]
736pub enum Readiness {
737 NotReady,
739 Ready,
741 Gap,
743 Unavailable,
745 Reset,
747 Terminal,
750}
751
752impl Readiness {
753 const fn refusal(self) -> Option<RevocationRefusal> {
754 match self {
755 Self::Ready => None,
756 Self::NotReady | Self::Unavailable | Self::Terminal => {
757 Some(RevocationRefusal::StreamUnavailable)
758 }
759 Self::Gap => Some(RevocationRefusal::StreamGap),
760 Self::Reset => Some(RevocationRefusal::LogReset),
761 }
762 }
763}
764
765#[derive(Clone, Debug, PartialEq, Eq)]
767pub enum GrantFeedChange {
768 Issue(GrantId),
770 Revoke(GrantId),
772}
773
774#[derive(Clone, Debug, PartialEq, Eq)]
777pub enum GrantFeedEvent {
778 Baseline {
781 log: GrantLogId,
783 through: Revision,
785 decisions: Vec<(GrantId, bool)>,
787 },
788 Change {
790 log: GrantLogId,
792 revision: Revision,
794 change: GrantFeedChange,
796 evidence: Digest32,
799 },
800 Watermark {
803 log: GrantLogId,
805 through: Revision,
807 },
808 Ready {
811 log: GrantLogId,
813 through: Revision,
815 },
816 Lost,
818 Reset {
820 from: GrantLogId,
822 to: GrantLogId,
824 },
825 Invalid {
827 reason: &'static str,
829 },
830}
831
832#[derive(Clone, Copy, Debug, PartialEq, Eq)]
834pub enum RevocationEffect {
835 Withdraw {
837 pid: u64,
839 right: usize,
841 },
842 Close {
844 pid: u64,
846 reason: RevocationRefusal,
848 },
849}
850
851impl RevocationEffect {
852 #[must_use]
854 pub const fn pid(self) -> u64 {
855 match self {
856 Self::Withdraw { pid, .. } | Self::Close { pid, .. } => pid,
857 }
858 }
859}
860
861#[derive(Clone, Copy, Debug, PartialEq, Eq)]
863pub enum Applied {
864 Baseline,
866 Change,
868 Duplicate,
870 Watermark,
872 Gap,
874 Ready,
877 Lost,
879 Reset,
881 Refused(&'static str),
883 Terminal(&'static str),
885}
886
887#[derive(Clone, Debug, PartialEq, Eq)]
889pub struct ApplyOutcome {
890 pub applied: Applied,
892 pub effects: Vec<RevocationEffect>,
894}
895
896#[derive(Clone, Copy, Debug, PartialEq, Eq)]
899pub enum CloseWrite {
900 Flushed,
902 Residue,
904 NotQueued,
906 TransportFailed,
908 NoStream,
910}
911
912#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
919pub struct RevocationCounts {
920 pub observed_revocations: u64,
922 pub applied_revisions: u64,
924 pub duplicate_revisions: u64,
926 pub watermark_advances: u64,
928 pub refused_events: u64,
930 pub affected_visits: u64,
932 pub resume_rechecks: u64,
934 pub admissions_committed: u64,
936 pub admissions_refused: u64,
938 pub binding_replacements_refused: u64,
940 pub withdrawals: u64,
942 pub closes_decided: u64,
944 pub close_publication_refused: u64,
947 pub closes_attempted: u64,
949 pub closes_flushed: u64,
951 pub close_transport_failures: u64,
953 pub cleanups: u64,
955 pub decisions_pruned: u64,
957 pub gap_transitions: u64,
959 pub loss_transitions: u64,
961 pub reset_transitions: u64,
963 pub terminal_errors: u64,
965 pub decision_asks: u64,
967 pub stale_replies_refused: u64,
969 pub stale_overwrites_prevented: u64,
971 pub rows_read: u64,
973}
974
975#[derive(Clone, Copy, Debug, PartialEq, Eq)]
977pub enum GapLength {
978 None,
980 Known(u64),
982 Unknown,
985}
986
987#[derive(Clone, Copy, Debug, PartialEq, Eq)]
989pub struct Transition {
990 pub readiness: Readiness,
992 pub reason: Option<RevocationRefusal>,
994 pub applied: Option<Revision>,
996 pub missing: GapLength,
998 pub refused_admissions: u64,
1000 pub rechecks: u64,
1002}
1003
1004#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1007#[error("revocation_authority_poisoned")]
1008pub struct AuthorityPoisoned;
1009
1010#[derive(Clone, Debug, PartialEq, Eq)]
1013pub struct RevocationDiagnostics {
1014 pub log: GrantLogId,
1016 pub readiness: Readiness,
1018 pub reason: Option<RevocationRefusal>,
1020 pub applied: Option<Revision>,
1022 pub high_water: Option<Revision>,
1024 pub missing: GapLength,
1026 pub resume_generation: u64,
1028 pub settled_generation: u64,
1030 pub bound_connections: usize,
1032 pub retained_decisions: usize,
1034 pub index_entries: usize,
1036 pub inflight_asks: u64,
1038 pub counts: RevocationCounts,
1040 pub hot_allowed: u64,
1042 pub hot_refused: u64,
1044 pub transitions: Vec<Transition>,
1046 pub transitions_dropped: u64,
1048}
1049
1050#[derive(Clone, Copy, Debug)]
1051struct Decision {
1052 revoked: bool,
1053 at: Revision,
1054 baseline: bool,
1055}
1056
1057#[derive(Debug)]
1058struct Bound {
1059 binding: Arc<GrantDecisionBinding>,
1060 live: Arc<LiveRights>,
1061}
1062
1063#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1064enum RightState {
1065 Allowed,
1066 Lost,
1067 Unknown,
1068}
1069
1070#[derive(Debug)]
1071struct Inner {
1072 link_life: crate::link::LinkRegistry,
1073 log: GrantLogId,
1074 readiness: Readiness,
1075 applied: Option<Revision>,
1076 high_water: Option<Revision>,
1077 pending_resume: bool,
1078 resume_generation: u64,
1079 settled_generation: u64,
1080 decisions: BTreeMap<GrantId, Decision>,
1081 evidence: VecDeque<(Revision, Digest32)>,
1082 connections: BTreeMap<u64, Bound>,
1083 index: BTreeMap<GrantId, BTreeSet<u64>>,
1084 orphans: BTreeSet<GrantId>,
1085 inflight_asks: u64,
1086 counts: RevocationCounts,
1087 transitions: VecDeque<Transition>,
1088 transitions_dropped: u64,
1089 announce: Option<Transition>,
1090}
1091
1092const fn bump(counter: &mut u64) {
1093 *counter = counter.saturating_add(1);
1094}
1095
1096impl Inner {
1097 fn new(log: GrantLogId) -> Self {
1098 Self {
1099 log,
1100 link_life: crate::link::LinkRegistry::default(),
1101 readiness: Readiness::NotReady,
1102 applied: None,
1103 high_water: None,
1104 pending_resume: false,
1105 resume_generation: 0,
1106 settled_generation: 0,
1107 decisions: BTreeMap::new(),
1108 evidence: VecDeque::new(),
1109 connections: BTreeMap::new(),
1110 index: BTreeMap::new(),
1111 orphans: BTreeSet::new(),
1112 inflight_asks: 0,
1113 counts: RevocationCounts::default(),
1114 transitions: VecDeque::new(),
1115 transitions_dropped: 0,
1116 announce: None,
1117 }
1118 }
1119
1120 fn missing(&self) -> GapLength {
1121 if self.readiness == Readiness::Ready {
1122 return GapLength::None;
1123 }
1124 match (self.applied, self.high_water) {
1125 (Some(applied), Some(high)) => match missing_between(applied, high) {
1126 Some(missing) if missing > 0 => GapLength::Known(missing),
1127 _ => GapLength::None,
1128 },
1129 _ => GapLength::Unknown,
1132 }
1133 }
1134
1135 fn transition(&mut self, readiness: Readiness) {
1138 if self.readiness == readiness {
1139 return;
1140 }
1141 self.readiness = readiness;
1142 match readiness {
1143 Readiness::Gap => bump(&mut self.counts.gap_transitions),
1144 Readiness::Unavailable => bump(&mut self.counts.loss_transitions),
1145 Readiness::Reset => bump(&mut self.counts.reset_transitions),
1146 Readiness::Terminal => bump(&mut self.counts.terminal_errors),
1147 Readiness::NotReady | Readiness::Ready => {}
1148 }
1149 let transition = Transition {
1150 readiness,
1151 reason: readiness.refusal(),
1152 applied: self.applied,
1153 missing: self.missing(),
1154 refused_admissions: self.counts.admissions_refused,
1155 rechecks: self.counts.resume_rechecks,
1156 };
1157 if self.transitions.len() >= MAX_DIAGNOSTIC_EVENTS {
1158 self.transitions.pop_front();
1159 bump(&mut self.transitions_dropped);
1160 }
1161 self.transitions.push_back(transition);
1162 self.announce = Some(transition);
1163 }
1164
1165 fn precheck(&self, binding: &GrantDecisionBinding) -> Result<(), RevocationRefusal> {
1166 if let Some(refusal) = self.readiness.refusal() {
1167 return Err(refusal);
1168 }
1169 if binding.log != self.log {
1170 return Err(RevocationRefusal::LogReset);
1171 }
1172 if self
1173 .applied
1174 .is_none_or(|applied| applied < binding.required)
1175 {
1176 return Err(RevocationRefusal::RevisionUnavailable);
1177 }
1178 Ok(())
1179 }
1180
1181 fn missing_grants(&self, binding: &GrantDecisionBinding) -> Vec<GrantId> {
1182 let unknown: BTreeSet<&GrantId> = binding
1183 .grants()
1184 .filter(|grant| !self.decisions.contains_key(*grant))
1185 .collect();
1186 unknown.into_iter().cloned().collect()
1187 }
1188
1189 fn right_state(&self, right: &BoundRight) -> RightState {
1190 let mut unknown = false;
1191 for chain in &right.chains {
1192 let mut chain_state = RightState::Allowed;
1193 for grant in chain {
1194 match self.decisions.get(grant) {
1195 Some(decision) if decision.revoked => {
1196 chain_state = RightState::Lost;
1197 break;
1198 }
1199 Some(_) => {}
1200 None => chain_state = RightState::Unknown,
1201 }
1202 }
1203 match chain_state {
1204 RightState::Allowed => return RightState::Allowed,
1205 RightState::Unknown => unknown = true,
1206 RightState::Lost => {}
1207 }
1208 }
1209 if unknown {
1210 RightState::Unknown
1211 } else {
1212 RightState::Lost
1213 }
1214 }
1215
1216 fn merge_decision(&mut self, grant: &GrantId, revoked: bool, at: Revision, baseline: bool) {
1219 match self.decisions.get_mut(grant) {
1220 Some(existing) if existing.revoked => {
1221 if !revoked {
1222 bump(&mut self.counts.stale_overwrites_prevented);
1223 }
1224 existing.baseline |= baseline;
1225 }
1226 Some(existing) if existing.at >= at && !revoked => {
1227 existing.baseline |= baseline;
1228 }
1229 Some(existing) => {
1230 existing.revoked = revoked;
1231 existing.at = at;
1232 existing.baseline |= baseline;
1233 }
1234 None => {
1235 self.decisions.insert(
1236 grant.clone(),
1237 Decision {
1238 revoked,
1239 at,
1240 baseline,
1241 },
1242 );
1243 if !baseline && !self.index.contains_key(grant) {
1244 self.orphans.insert(grant.clone());
1245 }
1246 }
1247 }
1248 }
1249
1250 fn install_reply(
1251 &mut self,
1252 reply: &DecisionReply,
1253 at_least: Revision,
1254 ) -> Result<(), RevocationRefusal> {
1255 if reply.log != self.log {
1256 return Err(RevocationRefusal::LogReset);
1257 }
1258 if reply.revision < at_least || reply.decisions.len() > MAX_DECISIONS_PER_EVENT {
1259 bump(&mut self.counts.stale_replies_refused);
1260 return Err(RevocationRefusal::RevisionUnavailable);
1261 }
1262 for (grant, revoked) in &reply.decisions {
1263 bump(&mut self.counts.rows_read);
1264 self.merge_decision(grant, *revoked, reply.revision, false);
1265 }
1266 Ok(())
1267 }
1268
1269 fn bind(
1271 &mut self,
1272 pid: u64,
1273 binding: &Arc<GrantDecisionBinding>,
1274 hot: &Arc<HotCounts>,
1275 ) -> Result<ConnectionGrants, RevocationRefusal> {
1276 self.precheck(binding)?;
1277 if self.connections.contains_key(&pid) {
1278 bump(&mut self.counts.binding_replacements_refused);
1279 tracing::warn!(
1280 connection_pid = pid,
1281 "a second grant-decision binding was offered; the first is kept"
1282 );
1283 return Err(RevocationRefusal::BindingMissing);
1284 }
1285 let mut lost = Vec::with_capacity(binding.rights.len());
1286 for (index, right) in binding.rights.iter().enumerate() {
1287 match (self.right_state(right), index == binding.admitting) {
1288 (RightState::Allowed, _) => lost.push(AtomicBool::new(false)),
1289 (RightState::Lost, true) => return Err(RevocationRefusal::GrantRevoked),
1290 (RightState::Lost, false) => lost.push(AtomicBool::new(true)),
1291 (RightState::Unknown, _) => return Err(RevocationRefusal::RevisionUnavailable),
1292 }
1293 }
1294 let live = Arc::new(LiveRights {
1295 lost: lost.into_boxed_slice(),
1296 close: AtomicU8::new(CLOSE_NONE),
1297 changes: AtomicU64::new(0),
1298 });
1299 for grant in binding.grants() {
1300 self.index.entry(grant.clone()).or_default().insert(pid);
1301 self.orphans.remove(grant);
1302 }
1303 self.connections.insert(
1304 pid,
1305 Bound {
1306 binding: Arc::clone(binding),
1307 live: Arc::clone(&live),
1308 },
1309 );
1310 bump(&mut self.counts.admissions_committed);
1311 Ok(ConnectionGrants {
1312 pid,
1313 binding: Arc::clone(binding),
1314 live,
1315 hot: Arc::clone(hot),
1316 })
1317 }
1318
1319 fn evaluate(&mut self, pid: u64, effects: &mut Vec<RevocationEffect>) {
1322 let Some(bound) = self.connections.get(&pid) else {
1323 return;
1324 };
1325 let binding = Arc::clone(&bound.binding);
1326 let live = Arc::clone(&bound.live);
1327 if live.close.load(Ordering::Acquire) != CLOSE_NONE {
1328 return;
1329 }
1330 if binding.log != self.log {
1331 self.close(pid, &live, RevocationRefusal::LogReset, effects);
1332 return;
1333 }
1334 for (index, (right, lost)) in binding.rights.iter().zip(live.lost.iter()).enumerate() {
1335 if lost.load(Ordering::Acquire) || self.right_state(right) == RightState::Allowed {
1336 continue;
1337 }
1338 lost.store(true, Ordering::Release);
1340 live.changes.fetch_add(1, Ordering::AcqRel);
1341 if index == binding.admitting {
1342 self.close(pid, &live, RevocationRefusal::GrantRevoked, effects);
1343 return;
1344 }
1345 bump(&mut self.counts.withdrawals);
1346 effects.push(RevocationEffect::Withdraw { pid, right: index });
1347 }
1348 }
1349
1350 fn close(
1351 &mut self,
1352 pid: u64,
1353 live: &LiveRights,
1354 reason: RevocationRefusal,
1355 effects: &mut Vec<RevocationEffect>,
1356 ) {
1357 self.link_life.close(pid, crate::link::LinkEnd::Revoked);
1358 live.close.store(close_code(reason), Ordering::Release);
1359 live.changes.fetch_add(1, Ordering::AcqRel);
1360 bump(&mut self.counts.closes_decided);
1361 effects.push(RevocationEffect::Close { pid, reason });
1362 }
1363
1364 fn remember_evidence(&mut self, revision: Revision, evidence: Digest32) {
1365 if self.evidence.len() >= EVIDENCE_RETAINED {
1366 self.evidence.pop_front();
1367 }
1368 self.evidence.push_back((revision, evidence));
1369 }
1370
1371 fn raise_high_water(&mut self, revision: Revision) {
1372 if self.high_water.is_none_or(|high| high < revision) {
1373 self.high_water = Some(revision);
1374 }
1375 }
1376
1377 fn apply_change(
1378 &mut self,
1379 revision: Revision,
1380 change: &GrantFeedChange,
1381 evidence: Digest32,
1382 effects: &mut Vec<RevocationEffect>,
1383 ) -> Applied {
1384 self.raise_high_water(revision);
1385 let Some(applied) = self.applied else {
1386 return Applied::Refused("change before baseline");
1387 };
1388 if revision <= applied {
1389 let conflicting = self
1390 .evidence
1391 .iter()
1392 .any(|(seen, digest)| *seen == revision && *digest != evidence);
1393 if conflicting {
1394 self.transition(Readiness::Terminal);
1395 return Applied::Terminal("conflicting_evidence");
1396 }
1397 bump(&mut self.counts.duplicate_revisions);
1398 return Applied::Duplicate;
1399 }
1400 if applied.next() != Some(revision) {
1401 self.pending_resume = true;
1402 self.transition(Readiness::Gap);
1403 return Applied::Gap;
1404 }
1405 self.applied = Some(revision);
1406 self.remember_evidence(revision, evidence);
1407 bump(&mut self.counts.applied_revisions);
1408 match change {
1409 GrantFeedChange::Issue(grant) => {
1410 if self.decisions.contains_key(grant) || self.index.contains_key(grant) {
1411 self.merge_decision(grant, false, revision, false);
1412 }
1413 }
1414 GrantFeedChange::Revoke(grant) => {
1415 bump(&mut self.counts.observed_revocations);
1416 let referenced = self.index.contains_key(grant);
1417 if referenced || self.inflight_asks > 0 || self.decisions.contains_key(grant) {
1418 self.merge_decision(grant, true, revision, false);
1421 }
1422 let affected: Vec<u64> = self
1423 .index
1424 .get(grant)
1425 .map(|pids| pids.iter().copied().collect())
1426 .unwrap_or_default();
1427 for pid in affected {
1428 bump(&mut self.counts.affected_visits);
1429 self.evaluate(pid, effects);
1430 }
1431 }
1432 }
1433 Applied::Change
1434 }
1435
1436 fn apply_ready(&mut self, through: Revision, effects: &mut Vec<RevocationEffect>) -> Applied {
1437 if self.applied != Some(through) {
1438 return Applied::Refused("ready does not match the applied cursor");
1439 }
1440 self.raise_high_water(through);
1441 if self.readiness == Readiness::Ready && !self.pending_resume {
1442 return Applied::Duplicate;
1443 }
1444 self.resume_generation = self.resume_generation.saturating_add(1);
1445 let open: Vec<u64> = self.connections.keys().copied().collect();
1446 for pid in open {
1447 bump(&mut self.counts.resume_rechecks);
1448 self.evaluate(pid, effects);
1449 }
1450 self.pending_resume = false;
1451 self.settled_generation = self.resume_generation;
1452 self.transition(Readiness::Ready);
1453 Applied::Ready
1454 }
1455
1456 fn apply(&mut self, event: &GrantFeedEvent, effects: &mut Vec<RevocationEffect>) -> Applied {
1457 if self.readiness == Readiness::Terminal {
1458 return Applied::Refused("stream is terminal");
1459 }
1460 match event {
1461 GrantFeedEvent::Baseline {
1462 log,
1463 through,
1464 decisions,
1465 } => self.apply_baseline(log, *through, decisions),
1466 GrantFeedEvent::Change {
1467 log,
1468 revision,
1469 change,
1470 evidence,
1471 } => {
1472 if *log != self.log {
1473 return Applied::Refused("change from another log");
1474 }
1475 self.apply_change(*revision, change, *evidence, effects)
1476 }
1477 GrantFeedEvent::Watermark { log, through } => {
1478 if *log != self.log {
1479 return Applied::Refused("watermark from another log");
1480 }
1481 self.raise_high_water(*through);
1482 match self.applied {
1483 None => Applied::Refused("watermark before baseline"),
1484 Some(applied) if *through <= applied => {
1485 bump(&mut self.counts.duplicate_revisions);
1486 Applied::Duplicate
1487 }
1488 Some(_) => {
1489 self.applied = Some(*through);
1490 bump(&mut self.counts.watermark_advances);
1491 Applied::Watermark
1492 }
1493 }
1494 }
1495 GrantFeedEvent::Ready { log, through } => {
1496 if *log != self.log {
1497 return Applied::Refused("ready from another log");
1498 }
1499 self.apply_ready(*through, effects)
1500 }
1501 GrantFeedEvent::Lost => {
1502 self.high_water = None;
1503 self.pending_resume = true;
1504 self.transition(Readiness::Unavailable);
1505 Applied::Lost
1506 }
1507 GrantFeedEvent::Reset { from, to } => self.apply_reset(from, to),
1508 GrantFeedEvent::Invalid { reason } => {
1509 self.transition(Readiness::Terminal);
1510 Applied::Terminal(reason)
1511 }
1512 }
1513 }
1514
1515 fn apply_baseline(
1516 &mut self,
1517 log: &GrantLogId,
1518 through: Revision,
1519 decisions: &[(GrantId, bool)],
1520 ) -> Applied {
1521 if *log != self.log {
1522 return Applied::Refused("baseline from another log");
1523 }
1524 if decisions.len() > MAX_DECISIONS_PER_EVENT {
1525 return Applied::Refused("baseline over its bound");
1526 }
1527 if self.applied.is_some_and(|applied| through < applied) {
1528 return Applied::Refused("baseline older than the applied cursor");
1529 }
1530 for (grant, revoked) in decisions {
1531 bump(&mut self.counts.rows_read);
1532 self.merge_decision(grant, *revoked, through, true);
1533 }
1534 self.applied = Some(through);
1535 self.raise_high_water(through);
1536 Applied::Baseline
1537 }
1538
1539 fn apply_reset(&mut self, from: &GrantLogId, to: &GrantLogId) -> Applied {
1540 if *from != self.log {
1541 return Applied::Refused("reset from a log not consumed here");
1542 }
1543 self.log = to.clone();
1544 self.decisions.clear();
1547 self.evidence.clear();
1548 self.orphans.clear();
1549 self.applied = None;
1550 self.high_water = None;
1551 self.pending_resume = true;
1552 self.transition(Readiness::Reset);
1553 Applied::Reset
1554 }
1555
1556 fn prune_orphans(&mut self) {
1557 if self.inflight_asks > 0 {
1558 return;
1559 }
1560 for grant in std::mem::take(&mut self.orphans) {
1561 if self.index.contains_key(&grant) {
1562 continue;
1563 }
1564 if self
1565 .decisions
1566 .get(&grant)
1567 .is_some_and(|decision| !decision.baseline)
1568 {
1569 self.decisions.remove(&grant);
1570 bump(&mut self.counts.decisions_pruned);
1571 }
1572 }
1573 }
1574
1575 fn finish_ask(&mut self) {
1576 self.inflight_asks = self.inflight_asks.saturating_sub(1);
1577 self.prune_orphans();
1578 }
1579
1580 fn release(&mut self, pid: u64) -> bool {
1581 let Some(bound) = self.connections.remove(&pid) else {
1582 return false;
1583 };
1584 for grant in bound.binding.grants() {
1585 if let Some(pids) = self.index.get_mut(grant) {
1586 pids.remove(&pid);
1587 if pids.is_empty() {
1588 self.index.remove(grant);
1589 self.orphans.insert(grant.clone());
1590 }
1591 }
1592 }
1593 bump(&mut self.counts.cleanups);
1594 self.prune_orphans();
1595 true
1596 }
1597}
1598
1599pub struct StagedAdmission {
1603 binding: Arc<GrantDecisionBinding>,
1604 inner: Arc<Mutex<Inner>>,
1605 registered: bool,
1606}
1607
1608impl fmt::Debug for StagedAdmission {
1609 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1610 formatter
1611 .debug_struct("StagedAdmission")
1612 .field("binding", &self.binding)
1613 .finish_non_exhaustive()
1614 }
1615}
1616
1617impl Drop for StagedAdmission {
1618 fn drop(&mut self) {
1619 if self.registered
1620 && let Ok(mut inner) = self.inner.lock()
1621 {
1622 inner.finish_ask();
1623 }
1624 }
1625}
1626
1627pub struct RevocationAuthority {
1630 pub(crate) link_life: crate::link::LinkRegistry,
1631 policy: RevocationPolicy,
1632 bindings: Arc<dyn GrantBindingSource>,
1633 decisions: Arc<dyn GrantDecisionSource>,
1634 inner: Arc<Mutex<Inner>>,
1635 hot: Arc<HotCounts>,
1636}
1637
1638impl fmt::Debug for RevocationAuthority {
1639 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1640 formatter
1641 .debug_struct("RevocationAuthority")
1642 .field("log", &self.policy.log)
1643 .finish_non_exhaustive()
1644 }
1645}
1646
1647impl RevocationAuthority {
1648 #[must_use]
1651 pub fn new(
1652 policy: RevocationPolicy,
1653 bindings: Arc<dyn GrantBindingSource>,
1654 decisions: Arc<dyn GrantDecisionSource>,
1655 ) -> Self {
1656 let state = Inner::new(policy.log.clone());
1657 let link_life = state.link_life.clone();
1658 let inner = Arc::new(Mutex::new(state));
1659 Self {
1660 policy,
1661 link_life,
1662 bindings,
1663 decisions,
1664 inner,
1665 hot: Arc::new(HotCounts::default()),
1666 }
1667 }
1668
1669 #[must_use]
1672 pub fn unpublished(policy: RevocationPolicy) -> Self {
1673 Self::new(
1674 policy,
1675 Arc::new(UnpublishedGrantBinding),
1676 Arc::new(UnpublishedGrantDecisions),
1677 )
1678 }
1679
1680 fn lock(&self) -> Result<MutexGuard<'_, Inner>, RevocationRefusal> {
1681 self.inner
1682 .lock()
1683 .map_err(|_| RevocationRefusal::StreamUnavailable)
1684 }
1685
1686 fn refused(&self, refusal: RevocationRefusal) -> RevocationRefusal {
1687 if let Ok(mut inner) = self.inner.lock() {
1688 bump(&mut inner.counts.admissions_refused);
1689 }
1690 refusal
1691 }
1692
1693 pub fn stage(
1701 &self,
1702 credential: &[u8],
1703 holder: &[u8],
1704 ) -> Result<StagedAdmission, RevocationRefusal> {
1705 let binding = verify_binding(&self.policy, self.bindings.as_ref(), credential, holder)
1706 .map_err(|refusal| self.refused(refusal))?;
1707 let binding = Arc::new(binding);
1708 let (needed, at_least) = {
1709 let mut inner = self.lock()?;
1710 if let Err(refusal) = inner.precheck(&binding) {
1711 bump(&mut inner.counts.admissions_refused);
1712 return Err(refusal);
1713 }
1714 inner.inflight_asks = inner.inflight_asks.saturating_add(1);
1715 let needed = inner.missing_grants(&binding);
1716 let at_least = inner.applied;
1717 drop(inner);
1718 (needed, at_least)
1719 };
1720 let staged = StagedAdmission {
1721 binding,
1722 inner: Arc::clone(&self.inner),
1723 registered: true,
1724 };
1725 if needed.is_empty() {
1726 return Ok(staged);
1727 }
1728 let at_least =
1729 at_least.ok_or_else(|| self.refused(RevocationRefusal::RevisionUnavailable))?;
1730 let reply = self
1731 .decisions
1732 .decide(&staged.binding.log, &needed, at_least);
1733 let installed = {
1734 let mut inner = self.lock()?;
1735 bump(&mut inner.counts.decision_asks);
1736 let installed = reply.and_then(|reply| inner.install_reply(&reply, at_least));
1737 if installed.is_err() {
1738 bump(&mut inner.counts.admissions_refused);
1739 }
1740 drop(inner);
1741 installed
1742 };
1743 installed.map(|()| staged)
1744 }
1745
1746 pub fn commit(
1754 &self,
1755 pid: u64,
1756 mut staged: StagedAdmission,
1757 ) -> Result<ConnectionGrants, RevocationRefusal> {
1758 if !Arc::ptr_eq(&staged.inner, &self.inner) {
1759 return Err(self.refused(RevocationRefusal::BindingMissing));
1760 }
1761 staged.registered = false;
1762 let mut inner = self.lock()?;
1763 let bound = inner.bind(pid, &staged.binding, &self.hot);
1764 if bound.is_err() {
1765 bump(&mut inner.counts.admissions_refused);
1766 }
1767 inner.finish_ask();
1768 drop(inner);
1769 bound
1770 }
1771
1772 pub fn admit(
1777 &self,
1778 pid: u64,
1779 credential: &[u8],
1780 holder: &[u8],
1781 ) -> Result<ConnectionGrants, RevocationRefusal> {
1782 let staged = self.stage(credential, holder)?;
1783 self.commit(pid, staged)
1784 }
1785
1786 #[must_use]
1789 pub fn apply(&self, event: &GrantFeedEvent) -> ApplyOutcome {
1790 let mut effects = Vec::new();
1791 let Ok(mut inner) = self.inner.lock() else {
1792 return ApplyOutcome {
1793 applied: Applied::Terminal("revocation authority lock poisoned"),
1794 effects,
1795 };
1796 };
1797 let before = inner.transitions_dropped;
1798 inner.announce = None;
1799 let applied = inner.apply(event, &mut effects);
1800 if matches!(applied, Applied::Refused(_)) {
1801 bump(&mut inner.counts.refused_events);
1802 }
1803 let announce = inner.announce.take();
1804 let dropped = inner.transitions_dropped > before;
1805 drop(inner);
1806 announce_outcome(applied, announce, dropped);
1807 ApplyOutcome { applied, effects }
1808 }
1809
1810 #[must_use]
1814 pub fn release(&self, pid: u64) -> bool {
1815 let Ok(mut inner) = self.inner.lock() else {
1816 tracing::error!(
1817 pid,
1818 "revocation authority lock poisoned; the binding is not released"
1819 );
1820 return false;
1821 };
1822 let released = inner.release(pid);
1823 drop(inner);
1824 self.link_life.forget(pid);
1825 released
1826 }
1827
1828 pub fn record_close_publication_refused(&self, pid: u64) {
1832 if let Ok(mut inner) = self.inner.lock() {
1833 bump(&mut inner.counts.close_publication_refused);
1834 }
1835 tracing::warn!(
1836 connection_pid = pid,
1837 "revocation close control could not be published; the connection's wake delivers it"
1838 );
1839 }
1840
1841 pub fn record_close_write(&self, pid: u64, write: CloseWrite) {
1844 if let Ok(mut inner) = self.inner.lock() {
1845 bump(&mut inner.counts.closes_attempted);
1846 match write {
1847 CloseWrite::Flushed => bump(&mut inner.counts.closes_flushed),
1848 CloseWrite::TransportFailed | CloseWrite::NoStream => {
1849 bump(&mut inner.counts.close_transport_failures);
1850 }
1851 CloseWrite::Residue | CloseWrite::NotQueued => {}
1852 }
1853 }
1854 if matches!(write, CloseWrite::TransportFailed | CloseWrite::NoStream) {
1855 tracing::warn!(
1856 connection_pid = pid,
1857 write = ?write,
1858 "revocation close could not be written; the denied state is kept and cleanup finishes"
1859 );
1860 }
1861 }
1862
1863 pub fn prune_unreferenced(&self) {
1866 let Ok(mut inner) = self.inner.lock() else {
1867 return;
1868 };
1869 if inner.inflight_asks > 0 {
1870 return;
1871 }
1872 let unreferenced: Vec<GrantId> = inner
1873 .decisions
1874 .keys()
1875 .filter(|grant| !inner.index.contains_key(*grant))
1876 .cloned()
1877 .collect();
1878 for grant in unreferenced {
1879 inner.decisions.remove(&grant);
1880 bump(&mut inner.counts.decisions_pruned);
1881 }
1882 inner.orphans.clear();
1883 }
1884
1885 pub fn diagnostics(&self) -> Result<RevocationDiagnostics, AuthorityPoisoned> {
1891 let inner = self.inner.lock().map_err(|_| AuthorityPoisoned)?;
1892 let snapshot = RevocationDiagnostics {
1893 log: inner.log.clone(),
1894 readiness: inner.readiness,
1895 reason: inner.readiness.refusal(),
1896 applied: inner.applied,
1897 high_water: inner.high_water,
1898 missing: inner.missing(),
1899 resume_generation: inner.resume_generation,
1900 settled_generation: inner.settled_generation,
1901 bound_connections: inner.connections.len(),
1902 retained_decisions: inner.decisions.len(),
1903 index_entries: inner.index.values().map(BTreeSet::len).sum(),
1904 inflight_asks: inner.inflight_asks,
1905 counts: inner.counts,
1906 hot_allowed: self.hot.allowed.load(Ordering::Relaxed),
1907 hot_refused: self.hot.refused.load(Ordering::Relaxed),
1908 transitions: inner.transitions.iter().copied().collect(),
1909 transitions_dropped: inner.transitions_dropped,
1910 };
1911 drop(inner);
1912 Ok(snapshot)
1913 }
1914}
1915
1916fn announce_outcome(applied: Applied, transition: Option<Transition>, dropped: bool) {
1920 if let Some(transition) = transition {
1921 match transition.readiness {
1922 Readiness::Ready => tracing::info!(
1923 applied = ?transition.applied.map(Revision::get),
1924 rechecks = transition.rechecks,
1925 "revocation stream ready"
1926 ),
1927 Readiness::NotReady
1928 | Readiness::Gap
1929 | Readiness::Unavailable
1930 | Readiness::Reset
1931 | Readiness::Terminal => tracing::warn!(
1932 readiness = ?transition.readiness,
1933 reason = ?transition.reason.map(RevocationRefusal::name),
1934 applied = ?transition.applied.map(Revision::get),
1935 missing = ?transition.missing,
1936 refused_admissions = transition.refused_admissions,
1937 rechecks = transition.rechecks,
1938 "revocation stream lost readiness; new admissions are refused by name"
1939 ),
1940 }
1941 }
1942 if dropped {
1943 tracing::warn!("revocation diagnostic transition ring overflowed; the oldest was dropped");
1944 }
1945 match applied {
1946 Applied::Refused(reason) => {
1947 tracing::warn!(reason, "revocation stream event refused");
1948 }
1949 Applied::Terminal(reason) => {
1950 tracing::error!(reason, "revocation stream ended on a terminal error");
1951 }
1952 Applied::Baseline
1953 | Applied::Change
1954 | Applied::Duplicate
1955 | Applied::Watermark
1956 | Applied::Gap
1957 | Applied::Ready
1958 | Applied::Lost
1959 | Applied::Reset => {}
1960 }
1961}