#[non_exhaustive]#[repr(C, align(8))]pub struct Frame {
pub magic: [u8; 2],
pub version: u8,
pub status: Status,
pub pid: u32,
pub timestamp: u64,
pub nonce: u64,
pub payload: u32,
}Expand description
On-wire health frame — exactly 32 bytes, 8-byte aligned, little-endian
integer fields. The struct is repr(C) so its layout is ABI-stable across
compilations and trivially verifiable by inspection.
Construct frames directly via the public fields, then call
Frame::encode to write to a socket buffer or Frame::decode to read
one. There is no Default; agents always supply a real pid, nonce and
timestamp.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.magic: [u8; 2]Magic prefix, always equal to MAGIC.
version: u8Protocol version, always equal to VERSION on emit.
status: StatusHealth status reported by the agent. Encoded on the wire as a
single byte at offset 3 (Status discriminants are #[repr(u8)]).
pid: u32OS process id of the emitting agent.
timestamp: u64Monotonic timestamp chosen by the emitter (typically nanoseconds since some agent-local epoch). Observers do not interpret it; they only compare consecutive timestamps for the same pid.
nonce: u64Strictly increasing counter, starting at 1 on the first beat after
Varta::connect. The panic hook pins this to NONCE_TERMINAL to
mark a final critical frame.
Regular beats wrap at NONCE_TERMINAL - 1 → 0 on exhaustion, so the
regular-beat nonce stream structurally never collides with
NONCE_TERMINAL. A wire frame with nonce == NONCE_TERMINAL is, by
construction, a panic frame (and Frame::decode enforces it must
also carry Status::Critical).
payload: u32Free-form 4-byte payload — application-defined health context (queue
depth, error code, etc.). Carried opaquely by the protocol. Shrunk
from u64 to u32 in VLP v0.2 to fit the CRC-32C trailer in 32
bytes; the on-wire CRC occupies bytes 28..32 and is not surfaced as
a struct field — see Frame::encode / Frame::decode.
Implementations§
Source§impl Frame
impl Frame
Sourcepub const fn new(
status: Status,
pid: u32,
timestamp: u64,
nonce: u64,
payload: u32,
) -> Frame
pub const fn new( status: Status, pid: u32, timestamp: u64, nonce: u64, payload: u32, ) -> Frame
Sourcepub fn encode(&self, out: &mut [u8; 32])
pub fn encode(&self, out: &mut [u8; 32])
Serialise this frame into a 32-byte buffer in canonical little-endian layout. The output buffer is overwritten in place; this method allocates nothing.
Bytes 28..32 are stamped with a CRC-32C computed over bytes 0..28 —
see crate::crc32c. The CRC is a wire-format artifact, not a
struct field; callers must never mutate the buffer between encode
and the on-wire write or the receiver will reject the frame as
DecodeError::BadCrc.
Sourcepub fn decode(bytes: &[u8; 32]) -> Result<Frame, DecodeError>
pub fn decode(bytes: &[u8; 32]) -> Result<Frame, DecodeError>
Decode a 32-byte buffer back into a Frame, validating magic,
version, CRC, status, and field ranges in that order. Returns
DecodeError on the first failed check.
Order rationale: magic + version come first so random bytes
from a wrong-protocol sender surface as
DecodeError::BadMagic / DecodeError::BadVersion (the
“this isn’t even VLP” diagnostic). The CRC then gates every
field-range check — a single-bit-flipped status byte must surface
as DecodeError::BadCrc, not as a valid frame with the wrong
meaning.
Field-range rules enforced after the CRC passes:
-
status == Status::Stallis rejected —Stallis observer-synthesized byvarta-watchwhen a pid goes silent past its threshold; no legitimate agent emits it on the wire. Accepting a spoofedStallframe would let a hostile sender pollute observer telemetry from any pid. -
pid ∈ {0, 1}is rejected — pid 0 is the kernel/scheduler and pid 1 is init/systemd; no legitimate agent runs at either, and accepting them lets a hostile sender spoof “init has stalled” to the recovery path. -
timestamp == u64::MAXis rejected —varta_client::Varta::beatsaturates at this value with.min(u64::MAX as u128) as u64, and reaching it through real elapsed time (~584 years) is impossible. The sentinel is reserved.Asymmetry note: a hypothetical agent whose monotonic clock saturates still observes
BeatOutcome::Sentfromsend(2)(the kernel sees a well-formed 32-byte datagram), while the observer drops the frame asDecodeError::BadTimestamp. The divergence is physically unreachable on a singleVarta::connecthandle and is documented for completeness only. -
nonce == NONCE_TERMINALis allowed only when paired withStatus::Critical; the sentinel is the panic-hook’s terminal marker and is never emitted on the regular beat path.