use crate::observe::ids;
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct TapEvent {
bytes: [u8; 16],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Evidence {
kind: u16,
reason: u8,
input: [u32; 4],
}
impl Evidence {
#[inline]
pub const fn kind(self) -> u16 {
self.kind
}
#[inline]
pub const fn reason(self) -> u8 {
self.reason
}
#[inline]
pub const fn input(self) -> [u32; 4] {
self.input
}
}
impl core::fmt::Debug for TapEvent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let event = *self;
f.debug_struct("TapEvent")
.field("ts", &event.ts())
.field("id", &event.id())
.field("causal_key", &event.causal_key())
.field("arg0", &event.arg0())
.field("arg1", &event.arg1())
.field("evidence", &event.evidence())
.finish()
}
}
impl TapEvent {
#[inline]
pub(crate) const fn new(ts: u32, id: u16, causal_key: u16, arg0: u32, arg1: u32) -> Self {
Self {
bytes: [
(ts >> 24) as u8,
(ts >> 16) as u8,
(ts >> 8) as u8,
ts as u8,
(id >> 8) as u8,
id as u8,
(causal_key >> 8) as u8,
causal_key as u8,
(arg0 >> 24) as u8,
(arg0 >> 16) as u8,
(arg0 >> 8) as u8,
arg0 as u8,
(arg1 >> 24) as u8,
(arg1 >> 16) as u8,
(arg1 >> 8) as u8,
arg1 as u8,
],
}
}
#[inline]
pub const fn ts(self) -> u32 {
u32::from_be_bytes([self.bytes[0], self.bytes[1], self.bytes[2], self.bytes[3]])
}
#[inline]
pub const fn id(self) -> u16 {
u16::from_be_bytes([self.bytes[4], self.bytes[5]])
}
#[inline]
pub const fn causal_key(self) -> u16 {
u16::from_be_bytes([self.bytes[6], self.bytes[7]])
}
#[inline]
pub const fn arg0(self) -> u32 {
u32::from_be_bytes([self.bytes[8], self.bytes[9], self.bytes[10], self.bytes[11]])
}
#[inline]
pub const fn arg1(self) -> u32 {
u32::from_be_bytes([
self.bytes[12],
self.bytes[13],
self.bytes[14],
self.bytes[15],
])
}
#[inline]
pub(crate) const fn with_arg0(mut self, arg0: u32) -> Self {
self.bytes[8] = (arg0 >> 24) as u8;
self.bytes[9] = (arg0 >> 16) as u8;
self.bytes[10] = (arg0 >> 8) as u8;
self.bytes[11] = arg0 as u8;
self
}
#[inline]
pub(crate) const fn with_arg1(mut self, arg1: u32) -> Self {
self.bytes[12] = (arg1 >> 24) as u8;
self.bytes[13] = (arg1 >> 16) as u8;
self.bytes[14] = (arg1 >> 8) as u8;
self.bytes[15] = arg1 as u8;
self
}
#[inline]
pub(crate) const fn with_causal_key(mut self, causal_key: u16) -> Self {
self.bytes[6] = (causal_key >> 8) as u8;
self.bytes[7] = causal_key as u8;
self
}
#[inline]
const fn causal_role(self) -> u8 {
(self.causal_key() >> 8) as u8
}
#[inline]
const fn causal_seq(self) -> u8 {
(self.causal_key() & 0xFF) as u8
}
#[inline]
pub(crate) const fn make_causal_key(role: u8, seq: u8) -> u16 {
((role as u16) << 8) | (seq as u16)
}
#[inline]
pub(crate) const fn zero() -> Self {
Self { bytes: [0; 16] }
}
#[inline]
pub const fn evidence(self) -> Evidence {
let id = self.id();
if id == ids::TRANSPORT_FRAME {
Evidence {
kind: id,
reason: 0,
input: [self.arg0(), self.arg1(), 0, (id as u32) << 16],
}
} else if id == ids::TRANSPORT_MISMATCH || id == ids::TRANSPORT_FAULT {
self.transport_evidence()
} else {
Evidence {
kind: id,
reason: 0,
input: [self.arg0(), self.arg1(), 0, self.causal_key() as u32],
}
}
}
#[inline]
const fn transport_evidence(self) -> Evidence {
let reason = self.causal_seq();
let lane = self.causal_role() as u32;
let id = self.id();
Evidence {
kind: id,
reason,
input: [
self.arg0(),
self.arg1(),
0,
((id as u32) << 16) | (lane << 8) | (reason as u32),
],
}
}
}
#[cfg(test)]
mod tests {
use super::TapEvent;
use crate::observe::ids;
use std::format;
#[test]
fn transport_mismatch_evidence_carries_expected_lane_and_reason() {
let event = TapEvent::zero()
.with_causal_key(TapEvent::make_causal_key(
1,
ids::TRANSPORT_MISMATCH_SESSION,
))
.with_arg0(0x1111_2222)
.with_arg1(0x3333_4444);
let event = super::super::events::raw_event(0, ids::TRANSPORT_MISMATCH)
.with_causal_key(event.causal_key())
.with_arg0(event.arg0())
.with_arg1(event.arg1());
let evidence = event.evidence();
assert_eq!(evidence.kind(), ids::TRANSPORT_MISMATCH);
assert_eq!(evidence.reason(), ids::TRANSPORT_MISMATCH_SESSION);
assert_eq!(
evidence.input(),
[
0x1111_2222,
0x3333_4444,
0,
((ids::TRANSPORT_MISMATCH as u32) << 16)
| (1 << 8)
| ids::TRANSPORT_MISMATCH_SESSION as u32
]
);
}
#[test]
fn transport_fault_evidence_carries_lane_and_reason() {
let event = TapEvent::zero()
.with_causal_key(TapEvent::make_causal_key(2, ids::TRANSPORT_FAULT_DEADLINE))
.with_arg0(0xaaaa_bbbb);
let event = super::super::events::raw_event(0, ids::TRANSPORT_FAULT)
.with_causal_key(event.causal_key())
.with_arg0(event.arg0());
let evidence = event.evidence();
assert_eq!(evidence.kind(), ids::TRANSPORT_FAULT);
assert_eq!(evidence.reason(), ids::TRANSPORT_FAULT_DEADLINE);
assert_eq!(
evidence.input(),
[
0xaaaa_bbbb,
0,
0,
((ids::TRANSPORT_FAULT as u32) << 16)
| (2 << 8)
| ids::TRANSPORT_FAULT_DEADLINE as u32
]
);
}
#[test]
fn tap_event_is_exactly_sixteen_bytes() {
assert_eq!(core::mem::size_of::<TapEvent>(), 16);
assert_eq!(core::mem::align_of::<TapEvent>(), 1);
}
#[test]
fn tap_event_record_bytes_are_exactly_sixteen_bytes() {
let event = super::super::events::raw_event(0x0102_0304, ids::TRANSPORT_FRAME)
.with_causal_key(0x0506)
.with_arg0(0x0708_090a)
.with_arg1(0x0b0c_0d0e);
assert_eq!(
event.bytes,
[
0x01, 0x02, 0x03, 0x04, 0x02, 0x06, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
0x0d, 0x0e,
]
);
}
#[test]
fn tap_event_debug_uses_semantic_fields_only() {
let event = super::super::events::raw_event(0x0102_0304, ids::TRANSPORT_FRAME)
.with_causal_key(0x0506)
.with_arg0(0x0708_090a)
.with_arg1(0x0b0c_0d0e);
let rendered = format!("{event:?}");
for field in [
"TapEvent",
"ts",
"id",
"causal_key",
"arg0",
"arg1",
"evidence",
] {
assert!(
rendered.contains(field),
"TapEvent Debug must expose semantic field {field}: {rendered}"
);
}
for forbidden in ["bytes", "[u8", "[1, 2, 3, 4", "0x01020304"] {
assert!(
!rendered.contains(forbidden),
"TapEvent Debug must not expose raw record storage {forbidden}: {rendered}"
);
}
}
}