Skip to main content

iris_abi/
lib.rs

1//! The guest and host ABI for iris self-decoding datasets.
2//!
3//! The ABI is the only surface in iris that can ossify, so it is shaped like a wire protocol: length prefixed records, negotiated capabilities, and a defined way for either side to refuse politely.
4//!
5//! This crate is `no_std` and has no dependencies at all, and both of those are checked by CI. It
6//! ends up inside every decoder anyone writes, so anything it pulls in, everyone pays for.
7//!
8//! # How a conversation goes
9//!
10//! The host sends a [`Hello`] saying which ABI version it speaks and what it can do. The decoder
11//! answers with a [`HelloAck`] saying what it needs. [`negotiate`] compares the two and either
12//! produces an [`Agreement`] or a [`Refusal`] that names the capability that was missing. After
13//! that the host sends [`ScanRequest`] records and the rows come back as [`Batch`] records. When the
14//! decoder needs bytes it has not been given it asks for them, as a [`RangeRequest`] record where
15//! there is a channel to put one on and as a call answered by a [`RangeStatus`] where the decoder is
16//! stopped inside the request waiting for it.
17//!
18//! ```
19//! use iris_abi::{Capability, CapabilitySet, Hello, HelloAck, negotiate};
20//!
21//! let host = Hello {
22//!     abi_major: iris_abi::ABI_MAJOR,
23//!     abi_minor: iris_abi::ABI_MINOR,
24//!     window_bytes: 64 << 20,
25//!     max_batch_rows: 8192,
26//!     offered: CapabilitySet::new()
27//!         .with(Capability::REQUIRE_RANGE)
28//!         .with(Capability::SLIDING_WINDOW),
29//!     source_bytes: 4 << 30,
30//! };
31//! let decoder = HelloAck {
32//!     abi_major: iris_abi::ABI_MAJOR,
33//!     abi_minor: iris_abi::ABI_MINOR,
34//!     required: CapabilitySet::new().with(Capability::REQUIRE_RANGE),
35//!     optional: CapabilitySet::new().with(Capability::PROJECTION),
36//!     decoder_id: "example",
37//! };
38//!
39//! let agreed = negotiate(&host, &decoder).expect("the host offers what the decoder needs");
40//! assert!(agreed.has(Capability::REQUIRE_RANGE));
41//! // The host offers sliding windows but this decoder never asked for them, so it is not on.
42//! assert!(!agreed.has(Capability::SLIDING_WINDOW));
43//! // The decoder would like projection pushdown but this host does not do it, and that is fine
44//! // because it was optional.
45//! assert!(!agreed.has(Capability::PROJECTION));
46//! ```
47//!
48//! # What is allowed to change
49//!
50//! Adding a field to the end of a record is allowed and does not bump that record's version, because
51//! a reader that does not know about the field steps over it. Adding a record is allowed, because a
52//! reader that does not know a tag steps over the whole record. Adding a capability is allowed,
53//! because a side that does not offer it says so and the other side decides what to do.
54//!
55//! Removing a field, reordering fields, changing what a field means, or changing what a capability
56//! bit means are all breaking, and all of them are supposed to be loud rather than silent. The
57//! tests in `tests/forward_compat.rs` hold the compatible half of that line.
58
59#![no_std]
60// Nothing in here parses untrusted bytes with a pointer. If a future change needs to, it needs a
61// conversation first, because this crate is the one piece of iris that runs inside every decoder
62// anybody writes.
63#![forbid(unsafe_code)]
64
65pub mod caps;
66pub mod error;
67pub mod handshake;
68pub mod message;
69pub mod range;
70pub mod record;
71pub mod wire;
72
73pub use caps::{Capability, CapabilitySet};
74pub use error::{Error, Result};
75pub use handshake::{Agreement, negotiate};
76pub use message::{
77    Batch, BufferRef, Buffers, Hello, HelloAck, Message, Node, Nodes, Projection, RangeRequest,
78    Refusal, RefusalReason, ScanRequest,
79};
80pub use range::RangeStatus;
81pub use record::{Header, Tag};
82pub use wire::{Reader, Writer};
83
84/// The major ABI version this build speaks.
85///
86/// Zero means the record layouts are still allowed to move. When this goes to one it means the
87/// layouts in [`message`] are frozen, and freezing them is a milestone with a written compatibility
88/// note behind it rather than something that happens because a refactor felt finished.
89pub const ABI_MAJOR: u16 = 0;
90
91/// The minor ABI version this build speaks.
92///
93/// This goes up when a field or a record or a capability is added. Two sides at different minor
94/// versions can always talk to each other, and they settle on the lower of the two.
95pub const ABI_MINOR: u16 = 3;