#[cfg(test)]
use core::slice;
use core::{
cell::{Cell, UnsafeCell},
marker::PhantomData,
ptr,
};
#[cfg(test)]
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
thread, thread_local,
};
use crate::{
observe::ids,
runtime::consts::{RING_BUFFER_SIZE, RING_EVENTS},
transport::wire::{CodecError, Payload, WireEncode, WirePayload, require_exact_len},
};
#[repr(C)]
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct TapEvent {
pub ts: u32,
pub id: u16,
pub causal_key: u16,
pub arg0: u32,
pub arg1: u32,
pub arg2: u32,
}
impl TapEvent {
#[inline]
pub const fn with_arg0(mut self, arg0: u32) -> Self {
self.arg0 = arg0;
self
}
#[inline]
pub const fn with_arg1(mut self, arg1: u32) -> Self {
self.arg1 = arg1;
self
}
#[inline]
pub const fn with_arg2(mut self, arg2: u32) -> Self {
self.arg2 = arg2;
self
}
#[inline]
pub const fn with_causal_key(mut self, causal_key: u16) -> Self {
self.causal_key = causal_key;
self
}
#[inline]
pub const fn causal_role(self) -> u8 {
(self.causal_key >> 8) as u8
}
#[inline]
pub const fn causal_seq(self) -> u8 {
(self.causal_key & 0xFF) as u8
}
#[inline]
pub const fn make_causal_key(role: u8, seq: u8) -> u16 {
((role as u16) << 8) | (seq as u16)
}
#[inline]
pub const fn zero() -> Self {
Self {
ts: 0,
id: 0,
causal_key: 0,
arg0: 0,
arg1: 0,
arg2: 0,
}
}
}
impl WireEncode for TapEvent {
fn encoded_len(&self) -> Option<usize> {
Some(20)
}
fn encode_into(&self, out: &mut [u8]) -> Result<usize, CodecError> {
if out.len() < 20 {
return Err(CodecError::Truncated);
}
out[0..4].copy_from_slice(&self.ts.to_be_bytes());
out[4..6].copy_from_slice(&self.id.to_be_bytes());
out[6..8].copy_from_slice(&self.causal_key.to_be_bytes());
out[8..12].copy_from_slice(&self.arg0.to_be_bytes());
out[12..16].copy_from_slice(&self.arg1.to_be_bytes());
out[16..20].copy_from_slice(&self.arg2.to_be_bytes());
Ok(20)
}
}
impl WirePayload for TapEvent {
type Decoded<'a> = Self;
fn decode_payload<'a>(input: Payload<'a>) -> Result<Self::Decoded<'a>, CodecError> {
let bytes = input.as_bytes();
require_exact_len(bytes.len(), 20, "tap event payload length")?;
Ok(Self {
ts: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
id: u16::from_be_bytes([bytes[4], bytes[5]]),
causal_key: u16::from_be_bytes([bytes[6], bytes[7]]),
arg0: u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
arg1: u32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
arg2: u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]),
})
}
}
struct RingBuffer<'a> {
head: Cell<usize>,
storage: *mut TapEvent,
_marker: PhantomData<&'a mut [TapEvent]>,
_no_send_sync: PhantomData<*mut ()>,
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PolicyEventKind {
Abort,
Trap,
Annotate,
Effect,
EffectOk,
Commit,
TxAbort,
StateRestore,
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PolicySidHint {
None,
Arg0SessionNonZero,
Arg1SessionNonZero,
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct PolicyEventSpec {
id: u16,
pub(super) kind: PolicyEventKind,
sid_hint: PolicySidHint,
}
#[cfg(test)]
impl PolicyEventSpec {
#[inline]
const fn new(id: u16, kind: PolicyEventKind, sid_hint: PolicySidHint) -> Self {
Self { id, kind, sid_hint }
}
#[inline]
pub(super) const fn id(self) -> u16 {
self.id
}
#[inline]
#[cfg(test)]
pub(super) fn sid_hint_from_tap(self, event: TapEvent) -> Option<u32> {
match self.sid_hint {
PolicySidHint::None => None,
PolicySidHint::Arg0SessionNonZero => {
if event.arg0 != 0 {
Some(event.arg0)
} else {
None
}
}
PolicySidHint::Arg1SessionNonZero => {
if event.arg1 != 0 {
Some(event.arg1)
} else {
None
}
}
}
}
}
#[cfg(test)]
const POLICY_EVENT_SPECS: [PolicyEventSpec; 9] = [
PolicyEventSpec::new(
ids::POLICY_ABORT,
PolicyEventKind::Abort,
PolicySidHint::Arg1SessionNonZero,
),
PolicyEventSpec::new(
ids::POLICY_TRAP,
PolicyEventKind::Trap,
PolicySidHint::Arg1SessionNonZero,
),
PolicyEventSpec::new(
ids::POLICY_ANNOT,
PolicyEventKind::Annotate,
PolicySidHint::None,
),
PolicyEventSpec::new(
ids::POLICY_EFFECT,
PolicyEventKind::Effect,
PolicySidHint::None,
),
PolicyEventSpec::new(
ids::POLICY_RA_OK,
PolicyEventKind::EffectOk,
PolicySidHint::Arg1SessionNonZero,
),
PolicyEventSpec::new(
ids::POLICY_COMMIT,
PolicyEventKind::Commit,
PolicySidHint::Arg0SessionNonZero,
),
PolicyEventSpec::new(
ids::POLICY_TX_ABORT,
PolicyEventKind::TxAbort,
PolicySidHint::Arg0SessionNonZero,
),
PolicyEventSpec::new(
ids::POLICY_STATE_RESTORE,
PolicyEventKind::StateRestore,
PolicySidHint::Arg0SessionNonZero,
),
PolicyEventSpec::new(
ids::POLICY_AUDIT_DEFER,
PolicyEventKind::Annotate,
PolicySidHint::None,
),
];
#[cfg(test)]
#[inline]
pub(super) fn policy_event_spec(id: u16) -> Option<PolicyEventSpec> {
for spec in POLICY_EVENT_SPECS.iter() {
if spec.id() == id {
return Some(*spec);
}
}
None
}
#[cfg(test)]
struct TapEvents<'cursor, 'ring, T, F>
where
F: FnMut(TapEvent) -> Option<T>,
{
cursor: &'cursor mut usize,
index: usize,
head: usize,
storage: &'ring [TapEvent],
mapper: F,
}
#[cfg(test)]
impl<'cursor, 'ring, T, F> Iterator for TapEvents<'cursor, 'ring, T, F>
where
F: FnMut(TapEvent) -> Option<T>,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
while self.index < self.head {
let event = self.storage[self.index % RING_BUFFER_SIZE];
self.index += 1;
if let Some(mapped) = (self.mapper)(event) {
return Some(mapped);
}
}
None
}
}
#[cfg(test)]
impl<'cursor, 'ring, T, F> Drop for TapEvents<'cursor, 'ring, T, F>
where
F: FnMut(TapEvent) -> Option<T>,
{
fn drop(&mut self) {
*self.cursor = self.index;
}
}
struct GlobalTap {
#[cfg(not(test))]
ring: UnsafeCell<*mut TapRing<'static>>,
}
impl GlobalTap {
const fn new() -> Self {
Self {
#[cfg(not(test))]
ring: UnsafeCell::new(ptr::null_mut()),
}
}
fn with_ring<R>(&self, f: impl FnOnce(&TapRing<'static>) -> R) -> Option<R> {
#[cfg(test)]
let ptr = TEST_GLOBAL_TAP_RING.with(Cell::get);
#[cfg(not(test))]
let ptr = unsafe { *self.ring.get() };
if ptr.is_null() {
None
} else {
Some(unsafe { f(&*ptr) })
}
}
fn invoke_post(&self, event: &TapEvent) {
let _ = event;
}
}
static GLOBAL_TAP: GlobalTap = GlobalTap::new();
unsafe impl Sync for GlobalTap {}
#[cfg(test)]
thread_local! {
static TEST_GLOBAL_TAP_RING: Cell<*mut TapRing<'static>> = const { Cell::new(ptr::null_mut()) };
static TS_CHECKER: Cell<Option<fn(u32)>> = const { Cell::new(None) };
static CHECKER_STATE: UnsafeCell<CheckerState> = const { UnsafeCell::new(CheckerState::new()) };
static RING_STORAGE: UnsafeCell<[TapEvent; RING_EVENTS]> =
const { UnsafeCell::new([TapEvent::zero(); RING_EVENTS]) };
}
pub(crate) fn push(event: TapEvent) {
let _ = GLOBAL_TAP.with_ring(|ring| {
ring.push(event);
});
}
pub(crate) fn emit(ring: &TapRing<'_>, event: TapEvent) {
ring.push(event);
}
#[cfg(test)]
struct CheckerState {
owner: Option<u64>,
last_ts: u32,
violation: bool,
}
#[cfg(test)]
impl CheckerState {
const fn new() -> Self {
Self {
owner: None,
last_ts: 0,
violation: false,
}
}
}
#[cfg(test)]
fn current_ts_checker() -> Option<fn(u32)> {
TS_CHECKER.with(Cell::get)
}
#[cfg(test)]
fn swap_ts_checker(new: Option<fn(u32)>) -> Option<fn(u32)> {
TS_CHECKER.with(|checker| {
let previous = checker.get();
checker.set(new);
previous
})
}
#[cfg(test)]
fn with_checker_state<R>(f: impl FnOnce(&mut CheckerState) -> R) -> R {
CHECKER_STATE.with(|state| unsafe { f(&mut *state.get()) })
}
#[cfg(test)]
pub(crate) fn install_ts_checker(checker: Option<fn(u32)>) -> Option<fn(u32)> {
swap_ts_checker(checker)
}
#[cfg(test)]
fn current_thread_id_u64() -> u64 {
let mut hasher = DefaultHasher::new();
thread::current().id().hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::observe::events::RawEvent;
use static_assertions::assert_not_impl_any;
assert_not_impl_any!(TapRing<'static>: Send, Sync);
struct CheckerGuard;
impl CheckerGuard {
fn acquire() -> Self {
with_checker_state(|state| {
state.owner = Some(current_thread_id_u64());
});
Self
}
}
impl Drop for CheckerGuard {
fn drop(&mut self) {
with_checker_state(|state| {
state.owner = None;
});
}
}
fn with_ring_storage<R>(f: impl FnOnce(&'static mut [TapEvent; RING_EVENTS]) -> R) -> R {
RING_STORAGE.with(|storage| {
let storage = unsafe { &mut *storage.get() };
storage.fill(TapEvent::zero());
f(storage)
})
}
#[test]
fn tap_event_fixed_decoder_rejects_trailing_bytes() {
let event = TapEvent {
ts: 0x0102_0304,
id: 0x0506,
causal_key: 0x0708,
arg0: 0x1112_1314,
arg1: 0x2122_2324,
arg2: 0x3132_3334,
};
let mut encoded = [0u8; 21];
assert_eq!(event.encode_into(&mut encoded[..20]), Ok(20));
assert_eq!(
TapEvent::decode_payload(Payload::new(&encoded[..20])),
Ok(event)
);
assert_eq!(
TapEvent::decode_payload(Payload::new(&encoded)),
Err(CodecError::Invalid("tap event payload length"))
);
}
#[test]
fn head_wraps_without_losing_alignment() {
with_ring_storage(|storage| {
let ring = TapRing::from_storage(storage);
let base_id = 0x0200;
ring.infra.head.set(usize::MAX - 2);
for idx in 0..4 {
ring.push(
RawEvent::new(0, base_id + idx as u16)
.with_arg0(idx as u32)
.with_arg1(idx as u32),
);
}
let expected = (usize::MAX - 2).wrapping_add(4);
assert_eq!(ring.head(), expected);
let first_index = (usize::MAX - 2) % RING_BUFFER_SIZE;
let infra_offset = RING_BUFFER_SIZE;
for offset in 0..4 {
let idx = (first_index + offset) % RING_BUFFER_SIZE;
assert_eq!(storage[infra_offset + idx].id, base_id + offset as u16);
}
});
}
#[test]
fn timestamp_checker_detects_non_monotonic_push() {
let checker_guard = CheckerGuard::acquire();
core::hint::black_box(&checker_guard);
fn checker(ts: u32) {
with_checker_state(|state| {
if ts < state.last_ts {
state.violation = true;
}
state.last_ts = ts;
});
}
with_ring_storage(|storage| {
let ring = TapRing::from_storage(storage);
let previous = install_ts_checker(Some(checker));
with_checker_state(|state| {
state.last_ts = 0;
state.violation = false;
});
for ts in [1, 2, 3, 3, 5] {
ring.push(RawEvent::new(ts, 0).with_arg0(0).with_arg1(0));
}
assert!(!with_checker_state(|state| state.violation));
ring.push(RawEvent::new(4, 0).with_arg0(0).with_arg1(0));
assert!(with_checker_state(|state| state.violation));
install_ts_checker(previous);
});
}
#[test]
fn policy_tx_abort_id_stays_distinct_from_audit_stream_ids() {
let policy_and_audit_ids = [
ids::POLICY_TX_ABORT,
ids::POLICY_AUDIT,
ids::POLICY_AUDIT_EXT,
ids::POLICY_AUDIT_RESULT,
ids::POLICY_REPLAY_EVENT,
ids::POLICY_REPLAY_INPUT0,
ids::POLICY_REPLAY_INPUT1,
ids::POLICY_REPLAY_TRANSPORT0,
ids::POLICY_REPLAY_TRANSPORT1,
ids::POLICY_REPLAY_EVENT_EXT,
ids::POLICY_AUDIT_DEFER,
];
let mut outer = 0usize;
while outer < policy_and_audit_ids.len() {
let mut inner = outer + 1;
while inner < policy_and_audit_ids.len() {
assert_ne!(
policy_and_audit_ids[outer], policy_and_audit_ids[inner],
"policy/audit tap ids must stay unique"
);
inner += 1;
}
outer += 1;
}
let tx_abort = policy_event_spec(ids::POLICY_TX_ABORT).expect("tx abort policy event");
assert_eq!(tx_abort.kind, PolicyEventKind::TxAbort);
assert_eq!(
tx_abort.sid_hint_from_tap(TapEvent::zero().with_arg0(0x1234_5678).with_arg1(7)),
Some(0x1234_5678)
);
assert!(
policy_event_spec(ids::POLICY_AUDIT).is_none(),
"audit digest tuples must not collide with policy execution events"
);
}
#[test]
fn commit_and_state_restore_policy_events_carry_sid_hints_in_arg0() {
let commit = policy_event_spec(ids::POLICY_COMMIT).expect("commit policy event");
assert_eq!(commit.kind, PolicyEventKind::Commit);
assert_eq!(
commit.sid_hint_from_tap(TapEvent::zero().with_arg0(0x0102_0304)),
Some(0x0102_0304)
);
let restore =
policy_event_spec(ids::POLICY_STATE_RESTORE).expect("state restore policy event");
assert_eq!(restore.kind, PolicyEventKind::StateRestore);
assert_eq!(
restore.sid_hint_from_tap(TapEvent::zero().with_arg0(0x5566_7788)),
Some(0x5566_7788)
);
}
}
impl<'a> RingBuffer<'a> {
fn new(storage: &'a mut [TapEvent]) -> Self {
assert!(storage.len() >= RING_BUFFER_SIZE);
Self {
head: Cell::new(0),
storage: storage.as_mut_ptr(),
_marker: PhantomData,
_no_send_sync: PhantomData,
}
}
fn push(&self, event: TapEvent) {
let head = self.head.get();
let idx = head % RING_BUFFER_SIZE;
self.head.set(head.wrapping_add(1));
#[cfg(test)]
{
if let Some(checker) = current_ts_checker() {
let should_run = with_checker_state(|state| {
state
.owner
.map(|owner| owner == current_thread_id_u64())
.unwrap_or(true)
});
if should_run {
checker(event.ts);
}
}
}
unsafe {
self.storage.add(idx).write(event);
}
}
#[cfg(test)]
fn as_slice(&self) -> &[TapEvent] {
unsafe { slice::from_raw_parts(self.storage, RING_BUFFER_SIZE) }
}
#[cfg(test)]
fn head(&self) -> usize {
self.head.get()
}
#[cfg(test)]
fn events_since<'cursor, T, F>(
&'a self,
cursor: &'cursor mut usize,
mapper: F,
) -> impl Iterator<Item = T> + 'cursor
where
F: FnMut(TapEvent) -> Option<T>,
F: 'cursor,
T: 'cursor,
'a: 'cursor,
{
let index = *cursor;
TapEvents {
cursor,
index,
head: self.head(),
storage: self.as_slice(),
mapper,
}
}
}
pub(crate) struct TapRing<'a> {
user: RingBuffer<'a>,
infra: RingBuffer<'a>,
}
impl<'a> TapRing<'a> {
pub(crate) fn from_storage(storage: &'a mut [TapEvent; RING_EVENTS]) -> Self {
let (user_slice, infra_slice) = storage.split_at_mut(RING_BUFFER_SIZE);
Self {
user: RingBuffer::new(user_slice),
infra: RingBuffer::new(infra_slice),
}
}
#[cfg(test)]
pub(crate) unsafe fn assume_static(&self) -> &'static TapRing<'static> {
let ptr: *const TapRing<'a> = self;
unsafe { &*ptr.cast::<TapRing<'static>>() }
}
pub(crate) fn push(&self, event: TapEvent) {
if event.id < ids::USER_EVENT_RANGE_END {
self.user.push(event);
} else {
self.infra.push(event);
}
GLOBAL_TAP.invoke_post(&event);
}
#[cfg(test)]
pub(crate) fn as_slice(&self) -> &[TapEvent] {
self.infra.as_slice()
}
#[cfg(test)]
pub(crate) fn head(&self) -> usize {
self.infra.head()
}
#[cfg(test)]
pub(crate) fn events_since<'cursor, T, F>(
&'a self,
cursor: &'cursor mut usize,
mapper: F,
) -> impl Iterator<Item = T> + 'cursor
where
F: FnMut(TapEvent) -> Option<T>,
F: 'cursor,
T: 'cursor,
'a: 'cursor,
{
self.infra.events_since(cursor, mapper)
}
}