Skip to main content

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}