Skip to main content

monty_proto/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{ops::RangeInclusive, time::Duration};
4
5mod budget_vec;
6#[doc(hidden)]
7pub mod budgeted_prost;
8mod convert;
9mod decode_budget;
10mod frame;
11mod generated;
12// Python ↔ MontyObject value conversion; opt-in because it links pyo3, which
13// pure-Rust consumers of the wire protocol must never pay for.
14#[cfg(feature = "python")]
15pub mod python;
16mod requirement;
17#[cfg(feature = "test-util")]
18#[doc(hidden)]
19pub mod test_util;
20mod wire;
21#[cfg(feature = "worker")]
22pub mod worker;
23
24/// Version of the wire schema this build speaks, sent in
25/// [`pb::Configure::protocol_version`] and range-checked by the child.
26///
27/// Bump on any change a peer at the previous version could mis-read: removing
28/// or repurposing a field, changing a field's meaning, or adding one the child
29/// requires. Purely additive changes an older peer can ignore do not need a
30/// bump.
31///
32/// `Configure.persistence`, `Configure.profile` and `ChildEvent.session_id` did not bump it: a
33/// serving relay and its client act on them, children ignore them, and a
34/// child that drops them loses nothing. Version 6 added the `import` and
35/// `import_stubs` OS calls, which version 5 parents cannot answer.
36pub const PROTOCOL_VERSION: u32 = 6;
37
38/// Oldest [`PROTOCOL_VERSION`] this build still serves.
39///
40/// Raising this cuts off every deployed peer at the older versions, so avoid
41/// it: an additive wire change bumps [`PROTOCOL_VERSION`] alone and older
42/// peers keep working for everything but the new thing. Raising it is a
43/// breaking change that requires a major version bump.
44///
45/// Version 4 and below are not served. Version 3 carried values as recursive
46/// `MontyObject` trees, where this build carries one flat `Arena` per message.
47/// Version 4 both lacked `max_feed_duration`/`max_turn_duration` and had the
48/// per-session `max_duration` this build dropped: a version 4 parent would
49/// send a budget nothing enforces, and a version 4 child would accept the new
50/// budgets and ignore them. Neither side can be told apart from a working one,
51/// so both are refused.
52pub const MIN_SUPPORTED_PROTOCOL_VERSION: u32 = 5;
53
54/// How long the child holds buffered `print()` output before emitting it as a
55/// `Print` event, when [`pb::Configure::print_flush_interval_ms`] says nothing.
56///
57/// Short enough to read as live output, long enough that a printing loop emits
58/// events at a rate set by elapsed time rather than by how often the program
59/// called `print()`.
60pub const DEFAULT_PRINT_FLUSH_INTERVAL: Duration = Duration::from_millis(5);
61
62// The supported range must be non-empty, and must exclude zero
63const _: () = assert!(MIN_SUPPORTED_PROTOCOL_VERSION >= 1);
64const _: () = assert!(MIN_SUPPORTED_PROTOCOL_VERSION <= PROTOCOL_VERSION);
65
66/// Protocol versions this build serves; a `Configure` outside it is fatal.
67const SUPPORTED_PROTOCOL_VERSIONS: RangeInclusive<u32> = MIN_SUPPORTED_PROTOCOL_VERSION..=PROTOCOL_VERSION;
68
69/// Checks a peer's declared [`pb::Configure::protocol_version`] against the
70/// range this build serves.
71pub fn check_protocol_version(version: u32) -> Result<(), String> {
72    if SUPPORTED_PROTOCOL_VERSIONS.contains(&version) {
73        Ok(())
74    } else {
75        // Singular while the window is one version wide — "2 to 2" reads as a range it is not.
76        // `let`-bound so the `format_args!` temporaries live long enough to be formatted below.
77        // use "server" since this error should only ever happen when using a client/server architecture
78        let supported = if MIN_SUPPORTED_PROTOCOL_VERSION == PROTOCOL_VERSION {
79            format_args!("server supports protocol version {PROTOCOL_VERSION}")
80        } else {
81            format_args!("server supports protocol versions {MIN_SUPPORTED_PROTOCOL_VERSION} to {PROTOCOL_VERSION}")
82        };
83
84        // Points the reader at the client for the refusal above: the worker is typically a deployed build the
85        // reader cannot change, so the client is the half to move.
86        let tip = if version < MIN_SUPPORTED_PROTOCOL_VERSION {
87            "try updating to a newer client version"
88        } else {
89            "make sure you are using the correct client version"
90        };
91
92        Err(format!("unsupported protocol version {version} ({supported}, {tip})"))
93    }
94}
95
96pub use budget_vec::BudgetVec;
97pub use convert::{
98    ProtoConvertError, ext_result_from_proto, ext_result_to_proto, future_results_from_proto, future_results_to_proto,
99    named_values_from_proto, named_values_to_proto, os_call_from_proto, os_call_to_proto, resume_call_from_proto,
100};
101#[cfg(feature = "test-util")]
102#[doc(hidden)]
103pub use decode_budget::{decode_budget_remaining, with_decode_budget};
104pub use frame::{
105    DEFAULT_MAX_DECODE_BYTES, FrameError, FrameReader, MAX_FRAME_LEN, decode_frame, encode_framed_into,
106    encode_to_capped_vec, exceeds_max_frame_len, write_frame,
107};
108pub use generated::pb;
109pub use requirement::validate_requirement;
110pub use wire::{WireArena, WireFunctionCall, WireIndexes, WireNamedTuple, WireNodePairs};