Skip to main content

gwk_kernel/wire/
mod.rs

1//! The local wire: frames in, control values out.
2//!
3//! Three layers, each refusing what the one above it cannot see:
4//!
5//! * [`frame`] — the `[u32 body_length][u8 kind][body]` codec. Length bounds
6//!   are checked against the ANNOUNCED length, before allocation, and the
7//!   connection's byte budget is charged for the whole frame.
8//! * [`strict`] — the validating JSON decoder ADR 0001 requires: recursive
9//!   duplicate keys, unknown fields, invalid UTF-8, trailing bytes.
10//! * [`hello`] — the handshake. Major matches exactly, minor negotiates down,
11//!   capabilities intersect, and the whole thing is on a deadline.
12//! * [`listen`] — the socket itself: where it may live, who may connect, and
13//!   what is removed when the daemon stops.
14//!
15//! Authentication is [`listen`]'s, not the handshake's. The UDS peer credential
16//! is the only identity this kernel has (ADR 0002) and it is checked before a
17//! single byte of the connection is read, so an unauthorized peer never reaches
18//! the decoder at all. Nothing in a hello can widen what a connection may do —
19//! `client` is a log label and the capability set is a feature list.
20
21pub mod frame;
22pub mod hello;
23pub mod listen;
24pub mod serve;
25pub mod strict;
26pub mod subscribe;
27
28use gwk_domain::protocol::{KernelErrorCode, KernelResult};
29
30/// A refusal on the wire, already carrying the code it will be sent as.
31///
32/// Same shape as [`crate::project::Refusal`] and deliberately separate: that
33/// one answers a COMMAND and can be turned into a `KernelResult`, while this
34/// one can also mean the connection itself is unusable — an I/O failure or a
35/// budget exhausted mid-frame has no request to answer.
36#[derive(Debug)]
37pub struct WireError {
38    pub code: KernelErrorCode,
39    pub message: String,
40    /// True when the connection cannot continue: the framing is out of step, or
41    /// the transport failed. A refusal that only concerns one request leaves
42    /// this false and the connection open.
43    pub fatal: bool,
44}
45
46impl WireError {
47    pub fn new(code: KernelErrorCode, message: impl Into<String>) -> Self {
48        Self {
49            code,
50            message: message.into(),
51            // Every framing and handshake refusal is fatal by construction: the
52            // reader cannot know where the next frame starts once a length or a
53            // kind was wrong. Per-request refusals are built by `KernelResult`
54            // and never travel as a `WireError`.
55            fatal: true,
56        }
57    }
58
59    pub fn io(what: &str, error: std::io::Error) -> Self {
60        Self::new(KernelErrorCode::Storage, format!("{what}: {error}"))
61    }
62
63    /// The value form, for the one case where a refusal still owes a response.
64    pub fn into_result(self) -> KernelResult {
65        KernelResult::Error {
66            code: self.code,
67            message: self.message,
68            detail: None,
69        }
70    }
71}
72
73impl std::fmt::Display for WireError {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{:?}: {}", self.code, self.message)
76    }
77}
78
79impl std::error::Error for WireError {}