Skip to main content

shep_core/protocol/
mod.rs

1//! The client<->daemon wire protocol (version 9).
2//!
3//! Typed request/response enums plus bus events. Framing lives in
4//! [`wire`]; a serialized shape change bumps [`PROTOCOL_VERSION`].
5//! Version 4 bumped on an addition. Version 5 bumped on a new `AppConfig`
6//! field: that struct was `deny_unknown_fields` then, so the additive rule
7//! below did not cover it and an older daemon could not decode
8//! `depends_on`. The denial has since moved to `Flockfile::parse`, so a
9//! field change now costs an older peer the field rather than the whole
10//! payload, and still bumps.
11//! Version 6 bumped on a retype: [`Response::Reloading`] became a struct
12//! variant to carry the apps a staged reload refused, so it serializes as
13//! an object where an older peer reads an array. Version 7 bumped on the
14//! same retype applied to [`Response::Restarted`], which a staged restart
15//! refuses apps of for the same reason and had nowhere to name them.
16//! Version 8 bumped on a second new `AppConfig` field, `environment`, for
17//! the reason version 5 did. [`Request::PutSecrets`] rode in on the same
18//! commit and forced nothing: it is an additive variant, and a daemon that
19//! has never heard of it decodes [`Request::Unrecognized`] and refuses by
20//! name. Version 9 bumped on removing `increment_var`, the first shape
21//! change here to subtract a field rather than add one.
22//! [`Request::HostUsage`] and [`Response::HostUsage`] rode in after it
23//! and forced nothing either, on the terms [`Request::PutSecrets`] set
24//! above.
25//!
26//! A `*_wire_v9` test pins today's shape. A
27//! `v1_*_fixture_still_deserializes` test pins an old peer's payload and
28//! never renames.
29
30pub mod events;
31pub mod frame;
32pub mod request;
33/// Frame encoding shared by daemon and client
34pub mod wire;
35
36pub use events::{BusEvent, ProcessEventKind};
37pub use frame::ServerFrame;
38pub use request::{
39    ActionOutcome, ActionReply, DogSectionToml, DogSource, EnvValue, Envelope, ExitInfo, Hello,
40    HelloAck, HelloReply, HostUsage, Lamb, LineOutcome, LineReply, ProcessInfo, ProcessInfoBuilder,
41    Reply, Request, Response, RpcError, RpcErrorCode, SelectorSpec, SheepApplied, SheepConfigView,
42    SheepDrift, SheepRefusal, SignalOutcome, SignalReply, Smit, SmitError, sort_flock,
43};
44pub use shep_channel::{CHANNEL_VERSION, ChildMessage, ShepherdMessage};
45pub use wire::{MAX_FRAME_BYTES, WireError, codec, decode_frame, encode_frame, reply_id};
46
47/// The shepherd channel's wire types. Moved to the `shep-channel` crate;
48/// this path is kept so consumers of 0.1.x do not break. Use
49/// `shep_core::protocol` directly instead.
50#[deprecated(note = "use `shep_core::protocol` directly")]
51pub mod channel {
52    pub use shep_channel::{CHANNEL_VERSION, ChildMessage, ShepherdMessage};
53}
54
55/// Wire protocol version.
56///
57/// Additive optional fields (new serde-defaulted `Option<T>` fields, new
58/// variants behind `#[non_exhaustive]`) keep the version. Removing,
59/// renaming, or retyping anything serialized bumps it, recorded in the
60/// CHANGELOG. Byte fixtures in each protocol module pin the deserialize
61/// direction.
62///
63/// `AppConfig` is the exception: every field change bumps, additive
64/// included. An older peer ignores a key it does not know, so it runs a
65/// config the operator did not write and says nothing. `environment`
66/// forced 8 after the denial had already moved to `Flockfile::parse`,
67/// which is the precedent.
68pub const PROTOCOL_VERSION: u32 = 9;
69
70/// The oldest protocol this build accepts from a peer.
71///
72/// The handshake compares against this rather than [`PROTOCOL_VERSION`],
73/// so a change that only adds does not refuse anyone. This rises only
74/// when a message shape changes such that an older peer cannot read it,
75/// and raising it refuses every peer built below it, which is why the
76/// rules in the spec exist to make that rare.
77pub const MIN_SUPPORTED: u32 = 8;
78
79#[cfg(test)]
80mod tests {
81    use super::{MIN_SUPPORTED, PROTOCOL_VERSION};
82
83    /// Fails whenever `PROTOCOL_VERSION` moves, which makes a bump a
84    /// deliberate edit rather than a reflex. It does not detect a shape
85    /// change that forgot to bump: the `*_wire_v9` snapshots do that, by
86    /// gaining or losing the key.
87    ///
88    /// A bump moves five things together, and only this one fails on its
89    /// own: the constant, this literal, this test's own name, the module
90    /// doc's header and its version list, and the three `*_wire_vN`
91    /// snapshots with the names that pin them.
92    ///
93    /// `depends_on` forced 5, `environment` 8, dropping `increment_var` 9.
94    /// The `Response::Reloading` and `Response::Restarted` retypes forced 6
95    /// and 7, an object not being an array.
96    #[test]
97    fn a_removed_app_config_field_forced_the_protocol_version_up() {
98        assert_eq!(PROTOCOL_VERSION, 9);
99    }
100
101    #[test]
102    fn the_floor_never_outruns_the_ceiling() {
103        // A floor above the current version would refuse every peer,
104        // including one built from this exact commit. Both sides are
105        // `const`, so clippy wants the check itself const-evaluated.
106        const { assert!(MIN_SUPPORTED <= PROTOCOL_VERSION) };
107    }
108}