Skip to main content

Frame

Struct Frame 

Source
#[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
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional 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: u8

Protocol version, always equal to VERSION on emit.

§status: Status

Health status reported by the agent. Encoded on the wire as a single byte at offset 3 (Status discriminants are #[repr(u8)]).

§pid: u32

OS process id of the emitting agent.

§timestamp: u64

Monotonic 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: u64

Strictly 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: u32

Free-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

Source

pub const fn new( status: Status, pid: u32, timestamp: u64, nonce: u64, payload: u32, ) -> Frame

Construct a new frame with the canonical MAGIC prefix and VERSION byte already populated. All other fields are caller-supplied.

Source

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.

Source

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::Stall is rejected — Stall is observer-synthesized by varta-watch when a pid goes silent past its threshold; no legitimate agent emits it on the wire. Accepting a spoofed Stall frame 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::MAX is rejected — varta_client::Varta::beat saturates 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::Sent from send(2) (the kernel sees a well-formed 32-byte datagram), while the observer drops the frame as DecodeError::BadTimestamp. The divergence is physically unreachable on a single Varta::connect handle and is documented for completeness only.

  • nonce == NONCE_TERMINAL is allowed only when paired with Status::Critical; the sentinel is the panic-hook’s terminal marker and is never emitted on the regular beat path.

Trait Implementations§

Source§

impl Clone for Frame

Source§

fn clone(&self) -> Frame

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Frame

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Frame

Source§

fn eq(&self, other: &Frame) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for Frame

Source§

impl Eq for Frame

Source§

impl StructuralPartialEq for Frame

Auto Trait Implementations§

§

impl Freeze for Frame

§

impl RefUnwindSafe for Frame

§

impl Send for Frame

§

impl Sync for Frame

§

impl Unpin for Frame

§

impl UnsafeUnpin for Frame

§

impl UnwindSafe for Frame

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.