use varta_vlp::{Frame, Status};
use crate::peer_cred::BeatOrigin;
pub const DEFAULT_CAPACITY: usize = 256;
pub const MAX_CAPACITY: usize = 4096;
const EVICTION_MULTIPLIER: u32 = 10;
pub const DEFAULT_EVICTION_SCAN_WINDOW: usize = 256;
pub const MIN_EVICTION_SCAN_WINDOW: usize = 1;
pub const MAX_EVICTION_SCAN_WINDOW: usize = MAX_CAPACITY;
const NONCE_WRAP_THRESHOLD: u64 = 1_048_576;
pub(crate) struct PidIndex(crate::probe_table::BoundedIndex<u32>);
pub(crate) use crate::probe_table::ProbeExhausted;
impl PidIndex {
#[allow(dead_code)]
pub(crate) const MAX_PROBE: usize = crate::probe_table::BoundedIndex::<u32>::MAX_PROBE;
pub(crate) fn new(capacity: usize) -> Self {
Self(crate::probe_table::BoundedIndex::new(capacity))
}
pub(crate) fn get(&self, pid: u32) -> Option<usize> {
self.0.get(pid)
}
pub(crate) fn insert(&mut self, pid: u32, slot_idx: usize) -> Result<(), ProbeExhausted> {
self.0.insert(pid, slot_idx)
}
pub(crate) fn remove(&mut self, pid: u32) -> Option<usize> {
self.0.remove(pid)
}
pub(crate) fn take_probe_exhausted(&mut self) -> u64 {
self.0.take_probe_exhausted()
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.0.len()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EvictionPolicy {
Strict,
Balanced,
}
#[derive(Clone, Copy, Debug)]
pub struct Slot {
pub(crate) pid: u32,
pub(crate) last_nonce: u64,
pub(crate) last_ns: u64,
pub(crate) status: Status,
pub(crate) origin: BeatOrigin,
pub(crate) pid_ns_inode: Option<u64>,
pub(crate) used: bool,
pub(crate) stall_emitted: bool,
}
impl Slot {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Update {
Inserted,
Refreshed,
OutOfOrder,
CapacityExceeded,
OriginConflict,
NamespaceConflict,
}
pub struct Tracker {
entries: Vec<Slot>,
len: usize,
pid_to_index: PidIndex,
evictions: u64,
capacity_exceeded: u64,
nonce_wraps: u64,
last_evicted_pid: Option<u32>,
eviction_policy: EvictionPolicy,
stall_emitted_count: usize,
eviction_scan_window: usize,
eviction_scan_cursor: usize,
eviction_scan_truncated: u64,
origin_conflicts: u64,
namespace_conflicts: u64,
invariant_violations: u64,
}
impl Default for Tracker {
fn default() -> Self {
Self::new(
DEFAULT_CAPACITY,
EvictionPolicy::Strict,
DEFAULT_EVICTION_SCAN_WINDOW,
)
}
}
impl Tracker {
pub fn new(
capacity: usize,
eviction_policy: EvictionPolicy,
eviction_scan_window: usize,
) -> Self {
let cap = capacity.min(MAX_CAPACITY);
let window = eviction_scan_window.clamp(MIN_EVICTION_SCAN_WINDOW, MAX_EVICTION_SCAN_WINDOW);
Tracker {
entries: Vec::with_capacity(cap),
len: 0,
pid_to_index: PidIndex::new(cap),
evictions: 0,
capacity_exceeded: 0,
nonce_wraps: 0,
last_evicted_pid: None,
eviction_policy,
stall_emitted_count: 0,
eviction_scan_window: window,
eviction_scan_cursor: 0,
eviction_scan_truncated: 0,
origin_conflicts: 0,
namespace_conflicts: 0,
invariant_violations: 0,
}
}
pub fn record(
&mut self,
frame: &Frame,
now_ns: u64,
threshold_ns: u64,
origin: BeatOrigin,
peer_pid_ns_inode: Option<u64>,
) -> Update {
let status = frame.status;
if let Some(idx) = self.pid_to_index.get(frame.pid) {
let Some(slot) = self.entries.get_mut(idx) else {
self.invariant_violations = self.invariant_violations.saturating_add(1);
let _ = self.pid_to_index.remove(frame.pid);
self.capacity_exceeded = self.capacity_exceeded.saturating_add(1);
return Update::CapacityExceeded;
};
if slot.used {
if slot.origin != origin {
self.origin_conflicts = self.origin_conflicts.saturating_add(1);
return Update::OriginConflict;
}
match (slot.pid_ns_inode, peer_pid_ns_inode) {
(Some(a), Some(b)) if a != b => {
self.namespace_conflicts = self.namespace_conflicts.saturating_add(1);
return Update::NamespaceConflict;
}
(Some(_), None) => {
self.namespace_conflicts = self.namespace_conflicts.saturating_add(1);
return Update::NamespaceConflict;
}
(None, Some(_)) => {
slot.pid_ns_inode = peer_pid_ns_inode;
}
_ => {}
}
if frame.nonce <= slot.last_nonce {
let wrap_lo = NONCE_WRAP_THRESHOLD;
let wrap_hi = u64::MAX.saturating_sub(NONCE_WRAP_THRESHOLD);
if slot.last_nonce >= wrap_hi && frame.nonce < wrap_lo {
slot.last_nonce = frame.nonce;
slot.last_ns = now_ns;
slot.status = status;
if slot.stall_emitted {
slot.stall_emitted = false;
self.stall_emitted_count = self.stall_emitted_count.saturating_sub(1);
}
self.nonce_wraps = self.nonce_wraps.saturating_add(1);
return Update::Refreshed;
}
return Update::OutOfOrder;
}
slot.last_nonce = frame.nonce;
slot.last_ns = now_ns;
slot.status = status;
if slot.stall_emitted {
slot.stall_emitted = false;
self.stall_emitted_count = self.stall_emitted_count.saturating_sub(1);
}
return Update::Refreshed;
}
}
if self.len >= self.entries.capacity() {
if let Some(evict_idx) = self.find_evictable_slot(now_ns, threshold_ns) {
let Some(&evicted_slot) = self.entries.get(evict_idx) else {
self.invariant_violations = self.invariant_violations.saturating_add(1);
self.capacity_exceeded = self.capacity_exceeded.saturating_add(1);
return Update::CapacityExceeded;
};
let _ = self.pid_to_index.remove(evicted_slot.pid);
let Some(slot_mut) = self.entries.get_mut(evict_idx) else {
self.invariant_violations = self.invariant_violations.saturating_add(1);
self.capacity_exceeded = self.capacity_exceeded.saturating_add(1);
return Update::CapacityExceeded;
};
*slot_mut = Slot {
pid: frame.pid,
last_nonce: frame.nonce,
last_ns: now_ns,
status,
origin,
pid_ns_inode: peer_pid_ns_inode,
used: true,
stall_emitted: false,
};
if self.pid_to_index.insert(frame.pid, evict_idx).is_err() {
if let Some(slot_mut) = self.entries.get_mut(evict_idx) {
*slot_mut = evicted_slot;
}
let _ = self.pid_to_index.insert(evicted_slot.pid, evict_idx);
self.capacity_exceeded = self.capacity_exceeded.saturating_add(1);
return Update::CapacityExceeded;
}
if evicted_slot.stall_emitted {
self.stall_emitted_count = self.stall_emitted_count.saturating_sub(1);
}
self.evictions = self.evictions.saturating_add(1);
self.last_evicted_pid = Some(evicted_slot.pid);
return Update::Inserted;
}
self.capacity_exceeded = self.capacity_exceeded.saturating_add(1);
return Update::CapacityExceeded;
}
let idx = self.len;
if self.pid_to_index.insert(frame.pid, idx).is_err() {
self.capacity_exceeded = self.capacity_exceeded.saturating_add(1);
return Update::CapacityExceeded;
}
self.entries.push(Slot {
pid: frame.pid,
last_nonce: frame.nonce,
last_ns: now_ns,
status,
origin,
pid_ns_inode: peer_pid_ns_inode,
used: true,
stall_emitted: false,
});
self.len += 1;
Update::Inserted
}
fn find_evictable_slot(&mut self, now_ns: u64, threshold_ns: u64) -> Option<usize> {
let evict_threshold = threshold_ns.saturating_mul(EVICTION_MULTIPLIER as u64);
if self.stall_emitted_count > 0 {
if let Some(idx) = self.scan_window(now_ns, evict_threshold, true) {
return Some(idx);
}
}
if self.eviction_policy == EvictionPolicy::Balanced {
if let Some(idx) = self.scan_window(now_ns, evict_threshold, false) {
return Some(idx);
}
}
self.eviction_scan_truncated = self.eviction_scan_truncated.saturating_add(1);
None
}
fn scan_window(
&mut self,
now_ns: u64,
evict_threshold: u64,
require_stall: bool,
) -> Option<usize> {
let n = self.len.min(self.entries.len());
if n == 0 {
return None;
}
let window = self.eviction_scan_window.min(n);
let start = self.eviction_scan_cursor % n;
for i in 0..window {
let idx = (start + i) % n;
let Some(slot) = self.entries.get(idx) else {
self.invariant_violations = self.invariant_violations.saturating_add(1);
continue;
};
let stale = now_ns.saturating_sub(slot.last_ns) > evict_threshold;
let qualifies = stale && (!require_stall || slot.stall_emitted);
if qualifies {
self.eviction_scan_cursor = (idx + 1) % n;
return Some(idx);
}
}
self.eviction_scan_cursor = (start + window) % n;
None
}
pub fn take_evictions(&mut self) -> u64 {
let count = self.evictions;
self.evictions = 0;
count
}
pub fn take_evicted_pid(&mut self) -> Option<u32> {
self.last_evicted_pid.take()
}
pub fn take_nonce_wraps(&mut self) -> u64 {
let count = self.nonce_wraps;
self.nonce_wraps = 0;
count
}
pub fn take_capacity_exceeded(&mut self) -> u64 {
let count = self.capacity_exceeded;
self.capacity_exceeded = 0;
count
}
pub fn len(&self) -> usize {
self.len
}
pub fn last_ns_of(&self, pid: u32) -> Option<u64> {
self.pid_to_index
.get(pid)
.and_then(|idx| self.entries.get(idx).map(|s| s.last_ns))
}
pub fn origin_of(&self, pid: u32) -> Option<BeatOrigin> {
self.pid_to_index
.get(pid)
.and_then(|idx| self.entries.get(idx))
.filter(|s| s.used)
.map(|s| s.origin)
}
pub fn pid_ns_inode_of(&self, pid: u32) -> Option<Option<u64>> {
self.pid_to_index
.get(pid)
.and_then(|idx| self.entries.get(idx))
.filter(|s| s.used)
.map(|s| s.pid_ns_inode)
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn drain_stalled_slots(
&mut self,
now_ns: u64,
threshold_ns: u64,
mut cb: impl FnMut(u32, u64, u64, BeatOrigin, Option<u64>),
) {
let upper = self.len.min(self.entries.len());
if upper < self.len {
self.invariant_violations = self.invariant_violations.saturating_add(1);
}
if let Some(slice) = self.entries.get_mut(..upper) {
for slot in slice {
if !slot.used || slot.stall_emitted {
continue;
}
if now_ns.saturating_sub(slot.last_ns) >= threshold_ns {
slot.stall_emitted = true;
self.stall_emitted_count = self.stall_emitted_count.saturating_add(1);
cb(
slot.pid,
slot.last_nonce,
slot.last_ns,
slot.origin,
slot.pid_ns_inode,
);
}
}
}
#[cfg(debug_assertions)]
self.debug_assert_stall_count();
}
pub fn take_origin_conflicts(&mut self) -> u64 {
let count = self.origin_conflicts;
self.origin_conflicts = 0;
count
}
pub fn take_namespace_conflicts(&mut self) -> u64 {
let count = self.namespace_conflicts;
self.namespace_conflicts = 0;
count
}
pub fn take_eviction_scan_truncated(&mut self) -> u64 {
let count = self.eviction_scan_truncated;
self.eviction_scan_truncated = 0;
count
}
pub fn take_invariant_violations(&mut self) -> u64 {
let count = self.invariant_violations;
self.invariant_violations = 0;
count
}
pub fn take_probe_exhausted(&mut self) -> u64 {
self.pid_to_index.take_probe_exhausted()
}
#[cfg(debug_assertions)]
fn debug_assert_stall_count(&self) {
let upper = self.len.min(self.entries.len());
let observed = self
.entries
.get(..upper)
.unwrap_or(&[])
.iter()
.filter(|s| s.stall_emitted)
.count();
debug_assert_eq!(
observed, self.stall_emitted_count,
"stall_emitted_count out of sync: observed {}, tracked {}",
observed, self.stall_emitted_count
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use varta_vlp::Frame;
fn frame(pid: u32, nonce: u64) -> Frame {
Frame::new(Status::Ok, pid, nonce, nonce, 0)
}
const ORIGIN: BeatOrigin = BeatOrigin::KernelAttested;
#[test]
fn find_evictable_slot_returns_none_when_no_stalls_emitted() {
let cap = 64;
let mut t = Tracker::new(cap, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 1_000;
for pid in 1u32..=(cap as u32) {
assert_eq!(
t.record(&frame(pid, 1), 0, threshold_ns, ORIGIN, None),
Update::Inserted
);
}
assert_eq!(t.len(), cap);
assert_eq!(t.stall_emitted_count, 0);
let now_ns = threshold_ns * 100;
let result = t.record(&frame(99_999, 1), now_ns, threshold_ns, ORIGIN, None);
assert_eq!(result, Update::CapacityExceeded);
assert_eq!(t.eviction_scan_cursor, 0);
}
#[test]
fn stall_counter_enables_eviction_after_drain() {
let cap = 8;
let mut t = Tracker::new(cap, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
for pid in 1u32..=(cap as u32) {
assert_eq!(
t.record(&frame(pid, 1), 0, threshold_ns, ORIGIN, None),
Update::Inserted
);
}
let now_ns = threshold_ns * 20;
let mut stalled = 0u32;
t.drain_stalled_slots(now_ns, threshold_ns, |_, _, _, _, _| stalled += 1);
assert_eq!(stalled, cap as u32);
assert_eq!(t.stall_emitted_count, cap);
let result = t.record(&frame(9_999, 1), now_ns, threshold_ns, ORIGIN, None);
assert_eq!(result, Update::Inserted);
assert_eq!(t.stall_emitted_count, cap - 1);
}
#[test]
fn stall_counter_decrements_on_refresh() {
let mut t = Tracker::new(4, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
assert_eq!(
t.record(&frame(1, 1), 0, threshold_ns, ORIGIN, None),
Update::Inserted
);
t.drain_stalled_slots(threshold_ns * 2, threshold_ns, |_, _, _, _, _| {});
assert_eq!(t.stall_emitted_count, 1);
assert_eq!(
t.record(&frame(1, 2), threshold_ns * 3, threshold_ns, ORIGIN, None),
Update::Refreshed
);
assert_eq!(t.stall_emitted_count, 0);
}
#[test]
fn find_evictable_slot_scan_is_bounded_to_window() {
let cap = MAX_CAPACITY;
let mut t = Tracker::new(cap, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
for pid in 1u32..=(cap as u32) {
assert_eq!(
t.record(&frame(pid, 1), 0, threshold_ns, ORIGIN, None),
Update::Inserted
);
}
let now_ns = threshold_ns * 20;
t.drain_stalled_slots(now_ns, threshold_ns, |_, _, _, _, _| {});
assert_eq!(t.stall_emitted_count, cap);
let window = t.eviction_scan_window;
let start_cursor = t.eviction_scan_cursor;
let _ = t.record(&frame(50_001, 1), now_ns, threshold_ns, ORIGIN, None);
let advanced = t.eviction_scan_cursor.wrapping_sub(start_cursor) % cap;
assert!(
advanced <= window,
"cursor advanced by {advanced}, expected ≤ {window}"
);
}
#[test]
fn eviction_scan_window_is_plumbed_through() {
let cap = 16;
let window = 4;
let mut t = Tracker::new(cap, EvictionPolicy::Strict, window);
assert_eq!(t.eviction_scan_window, window);
let threshold_ns = 100;
for pid in 1u32..=(cap as u32) {
assert_eq!(
t.record(&frame(pid, 1), 0, threshold_ns, ORIGIN, None),
Update::Inserted
);
}
let now_ns = threshold_ns * 20;
t.drain_stalled_slots(now_ns, threshold_ns, |_, _, _, _, _| {});
assert_eq!(t.stall_emitted_count, cap);
let start = t.eviction_scan_cursor;
let _ = t.record(&frame(9_999, 1), now_ns, threshold_ns, ORIGIN, None);
let advanced = t.eviction_scan_cursor.wrapping_sub(start) % cap;
assert!(
advanced <= window,
"cursor advanced {advanced}, expected ≤ {window} (configured window)"
);
}
#[test]
fn scan_window_cursor_wraps_correctly() {
let cap = 4;
let mut t = Tracker::new(cap, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
for pid in 1u32..=(cap as u32) {
assert_eq!(
t.record(&frame(pid, 1), 0, threshold_ns, ORIGIN, None),
Update::Inserted
);
}
for _ in 0..10 {
let _ = t.scan_window(50, 1_000_000, true);
}
assert!(t.eviction_scan_cursor < cap);
}
#[test]
fn stall_emitted_count_invariant_holds_across_random_ops() {
let mut t = Tracker::new(32, EvictionPolicy::Balanced, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
let mut now_ns: u64 = 0;
let mut s: u64 = 0xC0FFEE;
let mut next = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
for _ in 0..2000 {
let r = next() % 4;
now_ns = now_ns.saturating_add(20);
match r {
0 => {
let pid = (next() % 64) as u32 + 1;
let _ = t.record(&frame(pid, now_ns), now_ns, threshold_ns, ORIGIN, None);
}
1 => {
now_ns = now_ns.saturating_add(threshold_ns * 2);
t.drain_stalled_slots(now_ns, threshold_ns, |_, _, _, _, _| {});
}
_ => {
}
}
}
let observed = t.entries[..t.len]
.iter()
.filter(|s| s.stall_emitted)
.count();
assert_eq!(observed, t.stall_emitted_count);
}
#[test]
fn scan_truncated_counter_increments_on_dry_scan() {
let mut t = Tracker::new(32, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
for pid in 1u32..=32 {
assert_eq!(
t.record(&frame(pid, 1), 0, threshold_ns, ORIGIN, None),
Update::Inserted
);
}
let _ = t.record(
&frame(99_999, 1),
threshold_ns * 100,
threshold_ns,
ORIGIN,
None,
);
assert_eq!(t.take_eviction_scan_truncated(), 1);
assert_eq!(t.take_eviction_scan_truncated(), 0);
}
#[test]
fn origin_conflict_first_origin_wins() {
let mut t = Tracker::new(8, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
assert_eq!(
t.record(
&frame(7, 1),
10,
threshold_ns,
BeatOrigin::KernelAttested,
None
),
Update::Inserted
);
assert_eq!(
t.record(
&frame(7, 2),
20,
threshold_ns,
BeatOrigin::NetworkUnverified,
None,
),
Update::OriginConflict
);
assert_eq!(t.last_ns_of(7), Some(10));
assert_eq!(t.entries[0].last_nonce, 1);
assert_eq!(t.entries[0].origin, BeatOrigin::KernelAttested);
assert_eq!(t.take_origin_conflicts(), 1);
assert_eq!(t.take_origin_conflicts(), 0);
assert_eq!(
t.record(
&frame(7, 3),
30,
threshold_ns,
BeatOrigin::KernelAttested,
None
),
Update::Refreshed
);
}
#[test]
fn pid_index_insert_get_remove_roundtrip() {
let mut idx = PidIndex::new(16);
assert_eq!(idx.get(42), None);
idx.insert(42, 7).expect("insert");
assert_eq!(idx.get(42), Some(7));
idx.insert(42, 9).expect("update");
assert_eq!(idx.get(42), Some(9));
assert_eq!(idx.len(), 1);
assert_eq!(idx.remove(42), Some(9));
assert_eq!(idx.get(42), None);
assert_eq!(idx.len(), 0);
}
#[test]
fn pid_index_tombstone_reuse() {
let mut idx = PidIndex::new(64);
for pid in 1u32..=32 {
idx.insert(pid, pid as usize).expect("insert");
}
for pid in 1u32..=16 {
assert_eq!(idx.remove(pid), Some(pid as usize));
}
for pid in 17u32..=32 {
assert_eq!(idx.get(pid), Some(pid as usize));
}
for pid in 1u32..=16 {
idx.insert(pid, (pid + 100) as usize).expect("reinsert");
}
for pid in 1u32..=16 {
assert_eq!(idx.get(pid), Some((pid + 100) as usize));
}
for pid in 17u32..=32 {
assert_eq!(idx.get(pid), Some(pid as usize));
}
}
#[test]
fn pid_index_probe_exhaustion_returns_error() {
let mut idx = PidIndex::new(4);
for pid in 1u32..=8 {
idx.insert(pid, pid as usize).expect("fill");
}
let err = idx.insert(9999, 0).expect_err("must exhaust");
assert_eq!(err, ProbeExhausted);
assert_eq!(idx.take_probe_exhausted(), 1);
assert_eq!(idx.take_probe_exhausted(), 0);
}
#[test]
fn record_probe_exhaustion_surfaces_capacity_exceeded() {
let mut t = Tracker::new(32, EvictionPolicy::Balanced, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
let mut now = 0u64;
for pid in 1u32..=4096 {
now = now.saturating_add(1);
let _ = t.record(&frame(pid, 1), now, threshold_ns, ORIGIN, None);
}
assert_eq!(t.take_probe_exhausted(), 0);
}
#[test]
fn invariant_violations_stays_zero_under_random_ops() {
let mut t = Tracker::new(32, EvictionPolicy::Balanced, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
let mut now_ns: u64 = 0;
let mut s: u64 = 0xDEADBEEF;
let mut next = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
for _ in 0..4000 {
let r = next() % 4;
now_ns = now_ns.saturating_add(20);
match r {
0 => {
let pid = (next() % 96) as u32 + 1;
let _ = t.record(&frame(pid, now_ns), now_ns, threshold_ns, ORIGIN, None);
}
1 => {
now_ns = now_ns.saturating_add(threshold_ns * 2);
t.drain_stalled_slots(now_ns, threshold_ns, |_, _, _, _, _| {});
}
2 => {
let pid = (next() % 96) as u32 + 1;
let _ = t.last_ns_of(pid);
let _ = t.origin_of(pid);
}
_ => {}
}
}
assert_eq!(t.take_invariant_violations(), 0);
assert_eq!(t.take_probe_exhausted(), 0);
}
#[test]
fn drain_stalled_slots_emits_pinned_origin() {
let mut t = Tracker::new(4, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
assert_eq!(
t.record(
&frame(11, 1),
0,
threshold_ns,
BeatOrigin::KernelAttested,
None
),
Update::Inserted
);
assert_eq!(
t.record(
&frame(22, 1),
0,
threshold_ns,
BeatOrigin::NetworkUnverified,
None,
),
Update::Inserted
);
let mut seen: Vec<(u32, BeatOrigin)> = Vec::new();
t.drain_stalled_slots(threshold_ns * 2, threshold_ns, |pid, _, _, origin, _| {
seen.push((pid, origin));
});
seen.sort_by_key(|(p, _)| *p);
assert_eq!(
seen,
vec![
(11, BeatOrigin::KernelAttested),
(22, BeatOrigin::NetworkUnverified),
]
);
}
#[test]
fn namespace_conflict_blocks_rebind() {
let mut t = Tracker::new(8, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
assert_eq!(
t.record(
&frame(7, 1),
0,
threshold_ns,
BeatOrigin::KernelAttested,
Some(4026531836),
),
Update::Inserted
);
let r = t.record(
&frame(7, 2),
10,
threshold_ns,
BeatOrigin::KernelAttested,
Some(4026531840),
);
assert_eq!(r, Update::NamespaceConflict);
assert_eq!(t.pid_ns_inode_of(7), Some(Some(4026531836)));
assert_eq!(t.take_namespace_conflicts(), 1);
assert_eq!(t.take_namespace_conflicts(), 0);
}
#[test]
fn namespace_match_passes_through() {
let mut t = Tracker::new(8, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
let _ = t.record(
&frame(7, 1),
0,
threshold_ns,
BeatOrigin::KernelAttested,
Some(123),
);
let r = t.record(
&frame(7, 2),
10,
threshold_ns,
BeatOrigin::KernelAttested,
Some(123),
);
assert_eq!(r, Update::Refreshed);
assert_eq!(t.take_namespace_conflicts(), 0);
}
#[test]
fn namespace_some_to_none_is_conflict() {
let mut t = Tracker::new(8, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
let _ = t.record(
&frame(7, 1),
0,
threshold_ns,
BeatOrigin::KernelAttested,
Some(123),
);
let r = t.record(
&frame(7, 2),
10,
threshold_ns,
BeatOrigin::KernelAttested,
None,
);
assert_eq!(r, Update::NamespaceConflict);
assert_eq!(t.take_namespace_conflicts(), 1);
}
#[test]
fn namespace_none_to_some_upgrades_in_place() {
let mut t = Tracker::new(8, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
let _ = t.record(
&frame(7, 1),
0,
threshold_ns,
BeatOrigin::KernelAttested,
None,
);
assert_eq!(t.pid_ns_inode_of(7), Some(None));
let r = t.record(
&frame(7, 2),
10,
threshold_ns,
BeatOrigin::KernelAttested,
Some(999),
);
assert_eq!(r, Update::Refreshed);
assert_eq!(t.pid_ns_inode_of(7), Some(Some(999)));
assert_eq!(t.take_namespace_conflicts(), 0);
}
#[test]
fn namespace_both_none_is_match() {
let mut t = Tracker::new(8, EvictionPolicy::Strict, DEFAULT_EVICTION_SCAN_WINDOW);
let threshold_ns = 100;
let _ = t.record(
&frame(7, 1),
0,
threshold_ns,
BeatOrigin::KernelAttested,
None,
);
let r = t.record(
&frame(7, 2),
10,
threshold_ns,
BeatOrigin::KernelAttested,
None,
);
assert_eq!(r, Update::Refreshed);
assert_eq!(t.take_namespace_conflicts(), 0);
}
#[test]
fn pid_index_occupied_tracks_live_entries_under_churn() {
const CAP: usize = 32;
const PID_RANGE: u32 = 48; let mut idx = PidIndex::new(CAP);
let mut expected_live: u32 = 0;
let mut live_set = std::collections::HashSet::new();
for i in 0u32..2_000 {
let pid = i % PID_RANGE;
if live_set.contains(&pid) {
idx.remove(pid);
live_set.remove(&pid);
expected_live -= 1;
idx.insert(pid, pid as usize).expect("re-insert");
live_set.insert(pid);
expected_live += 1;
} else if expected_live < CAP as u32 {
idx.insert(pid, pid as usize).expect("fresh insert");
live_set.insert(pid);
expected_live += 1;
} else {
let victim = *live_set.iter().next().unwrap();
idx.remove(victim);
live_set.remove(&victim);
expected_live -= 1;
idx.insert(pid, pid as usize).expect("insert after evict");
live_set.insert(pid);
expected_live += 1;
}
assert_eq!(
idx.len(),
expected_live as usize,
"i={i} pid={pid}: occupied={} expected={expected_live}",
idx.len()
);
}
}
#[test]
fn pid_index_occupied_restored_on_tombstone_reuse() {
let mut idx = PidIndex::new(16);
idx.insert(42, 0).expect("first insert");
assert_eq!(idx.len(), 1);
idx.remove(42);
assert_eq!(idx.len(), 0);
idx.insert(42, 5).expect("reinsert via tombstone");
assert_eq!(
idx.len(),
1,
"reinsert via tombstone did not restore occupied to 1 (was {})",
idx.len()
);
}
}