shep_core/protocol/mod.rs
1//! The client<->daemon wire protocol (version 8).
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 is `deny_unknown_fields`, so the additive rule below
7//! does not cover it and an older daemon cannot decode `depends_on`.
8//! Version 6 bumped on a retype: [`Response::Reloading`] became a struct
9//! variant to carry the apps a staged reload refused, so it serializes as
10//! an object where an older peer reads an array. Version 7 bumped on the
11//! same retype applied to [`Response::Restarted`], which a staged restart
12//! refuses apps of for the same reason and had nowhere to name them.
13//! Version 8 bumped on a second new `AppConfig` field, `environment`, for
14//! the reason version 5 did. [`Request::PutSecrets`] rode in on the same
15//! commit and forced nothing: it is an additive variant, and a daemon that
16//! has never heard of it decodes [`Request::Unrecognized`] and refuses by
17//! name.
18//!
19//! A `*_wire_v8` test pins today's shape. A
20//! `v1_*_fixture_still_deserializes` test pins an old peer's payload and
21//! never renames.
22
23pub mod events;
24pub mod frame;
25pub mod request;
26/// Frame encoding shared by daemon and client
27pub mod wire;
28
29pub use events::{BusEvent, ProcessEventKind};
30pub use frame::ServerFrame;
31pub use request::{
32 ActionOutcome, ActionReply, DogSectionToml, DogSource, EnvValue, Envelope, ExitInfo, Hello,
33 HelloAck, HelloReply, Lamb, LineOutcome, LineReply, ProcessInfo, ProcessInfoBuilder, Reply,
34 Request, Response, RpcError, RpcErrorCode, SelectorSpec, SheepApplied, SheepConfigView,
35 SheepDrift, SheepRefusal, SignalOutcome, SignalReply, Smit, SmitError, sort_flock,
36};
37pub use shep_channel::{CHANNEL_VERSION, ChildMessage, ShepherdMessage};
38pub use wire::{MAX_FRAME_BYTES, WireError, codec, decode_frame, encode_frame, reply_id};
39
40/// The shepherd channel's wire types. Moved to the `shep-channel` crate;
41/// this path is kept so consumers of 0.1.x do not break. Use
42/// `shep_core::protocol` directly instead.
43#[deprecated(note = "use `shep_core::protocol` directly")]
44pub mod channel {
45 pub use shep_channel::{CHANNEL_VERSION, ChildMessage, ShepherdMessage};
46}
47
48/// Wire protocol version.
49///
50/// Additive optional fields (new serde-defaulted `Option<T>` fields, new
51/// variants behind `#[non_exhaustive]`) keep the version. Removing,
52/// renaming, or retyping anything serialized bumps it, recorded in the
53/// CHANGELOG. Byte fixtures in each protocol module pin the deserialize
54/// direction.
55pub const PROTOCOL_VERSION: u32 = 8;
56
57/// The oldest protocol this build accepts from a peer.
58///
59/// The handshake compares against this rather than [`PROTOCOL_VERSION`],
60/// so a change that only adds does not refuse anyone. This rises only
61/// when a message shape changes such that an older peer cannot read it,
62/// and raising it refuses every peer built below it, which is why the
63/// rules in the spec exist to make that rare.
64pub const MIN_SUPPORTED: u32 = 8;
65
66#[cfg(test)]
67mod tests {
68 use super::{MIN_SUPPORTED, PROTOCOL_VERSION};
69
70 #[test]
71 fn a_new_app_config_field_forced_the_protocol_version_up() {
72 // fails if a field is added to `AppConfig` without the bump, the
73 // same way `depends_on` forced 5 and `environment` forced 8. That
74 // struct is `deny_unknown_fields`, so an older peer refuses the
75 // whole payload rather than ignoring a key it does not know, and
76 // the handshake is the only place that can say so. The retypes of
77 // `Response::Reloading` and `Response::Restarted` forced 6 and 7
78 // for the separate reason that an object is not an array.
79 assert_eq!(PROTOCOL_VERSION, 8);
80 }
81
82 #[test]
83 fn the_floor_never_outruns_the_ceiling() {
84 // A floor above the current version would refuse every peer,
85 // including one built from this exact commit. Both sides are
86 // `const`, so clippy wants the check itself const-evaluated.
87 const { assert!(MIN_SUPPORTED <= PROTOCOL_VERSION) };
88 }
89}