iris_abi/handshake.rs
1//! Working out whether the host and the decoder can work together, and saying so plainly when they
2//! cannot.
3//!
4//! The thing this is trying to avoid is a decoder that runs and produces wrong answers because the
5//! host quietly ignored something it did not understand. Every path through here either ends in an
6//! agreement that both sides can name, or in a refusal that says which bit was the problem.
7
8use crate::caps::{Capability, CapabilitySet};
9use crate::message::{Hello, HelloAck, Refusal, RefusalReason};
10
11/// What the two sides agreed on.
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
13pub struct Agreement {
14 /// The major version both sides speak.
15 pub abi_major: u16,
16 /// The minor version both sides speak, which is the lower of the two.
17 pub abi_minor: u16,
18 /// The capabilities the host offers and the decoder asked for, required or optional.
19 ///
20 /// A capability the host offers and the decoder never mentioned is not in here, so the host can
21 /// use this to decide what it actually has to set up.
22 pub agreed: CapabilitySet,
23 /// Carried over from the host's [`Hello`] so the caller has one thing to hold on to.
24 pub window_bytes: u64,
25 /// Carried over from the host's [`Hello`].
26 pub max_batch_rows: u64,
27}
28
29/// Decides whether a host and a decoder can work together.
30///
31/// # Errors
32///
33/// Returns the [`Refusal`] that should be sent to the other side. The detail strings are fixed
34/// rather than formatted, because this crate does not allocate, and the machine-readable part of a
35/// refusal is the reason code and the capability anyway.
36pub fn negotiate(hello: &Hello, ack: &HelloAck<'_>) -> Result<Agreement, Refusal<'static>> {
37 if ack.abi_major > hello.abi_major {
38 return Err(Refusal::new(
39 RefusalReason::ABI_TOO_NEW,
40 "the decoder was built against a later major version of the iris ABI than this host speaks",
41 ));
42 }
43 if ack.abi_major < hello.abi_major {
44 return Err(Refusal::new(
45 RefusalReason::ABI_TOO_OLD,
46 "the decoder was built against an earlier major version of the iris ABI than this host speaks",
47 ));
48 }
49
50 // A decoder built against a later minor version can require a capability that did not have a
51 // name when this host was compiled. Truncating the bitset would turn that into "requires
52 // nothing", so it has to be checked before the set difference below, which cannot see it.
53 if ack.required.has_bits_beyond_this_build() {
54 return Err(Refusal::new(
55 RefusalReason::MISSING_CAPABILITY,
56 "the decoder requires a capability that is past the end of the bitset this host understands",
57 ));
58 }
59
60 let missing = ack.required.difference(hello.offered);
61 if let Some(cap) = missing.iter().next() {
62 return Err(Refusal {
63 reason: RefusalReason::MISSING_CAPABILITY,
64 capability: cap,
65 detail: "the decoder requires a capability this host does not offer",
66 });
67 }
68
69 Ok(Agreement {
70 abi_major: hello.abi_major,
71 abi_minor: hello.abi_minor.min(ack.abi_minor),
72 agreed: hello.offered.intersection(ack.required.union(ack.optional)),
73 window_bytes: hello.window_bytes,
74 max_batch_rows: hello.max_batch_rows,
75 })
76}
77
78impl Agreement {
79 /// Whether a capability is in force for this pairing.
80 #[must_use]
81 pub const fn has(&self, cap: Capability) -> bool {
82 self.agreed.contains(cap)
83 }
84}