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 log: GrantLogId,
1073 readiness: Readiness,
1074 applied: Option<Revision>,
1075 high_water: Option<Revision>,
1076 pending_resume: bool,
1077 resume_generation: u64,
1078 settled_generation: u64,
1079 decisions: BTreeMap<GrantId, Decision>,
1080 evidence: VecDeque<(Revision, Digest32)>,
1081 connections: BTreeMap<u64, Bound>,
1082 index: BTreeMap<GrantId, BTreeSet<u64>>,
1083 orphans: BTreeSet<GrantId>,
1084 inflight_asks: u64,
1085 counts: RevocationCounts,
1086 transitions: VecDeque<Transition>,
1087 transitions_dropped: u64,
1088 announce: Option<Transition>,
1089}
1090
1091const fn bump(counter: &mut u64) {
1092 *counter = counter.saturating_add(1);
1093}
1094
1095impl Inner {
1096 fn new(log: GrantLogId) -> Self {
1097 Self {
1098 log,
1099 readiness: Readiness::NotReady,
1100 applied: None,
1101 high_water: None,
1102 pending_resume: false,
1103 resume_generation: 0,
1104 settled_generation: 0,
1105 decisions: BTreeMap::new(),
1106 evidence: VecDeque::new(),
1107 connections: BTreeMap::new(),
1108 index: BTreeMap::new(),
1109 orphans: BTreeSet::new(),
1110 inflight_asks: 0,
1111 counts: RevocationCounts::default(),
1112 transitions: VecDeque::new(),
1113 transitions_dropped: 0,
1114 announce: None,
1115 }
1116 }
1117
1118 fn missing(&self) -> GapLength {
1119 if self.readiness == Readiness::Ready {
1120 return GapLength::None;
1121 }
1122 match (self.applied, self.high_water) {
1123 (Some(applied), Some(high)) => match missing_between(applied, high) {
1124 Some(missing) if missing > 0 => GapLength::Known(missing),
1125 _ => GapLength::None,
1126 },
1127 _ => GapLength::Unknown,
1130 }
1131 }
1132
1133 fn transition(&mut self, readiness: Readiness) {
1136 if self.readiness == readiness {
1137 return;
1138 }
1139 self.readiness = readiness;
1140 match readiness {
1141 Readiness::Gap => bump(&mut self.counts.gap_transitions),
1142 Readiness::Unavailable => bump(&mut self.counts.loss_transitions),
1143 Readiness::Reset => bump(&mut self.counts.reset_transitions),
1144 Readiness::Terminal => bump(&mut self.counts.terminal_errors),
1145 Readiness::NotReady | Readiness::Ready => {}
1146 }
1147 let transition = Transition {
1148 readiness,
1149 reason: readiness.refusal(),
1150 applied: self.applied,
1151 missing: self.missing(),
1152 refused_admissions: self.counts.admissions_refused,
1153 rechecks: self.counts.resume_rechecks,
1154 };
1155 if self.transitions.len() >= MAX_DIAGNOSTIC_EVENTS {
1156 self.transitions.pop_front();
1157 bump(&mut self.transitions_dropped);
1158 }
1159 self.transitions.push_back(transition);
1160 self.announce = Some(transition);
1161 }
1162
1163 fn precheck(&self, binding: &GrantDecisionBinding) -> Result<(), RevocationRefusal> {
1164 if let Some(refusal) = self.readiness.refusal() {
1165 return Err(refusal);
1166 }
1167 if binding.log != self.log {
1168 return Err(RevocationRefusal::LogReset);
1169 }
1170 if self
1171 .applied
1172 .is_none_or(|applied| applied < binding.required)
1173 {
1174 return Err(RevocationRefusal::RevisionUnavailable);
1175 }
1176 Ok(())
1177 }
1178
1179 fn missing_grants(&self, binding: &GrantDecisionBinding) -> Vec<GrantId> {
1180 let unknown: BTreeSet<&GrantId> = binding
1181 .grants()
1182 .filter(|grant| !self.decisions.contains_key(*grant))
1183 .collect();
1184 unknown.into_iter().cloned().collect()
1185 }
1186
1187 fn right_state(&self, right: &BoundRight) -> RightState {
1188 let mut unknown = false;
1189 for chain in &right.chains {
1190 let mut chain_state = RightState::Allowed;
1191 for grant in chain {
1192 match self.decisions.get(grant) {
1193 Some(decision) if decision.revoked => {
1194 chain_state = RightState::Lost;
1195 break;
1196 }
1197 Some(_) => {}
1198 None => chain_state = RightState::Unknown,
1199 }
1200 }
1201 match chain_state {
1202 RightState::Allowed => return RightState::Allowed,
1203 RightState::Unknown => unknown = true,
1204 RightState::Lost => {}
1205 }
1206 }
1207 if unknown {
1208 RightState::Unknown
1209 } else {
1210 RightState::Lost
1211 }
1212 }
1213
1214 fn merge_decision(&mut self, grant: &GrantId, revoked: bool, at: Revision, baseline: bool) {
1217 match self.decisions.get_mut(grant) {
1218 Some(existing) if existing.revoked => {
1219 if !revoked {
1220 bump(&mut self.counts.stale_overwrites_prevented);
1221 }
1222 existing.baseline |= baseline;
1223 }
1224 Some(existing) if existing.at >= at && !revoked => {
1225 existing.baseline |= baseline;
1226 }
1227 Some(existing) => {
1228 existing.revoked = revoked;
1229 existing.at = at;
1230 existing.baseline |= baseline;
1231 }
1232 None => {
1233 self.decisions.insert(
1234 grant.clone(),
1235 Decision {
1236 revoked,
1237 at,
1238 baseline,
1239 },
1240 );
1241 if !baseline && !self.index.contains_key(grant) {
1242 self.orphans.insert(grant.clone());
1243 }
1244 }
1245 }
1246 }
1247
1248 fn install_reply(
1249 &mut self,
1250 reply: &DecisionReply,
1251 at_least: Revision,
1252 ) -> Result<(), RevocationRefusal> {
1253 if reply.log != self.log {
1254 return Err(RevocationRefusal::LogReset);
1255 }
1256 if reply.revision < at_least || reply.decisions.len() > MAX_DECISIONS_PER_EVENT {
1257 bump(&mut self.counts.stale_replies_refused);
1258 return Err(RevocationRefusal::RevisionUnavailable);
1259 }
1260 for (grant, revoked) in &reply.decisions {
1261 bump(&mut self.counts.rows_read);
1262 self.merge_decision(grant, *revoked, reply.revision, false);
1263 }
1264 Ok(())
1265 }
1266
1267 fn bind(
1269 &mut self,
1270 pid: u64,
1271 binding: &Arc<GrantDecisionBinding>,
1272 hot: &Arc<HotCounts>,
1273 ) -> Result<ConnectionGrants, RevocationRefusal> {
1274 self.precheck(binding)?;
1275 if self.connections.contains_key(&pid) {
1276 bump(&mut self.counts.binding_replacements_refused);
1277 tracing::warn!(
1278 connection_pid = pid,
1279 "a second grant-decision binding was offered; the first is kept"
1280 );
1281 return Err(RevocationRefusal::BindingMissing);
1282 }
1283 let mut lost = Vec::with_capacity(binding.rights.len());
1284 for (index, right) in binding.rights.iter().enumerate() {
1285 match (self.right_state(right), index == binding.admitting) {
1286 (RightState::Allowed, _) => lost.push(AtomicBool::new(false)),
1287 (RightState::Lost, true) => return Err(RevocationRefusal::GrantRevoked),
1288 (RightState::Lost, false) => lost.push(AtomicBool::new(true)),
1289 (RightState::Unknown, _) => return Err(RevocationRefusal::RevisionUnavailable),
1290 }
1291 }
1292 let live = Arc::new(LiveRights {
1293 lost: lost.into_boxed_slice(),
1294 close: AtomicU8::new(CLOSE_NONE),
1295 changes: AtomicU64::new(0),
1296 });
1297 for grant in binding.grants() {
1298 self.index.entry(grant.clone()).or_default().insert(pid);
1299 self.orphans.remove(grant);
1300 }
1301 self.connections.insert(
1302 pid,
1303 Bound {
1304 binding: Arc::clone(binding),
1305 live: Arc::clone(&live),
1306 },
1307 );
1308 bump(&mut self.counts.admissions_committed);
1309 Ok(ConnectionGrants {
1310 pid,
1311 binding: Arc::clone(binding),
1312 live,
1313 hot: Arc::clone(hot),
1314 })
1315 }
1316
1317 fn evaluate(&mut self, pid: u64, effects: &mut Vec<RevocationEffect>) {
1320 let Some(bound) = self.connections.get(&pid) else {
1321 return;
1322 };
1323 let binding = Arc::clone(&bound.binding);
1324 let live = Arc::clone(&bound.live);
1325 if live.close.load(Ordering::Acquire) != CLOSE_NONE {
1326 return;
1327 }
1328 if binding.log != self.log {
1329 self.close(pid, &live, RevocationRefusal::LogReset, effects);
1330 return;
1331 }
1332 for (index, (right, lost)) in binding.rights.iter().zip(live.lost.iter()).enumerate() {
1333 if lost.load(Ordering::Acquire) || self.right_state(right) == RightState::Allowed {
1334 continue;
1335 }
1336 lost.store(true, Ordering::Release);
1338 live.changes.fetch_add(1, Ordering::AcqRel);
1339 if index == binding.admitting {
1340 self.close(pid, &live, RevocationRefusal::GrantRevoked, effects);
1341 return;
1342 }
1343 bump(&mut self.counts.withdrawals);
1344 effects.push(RevocationEffect::Withdraw { pid, right: index });
1345 }
1346 }
1347
1348 fn close(
1349 &mut self,
1350 pid: u64,
1351 live: &LiveRights,
1352 reason: RevocationRefusal,
1353 effects: &mut Vec<RevocationEffect>,
1354 ) {
1355 live.close.store(close_code(reason), Ordering::Release);
1356 live.changes.fetch_add(1, Ordering::AcqRel);
1357 bump(&mut self.counts.closes_decided);
1358 effects.push(RevocationEffect::Close { pid, reason });
1359 }
1360
1361 fn remember_evidence(&mut self, revision: Revision, evidence: Digest32) {
1362 if self.evidence.len() >= EVIDENCE_RETAINED {
1363 self.evidence.pop_front();
1364 }
1365 self.evidence.push_back((revision, evidence));
1366 }
1367
1368 fn raise_high_water(&mut self, revision: Revision) {
1369 if self.high_water.is_none_or(|high| high < revision) {
1370 self.high_water = Some(revision);
1371 }
1372 }
1373
1374 fn apply_change(
1375 &mut self,
1376 revision: Revision,
1377 change: &GrantFeedChange,
1378 evidence: Digest32,
1379 effects: &mut Vec<RevocationEffect>,
1380 ) -> Applied {
1381 self.raise_high_water(revision);
1382 let Some(applied) = self.applied else {
1383 return Applied::Refused("change before baseline");
1384 };
1385 if revision <= applied {
1386 let conflicting = self
1387 .evidence
1388 .iter()
1389 .any(|(seen, digest)| *seen == revision && *digest != evidence);
1390 if conflicting {
1391 self.transition(Readiness::Terminal);
1392 return Applied::Terminal("conflicting_evidence");
1393 }
1394 bump(&mut self.counts.duplicate_revisions);
1395 return Applied::Duplicate;
1396 }
1397 if applied.next() != Some(revision) {
1398 self.pending_resume = true;
1399 self.transition(Readiness::Gap);
1400 return Applied::Gap;
1401 }
1402 self.applied = Some(revision);
1403 self.remember_evidence(revision, evidence);
1404 bump(&mut self.counts.applied_revisions);
1405 match change {
1406 GrantFeedChange::Issue(grant) => {
1407 if self.decisions.contains_key(grant) || self.index.contains_key(grant) {
1408 self.merge_decision(grant, false, revision, false);
1409 }
1410 }
1411 GrantFeedChange::Revoke(grant) => {
1412 bump(&mut self.counts.observed_revocations);
1413 let referenced = self.index.contains_key(grant);
1414 if referenced || self.inflight_asks > 0 || self.decisions.contains_key(grant) {
1415 self.merge_decision(grant, true, revision, false);
1418 }
1419 let affected: Vec<u64> = self
1420 .index
1421 .get(grant)
1422 .map(|pids| pids.iter().copied().collect())
1423 .unwrap_or_default();
1424 for pid in affected {
1425 bump(&mut self.counts.affected_visits);
1426 self.evaluate(pid, effects);
1427 }
1428 }
1429 }
1430 Applied::Change
1431 }
1432
1433 fn apply_ready(&mut self, through: Revision, effects: &mut Vec<RevocationEffect>) -> Applied {
1434 if self.applied != Some(through) {
1435 return Applied::Refused("ready does not match the applied cursor");
1436 }
1437 self.raise_high_water(through);
1438 if self.readiness == Readiness::Ready && !self.pending_resume {
1439 return Applied::Duplicate;
1440 }
1441 self.resume_generation = self.resume_generation.saturating_add(1);
1442 let open: Vec<u64> = self.connections.keys().copied().collect();
1443 for pid in open {
1444 bump(&mut self.counts.resume_rechecks);
1445 self.evaluate(pid, effects);
1446 }
1447 self.pending_resume = false;
1448 self.settled_generation = self.resume_generation;
1449 self.transition(Readiness::Ready);
1450 Applied::Ready
1451 }
1452
1453 fn apply(&mut self, event: &GrantFeedEvent, effects: &mut Vec<RevocationEffect>) -> Applied {
1454 if self.readiness == Readiness::Terminal {
1455 return Applied::Refused("stream is terminal");
1456 }
1457 match event {
1458 GrantFeedEvent::Baseline {
1459 log,
1460 through,
1461 decisions,
1462 } => self.apply_baseline(log, *through, decisions),
1463 GrantFeedEvent::Change {
1464 log,
1465 revision,
1466 change,
1467 evidence,
1468 } => {
1469 if *log != self.log {
1470 return Applied::Refused("change from another log");
1471 }
1472 self.apply_change(*revision, change, *evidence, effects)
1473 }
1474 GrantFeedEvent::Watermark { log, through } => {
1475 if *log != self.log {
1476 return Applied::Refused("watermark from another log");
1477 }
1478 self.raise_high_water(*through);
1479 match self.applied {
1480 None => Applied::Refused("watermark before baseline"),
1481 Some(applied) if *through <= applied => {
1482 bump(&mut self.counts.duplicate_revisions);
1483 Applied::Duplicate
1484 }
1485 Some(_) => {
1486 self.applied = Some(*through);
1487 bump(&mut self.counts.watermark_advances);
1488 Applied::Watermark
1489 }
1490 }
1491 }
1492 GrantFeedEvent::Ready { log, through } => {
1493 if *log != self.log {
1494 return Applied::Refused("ready from another log");
1495 }
1496 self.apply_ready(*through, effects)
1497 }
1498 GrantFeedEvent::Lost => {
1499 self.high_water = None;
1500 self.pending_resume = true;
1501 self.transition(Readiness::Unavailable);
1502 Applied::Lost
1503 }
1504 GrantFeedEvent::Reset { from, to } => self.apply_reset(from, to),
1505 GrantFeedEvent::Invalid { reason } => {
1506 self.transition(Readiness::Terminal);
1507 Applied::Terminal(reason)
1508 }
1509 }
1510 }
1511
1512 fn apply_baseline(
1513 &mut self,
1514 log: &GrantLogId,
1515 through: Revision,
1516 decisions: &[(GrantId, bool)],
1517 ) -> Applied {
1518 if *log != self.log {
1519 return Applied::Refused("baseline from another log");
1520 }
1521 if decisions.len() > MAX_DECISIONS_PER_EVENT {
1522 return Applied::Refused("baseline over its bound");
1523 }
1524 if self.applied.is_some_and(|applied| through < applied) {
1525 return Applied::Refused("baseline older than the applied cursor");
1526 }
1527 for (grant, revoked) in decisions {
1528 bump(&mut self.counts.rows_read);
1529 self.merge_decision(grant, *revoked, through, true);
1530 }
1531 self.applied = Some(through);
1532 self.raise_high_water(through);
1533 Applied::Baseline
1534 }
1535
1536 fn apply_reset(&mut self, from: &GrantLogId, to: &GrantLogId) -> Applied {
1537 if *from != self.log {
1538 return Applied::Refused("reset from a log not consumed here");
1539 }
1540 self.log = to.clone();
1541 self.decisions.clear();
1544 self.evidence.clear();
1545 self.orphans.clear();
1546 self.applied = None;
1547 self.high_water = None;
1548 self.pending_resume = true;
1549 self.transition(Readiness::Reset);
1550 Applied::Reset
1551 }
1552
1553 fn prune_orphans(&mut self) {
1554 if self.inflight_asks > 0 {
1555 return;
1556 }
1557 for grant in std::mem::take(&mut self.orphans) {
1558 if self.index.contains_key(&grant) {
1559 continue;
1560 }
1561 if self
1562 .decisions
1563 .get(&grant)
1564 .is_some_and(|decision| !decision.baseline)
1565 {
1566 self.decisions.remove(&grant);
1567 bump(&mut self.counts.decisions_pruned);
1568 }
1569 }
1570 }
1571
1572 fn finish_ask(&mut self) {
1573 self.inflight_asks = self.inflight_asks.saturating_sub(1);
1574 self.prune_orphans();
1575 }
1576
1577 fn release(&mut self, pid: u64) -> bool {
1578 let Some(bound) = self.connections.remove(&pid) else {
1579 return false;
1580 };
1581 for grant in bound.binding.grants() {
1582 if let Some(pids) = self.index.get_mut(grant) {
1583 pids.remove(&pid);
1584 if pids.is_empty() {
1585 self.index.remove(grant);
1586 self.orphans.insert(grant.clone());
1587 }
1588 }
1589 }
1590 bump(&mut self.counts.cleanups);
1591 self.prune_orphans();
1592 true
1593 }
1594}
1595
1596pub struct StagedAdmission {
1600 binding: Arc<GrantDecisionBinding>,
1601 inner: Arc<Mutex<Inner>>,
1602 registered: bool,
1603}
1604
1605impl fmt::Debug for StagedAdmission {
1606 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1607 formatter
1608 .debug_struct("StagedAdmission")
1609 .field("binding", &self.binding)
1610 .finish_non_exhaustive()
1611 }
1612}
1613
1614impl Drop for StagedAdmission {
1615 fn drop(&mut self) {
1616 if self.registered
1617 && let Ok(mut inner) = self.inner.lock()
1618 {
1619 inner.finish_ask();
1620 }
1621 }
1622}
1623
1624pub struct RevocationAuthority {
1627 policy: RevocationPolicy,
1628 bindings: Arc<dyn GrantBindingSource>,
1629 decisions: Arc<dyn GrantDecisionSource>,
1630 inner: Arc<Mutex<Inner>>,
1631 hot: Arc<HotCounts>,
1632}
1633
1634impl fmt::Debug for RevocationAuthority {
1635 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1636 formatter
1637 .debug_struct("RevocationAuthority")
1638 .field("log", &self.policy.log)
1639 .finish_non_exhaustive()
1640 }
1641}
1642
1643impl RevocationAuthority {
1644 #[must_use]
1647 pub fn new(
1648 policy: RevocationPolicy,
1649 bindings: Arc<dyn GrantBindingSource>,
1650 decisions: Arc<dyn GrantDecisionSource>,
1651 ) -> Self {
1652 let inner = Arc::new(Mutex::new(Inner::new(policy.log.clone())));
1653 Self {
1654 policy,
1655 bindings,
1656 decisions,
1657 inner,
1658 hot: Arc::new(HotCounts::default()),
1659 }
1660 }
1661
1662 #[must_use]
1665 pub fn unpublished(policy: RevocationPolicy) -> Self {
1666 Self::new(
1667 policy,
1668 Arc::new(UnpublishedGrantBinding),
1669 Arc::new(UnpublishedGrantDecisions),
1670 )
1671 }
1672
1673 fn lock(&self) -> Result<MutexGuard<'_, Inner>, RevocationRefusal> {
1674 self.inner
1675 .lock()
1676 .map_err(|_| RevocationRefusal::StreamUnavailable)
1677 }
1678
1679 fn refused(&self, refusal: RevocationRefusal) -> RevocationRefusal {
1680 if let Ok(mut inner) = self.inner.lock() {
1681 bump(&mut inner.counts.admissions_refused);
1682 }
1683 refusal
1684 }
1685
1686 pub fn stage(
1694 &self,
1695 credential: &[u8],
1696 holder: &[u8],
1697 ) -> Result<StagedAdmission, RevocationRefusal> {
1698 let binding = verify_binding(&self.policy, self.bindings.as_ref(), credential, holder)
1699 .map_err(|refusal| self.refused(refusal))?;
1700 let binding = Arc::new(binding);
1701 let (needed, at_least) = {
1702 let mut inner = self.lock()?;
1703 if let Err(refusal) = inner.precheck(&binding) {
1704 bump(&mut inner.counts.admissions_refused);
1705 return Err(refusal);
1706 }
1707 inner.inflight_asks = inner.inflight_asks.saturating_add(1);
1708 let needed = inner.missing_grants(&binding);
1709 let at_least = inner.applied;
1710 drop(inner);
1711 (needed, at_least)
1712 };
1713 let staged = StagedAdmission {
1714 binding,
1715 inner: Arc::clone(&self.inner),
1716 registered: true,
1717 };
1718 if needed.is_empty() {
1719 return Ok(staged);
1720 }
1721 let at_least =
1722 at_least.ok_or_else(|| self.refused(RevocationRefusal::RevisionUnavailable))?;
1723 let reply = self
1724 .decisions
1725 .decide(&staged.binding.log, &needed, at_least);
1726 let installed = {
1727 let mut inner = self.lock()?;
1728 bump(&mut inner.counts.decision_asks);
1729 let installed = reply.and_then(|reply| inner.install_reply(&reply, at_least));
1730 if installed.is_err() {
1731 bump(&mut inner.counts.admissions_refused);
1732 }
1733 drop(inner);
1734 installed
1735 };
1736 installed.map(|()| staged)
1737 }
1738
1739 pub fn commit(
1747 &self,
1748 pid: u64,
1749 mut staged: StagedAdmission,
1750 ) -> Result<ConnectionGrants, RevocationRefusal> {
1751 if !Arc::ptr_eq(&staged.inner, &self.inner) {
1752 return Err(self.refused(RevocationRefusal::BindingMissing));
1753 }
1754 staged.registered = false;
1755 let mut inner = self.lock()?;
1756 let bound = inner.bind(pid, &staged.binding, &self.hot);
1757 if bound.is_err() {
1758 bump(&mut inner.counts.admissions_refused);
1759 }
1760 inner.finish_ask();
1761 drop(inner);
1762 bound
1763 }
1764
1765 pub fn admit(
1770 &self,
1771 pid: u64,
1772 credential: &[u8],
1773 holder: &[u8],
1774 ) -> Result<ConnectionGrants, RevocationRefusal> {
1775 let staged = self.stage(credential, holder)?;
1776 self.commit(pid, staged)
1777 }
1778
1779 #[must_use]
1782 pub fn apply(&self, event: &GrantFeedEvent) -> ApplyOutcome {
1783 let mut effects = Vec::new();
1784 let Ok(mut inner) = self.inner.lock() else {
1785 return ApplyOutcome {
1786 applied: Applied::Terminal("revocation authority lock poisoned"),
1787 effects,
1788 };
1789 };
1790 let before = inner.transitions_dropped;
1791 inner.announce = None;
1792 let applied = inner.apply(event, &mut effects);
1793 if matches!(applied, Applied::Refused(_)) {
1794 bump(&mut inner.counts.refused_events);
1795 }
1796 let announce = inner.announce.take();
1797 let dropped = inner.transitions_dropped > before;
1798 drop(inner);
1799 announce_outcome(applied, announce, dropped);
1800 ApplyOutcome { applied, effects }
1801 }
1802
1803 #[must_use]
1807 pub fn release(&self, pid: u64) -> bool {
1808 let Ok(mut inner) = self.inner.lock() else {
1809 tracing::error!(
1810 pid,
1811 "revocation authority lock poisoned; the binding is not released"
1812 );
1813 return false;
1814 };
1815 inner.release(pid)
1816 }
1817
1818 pub fn record_close_publication_refused(&self, pid: u64) {
1822 if let Ok(mut inner) = self.inner.lock() {
1823 bump(&mut inner.counts.close_publication_refused);
1824 }
1825 tracing::warn!(
1826 connection_pid = pid,
1827 "revocation close control could not be published; the connection's wake delivers it"
1828 );
1829 }
1830
1831 pub fn record_close_write(&self, pid: u64, write: CloseWrite) {
1834 if let Ok(mut inner) = self.inner.lock() {
1835 bump(&mut inner.counts.closes_attempted);
1836 match write {
1837 CloseWrite::Flushed => bump(&mut inner.counts.closes_flushed),
1838 CloseWrite::TransportFailed | CloseWrite::NoStream => {
1839 bump(&mut inner.counts.close_transport_failures);
1840 }
1841 CloseWrite::Residue | CloseWrite::NotQueued => {}
1842 }
1843 }
1844 if matches!(write, CloseWrite::TransportFailed | CloseWrite::NoStream) {
1845 tracing::warn!(
1846 connection_pid = pid,
1847 write = ?write,
1848 "revocation close could not be written; the denied state is kept and cleanup finishes"
1849 );
1850 }
1851 }
1852
1853 pub fn prune_unreferenced(&self) {
1856 let Ok(mut inner) = self.inner.lock() else {
1857 return;
1858 };
1859 if inner.inflight_asks > 0 {
1860 return;
1861 }
1862 let unreferenced: Vec<GrantId> = inner
1863 .decisions
1864 .keys()
1865 .filter(|grant| !inner.index.contains_key(*grant))
1866 .cloned()
1867 .collect();
1868 for grant in unreferenced {
1869 inner.decisions.remove(&grant);
1870 bump(&mut inner.counts.decisions_pruned);
1871 }
1872 inner.orphans.clear();
1873 }
1874
1875 pub fn diagnostics(&self) -> Result<RevocationDiagnostics, AuthorityPoisoned> {
1881 let inner = self.inner.lock().map_err(|_| AuthorityPoisoned)?;
1882 let snapshot = RevocationDiagnostics {
1883 log: inner.log.clone(),
1884 readiness: inner.readiness,
1885 reason: inner.readiness.refusal(),
1886 applied: inner.applied,
1887 high_water: inner.high_water,
1888 missing: inner.missing(),
1889 resume_generation: inner.resume_generation,
1890 settled_generation: inner.settled_generation,
1891 bound_connections: inner.connections.len(),
1892 retained_decisions: inner.decisions.len(),
1893 index_entries: inner.index.values().map(BTreeSet::len).sum(),
1894 inflight_asks: inner.inflight_asks,
1895 counts: inner.counts,
1896 hot_allowed: self.hot.allowed.load(Ordering::Relaxed),
1897 hot_refused: self.hot.refused.load(Ordering::Relaxed),
1898 transitions: inner.transitions.iter().copied().collect(),
1899 transitions_dropped: inner.transitions_dropped,
1900 };
1901 drop(inner);
1902 Ok(snapshot)
1903 }
1904}
1905
1906fn announce_outcome(applied: Applied, transition: Option<Transition>, dropped: bool) {
1910 if let Some(transition) = transition {
1911 match transition.readiness {
1912 Readiness::Ready => tracing::info!(
1913 applied = ?transition.applied.map(Revision::get),
1914 rechecks = transition.rechecks,
1915 "revocation stream ready"
1916 ),
1917 Readiness::NotReady
1918 | Readiness::Gap
1919 | Readiness::Unavailable
1920 | Readiness::Reset
1921 | Readiness::Terminal => tracing::warn!(
1922 readiness = ?transition.readiness,
1923 reason = ?transition.reason.map(RevocationRefusal::name),
1924 applied = ?transition.applied.map(Revision::get),
1925 missing = ?transition.missing,
1926 refused_admissions = transition.refused_admissions,
1927 rechecks = transition.rechecks,
1928 "revocation stream lost readiness; new admissions are refused by name"
1929 ),
1930 }
1931 }
1932 if dropped {
1933 tracing::warn!("revocation diagnostic transition ring overflowed; the oldest was dropped");
1934 }
1935 match applied {
1936 Applied::Refused(reason) => {
1937 tracing::warn!(reason, "revocation stream event refused");
1938 }
1939 Applied::Terminal(reason) => {
1940 tracing::error!(reason, "revocation stream ended on a terminal error");
1941 }
1942 Applied::Baseline
1943 | Applied::Change
1944 | Applied::Duplicate
1945 | Applied::Watermark
1946 | Applied::Gap
1947 | Applied::Ready
1948 | Applied::Lost
1949 | Applied::Reset => {}
1950 }
1951}