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 {}