refactor(core): narrow the 1.0 public API
This commit is contained in:
@@ -21,8 +21,8 @@ use crate::observation::ObservationId;
|
||||
|
||||
/// A fixed-size salted Bloom filter keyed by `ObservationId`.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Bloom {
|
||||
pub salt: u32,
|
||||
pub(crate) struct Bloom {
|
||||
pub(crate) salt: u32,
|
||||
bits: [u8; consts::SUMMARY_BYTES],
|
||||
/// Bytes in use: a power of two in `1..=SUMMARY_BYTES`.
|
||||
len: usize,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/// Wire envelope version byte prepended to every serialized `Frame`.
|
||||
///
|
||||
/// The first byte of every frame on the wire; [`crate::wire::Frame::decode`]
|
||||
/// The first byte of every frame on the wire; `crate::wire::Frame::decode`
|
||||
/// rejects any frame whose leading byte differs from this
|
||||
/// (`WireError::VersionMismatch`), and [`crate::wire::frame_sender`] skips it.
|
||||
/// `4` is the first value in the `4..=0x7F` range that fits a single-byte
|
||||
@@ -42,7 +42,7 @@ pub const REASSEMBLY_TIMEOUT_MS: u64 = 5000;
|
||||
pub const SEND_QUEUE_CAP: usize = 256;
|
||||
|
||||
/// Minimum usable UDP frame size in bytes (the BLE floor below which a
|
||||
/// fragment header leaves no room for payload). [`crate::frag::fragment`] gives
|
||||
/// fragment header leaves no room for payload). `crate::frag::fragment` gives
|
||||
/// up cleanly with `WireError::TooLarge` for any `mtu` below this rather than
|
||||
/// emitting a truncated frame; the MTU sweep runs from here to [`MAX_FRAME`].
|
||||
pub const MIN_MTU: usize = 23;
|
||||
|
||||
+17
-13
@@ -1,6 +1,6 @@
|
||||
//! Exchange state machine for tines-core.
|
||||
//!
|
||||
//! [`Exchange`] tracks the peers this node knows about and drives the
|
||||
//! `Exchange` tracks the peers this node knows about and drives the
|
||||
//! content-defined gossip protocol: acknowledging received observations,
|
||||
//! summarizing what we hold, requesting what peers are missing, and delivering
|
||||
//! observations (fragmenting them across the wire when they exceed the MTU).
|
||||
@@ -13,8 +13,10 @@
|
||||
|
||||
use crate::consts;
|
||||
use crate::{
|
||||
Bloom, Event, Frame, NodeId, Observation, ObservationId, Reassembler, SendError, Store, Tick,
|
||||
WireError,
|
||||
bloom::Bloom,
|
||||
frag::Reassembler,
|
||||
wire::{Frame, WireError},
|
||||
Event, NodeId, Observation, ObservationId, SendError, Store, Tick,
|
||||
};
|
||||
use heapless::Vec;
|
||||
|
||||
@@ -31,7 +33,7 @@ const SUMMARY_OVERHEAD: usize = 25;
|
||||
/// MTU changed). The stream is then abandoned and restarted rather than
|
||||
/// sending bytes the receiver cannot reassemble.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct InFlight {
|
||||
pub(crate) struct InFlight {
|
||||
/// The observation being fragmented.
|
||||
pub id: ObservationId,
|
||||
/// Index of the next fragment to send.
|
||||
@@ -45,8 +47,8 @@ pub struct InFlight {
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
/// Why the [`Exchange`] dropped an inbound frame it could not use; reported
|
||||
/// via [`NodeEvent::Dropped`]. A peer's [`Exchange`] rejects expired addressed
|
||||
/// Why the `Exchange` dropped an inbound frame it could not use; reported
|
||||
/// via [`NodeEvent::Dropped`]. A peer's `Exchange` rejects expired addressed
|
||||
/// observations by policy — those are *not* counted as drops.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@@ -57,7 +59,7 @@ pub enum DropReason {
|
||||
/// The version byte read off the wire.
|
||||
version: u8,
|
||||
},
|
||||
/// The frame was not a valid [`Frame`] (truncated, garbage, or empty).
|
||||
/// The frame was not a valid `Frame` (truncated, garbage, or empty).
|
||||
Malformed,
|
||||
}
|
||||
|
||||
@@ -91,7 +93,7 @@ pub enum NodeEvent<A> {
|
||||
},
|
||||
}
|
||||
|
||||
/// The [`Exchange`]'s counted-drop table: up to [`consts::DROP_TABLE_CAP`]
|
||||
/// The `Exchange`'s counted-drop table: up to [`consts::DROP_TABLE_CAP`]
|
||||
/// distinct `(addr, DropReason)` pairs, each with a saturating count, plus a
|
||||
/// single overflow counter. Counters are drained — and reset — through
|
||||
/// [`Exchange::drain_events`].
|
||||
@@ -163,8 +165,8 @@ impl<A: Clone + PartialEq> DropTable<A> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A single peer the [`Exchange`] is tracking.
|
||||
pub struct Peer<A> {
|
||||
/// A single peer the `Exchange` is tracking.
|
||||
pub(crate) struct Peer<A> {
|
||||
/// Wire address of the peer.
|
||||
pub addr: A,
|
||||
/// Node id learned from the first frame received from this peer.
|
||||
@@ -266,7 +268,7 @@ struct UndoAck<A> {
|
||||
}
|
||||
|
||||
/// The exchange state machine, tracking up to [`consts::MAX_PEERS`] peers.
|
||||
pub struct Exchange<A> {
|
||||
pub(crate) struct Exchange<A> {
|
||||
me: NodeId,
|
||||
salt: u32,
|
||||
peers: Vec<Peer<A>, { consts::MAX_PEERS }>,
|
||||
@@ -763,12 +765,14 @@ impl<A: Clone + PartialEq + core::fmt::Debug> Exchange<A> {
|
||||
Some((addr, n))
|
||||
}
|
||||
|
||||
// Only reached from alloc-gated tests, so unused under --no-default-features.
|
||||
#[allow(dead_code)]
|
||||
/// Returns the current peers.
|
||||
pub fn peers(&self) -> &[Peer<A>] {
|
||||
&self.peers
|
||||
}
|
||||
|
||||
/// [`Exchange`]s emit [`NodeEvent`]s — confirmed deliveries plus counted
|
||||
/// `Exchange`s emit [`NodeEvent`]s — confirmed deliveries plus counted
|
||||
/// drops — through `Exchange::drain_events`; `tines` surfaces them to its
|
||||
/// transport, so the drain method is unused inside `tines-core` itself.
|
||||
///
|
||||
@@ -3103,7 +3107,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A frame that passes the version gate (byte 0 is WIRE_VERSION) but whose
|
||||
/// body isn't a valid postcard [`Frame`] is a counted malformed drop.
|
||||
/// body isn't a valid postcard `Frame` is a counted malformed drop.
|
||||
#[test]
|
||||
fn malformed_is_counted() {
|
||||
let mut store = MemStore::new();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
//! Fragmentation and reassembly of `Observation`s over a byte-oriented link.
|
||||
|
||||
use crate::consts;
|
||||
use crate::{Frame, NodeId, Observation, ObservationId, Tick, WireError};
|
||||
use crate::{
|
||||
wire::{Frame, WireError},
|
||||
NodeId, Observation, ObservationId, Tick,
|
||||
};
|
||||
use heapless::Vec;
|
||||
|
||||
/// Header allowance per fragment (bytes), pinned.
|
||||
pub const FRAG_OVERHEAD: usize = 65;
|
||||
pub(crate) const FRAG_OVERHEAD: usize = 65;
|
||||
|
||||
/// The most fragments one observation may be split into: the reassembler
|
||||
/// tracks received indices in a `u64` bitset.
|
||||
@@ -27,9 +30,11 @@ pub(crate) fn plan(len: usize, mtu: usize) -> Option<(usize, usize)> {
|
||||
Some((chunk, count))
|
||||
}
|
||||
|
||||
// Only reached from alloc-gated tests, so unused under --no-default-features.
|
||||
#[allow(dead_code)]
|
||||
/// Encodes `obs` as one Obs frame if it fits in `mtu`, else as N Frag frames.
|
||||
/// Returns the number of frames; writes each via `emit(frame_bytes)`.
|
||||
pub fn fragment(
|
||||
pub(crate) fn fragment(
|
||||
from: NodeId,
|
||||
obs: &Observation,
|
||||
mtu: usize,
|
||||
@@ -74,7 +79,7 @@ pub fn fragment(
|
||||
}
|
||||
|
||||
/// A single-slot reassembler for inbound Frag streams.
|
||||
pub struct Reassembler {
|
||||
pub(crate) struct Reassembler {
|
||||
id: Option<ObservationId>,
|
||||
count: u8,
|
||||
received: u64,
|
||||
|
||||
@@ -5,29 +5,27 @@
|
||||
extern crate alloc;
|
||||
|
||||
pub mod belief;
|
||||
pub mod bloom;
|
||||
mod bloom;
|
||||
pub mod consts;
|
||||
pub mod exchange;
|
||||
pub mod frag;
|
||||
mod exchange;
|
||||
mod frag;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod mem_store;
|
||||
pub mod node;
|
||||
pub mod observation;
|
||||
pub mod store;
|
||||
pub mod transport;
|
||||
pub mod wire;
|
||||
mod wire;
|
||||
|
||||
pub use belief::{BeliefPolicy, LatestWins, SelfFirst};
|
||||
pub use bloom::Bloom;
|
||||
pub use exchange::{DropReason, Exchange, NodeEvent, Peer};
|
||||
pub use frag::{fragment, Reassembler};
|
||||
pub use exchange::{DropReason, NodeEvent};
|
||||
pub use node::{Node, ObserveError};
|
||||
pub use observation::{
|
||||
Dest, NodeId, Observation, ObservationId, Payload, SigAlgo, Signature, Tick, Topic,
|
||||
};
|
||||
pub use store::{Store, StoreError, TrimPolicy};
|
||||
pub use transport::{Event, FrameBuf, SendError, Transport};
|
||||
pub use wire::{Frame, WireError};
|
||||
pub use wire::frame_sender;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use mem_store::MemStore;
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
//! Node facade for tines-core.
|
||||
//!
|
||||
//! [`Node`] is the single entry point that ties the [`Store`], the
|
||||
//! [`Exchange`] state machine, and a [`Transport`] together behind the two
|
||||
//! `Exchange` state machine, and a [`Transport`] together behind the two
|
||||
//! operations an application needs: [`Node::observe`] to record an observation,
|
||||
//! and [`Node::poll`] to drive the exchange.
|
||||
//!
|
||||
//! The facade never touches the heap: its only owned buffer, `out`, is a fixed
|
||||
//! `[u8; MAX_FRAME]`. It also panics on nothing — oversized topics/payloads
|
||||
//! become [`ObserveError::TooLarge`] rather than truncating, and malformed
|
||||
//! transport frames are dropped by the [`Exchange`].
|
||||
//! transport frames are dropped by the `Exchange`.
|
||||
|
||||
use crate::{
|
||||
consts, BeliefPolicy, Dest, Exchange, NodeEvent, NodeId, Observation, ObservationId, Payload,
|
||||
Store, StoreError, Tick, Topic, Transport, TrimPolicy,
|
||||
consts, exchange::Exchange, BeliefPolicy, Dest, NodeEvent, NodeId, Observation, ObservationId,
|
||||
Payload, Store, StoreError, Tick, Topic, Transport, TrimPolicy,
|
||||
};
|
||||
|
||||
/// Errors returned by [`Node::observe`].
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ObserveError {
|
||||
/// The topic or payload exceeded `MAX_TOPIC`/`MAX_PAYLOAD`. The
|
||||
@@ -105,7 +106,7 @@ impl<S: Store, T: Transport> Node<S, T> {
|
||||
///
|
||||
/// The resulting observation has `dest = Some(Dest { to, lifetime_ms })`,
|
||||
/// so the exchange will deliver it only
|
||||
/// to `to` (see [`Exchange`]). Returns the observation's content-derived
|
||||
/// to `to` (see `Exchange`). Returns the observation's content-derived
|
||||
/// id. An oversized topic or payload is rejected with
|
||||
/// [`ObserveError::TooLarge`] rather than truncated; a store rejection is
|
||||
/// surfaced as [`ObserveError::Store`]. Duplicate ids are accepted (the id
|
||||
@@ -143,7 +144,7 @@ impl<S: Store, T: Transport> Node<S, T> {
|
||||
/// Returns the number of *new* observations stored this round. If the
|
||||
/// transport refuses the frame with `Busy` or `Unreachable`, the exchange
|
||||
/// rolls back and offers the same frame on a later poll; a `TooLarge`
|
||||
/// frame is dropped (see [`Exchange::on_send_error`]).
|
||||
/// frame is dropped (see `Exchange::on_send_error`).
|
||||
pub fn poll(&mut self, now: Tick) -> usize {
|
||||
let mut stored = 0usize;
|
||||
while let Some(ev) = self.transport.poll() {
|
||||
@@ -165,7 +166,7 @@ impl<S: Store, T: Transport> Node<S, T> {
|
||||
stored
|
||||
}
|
||||
|
||||
/// Drains the [`NodeEvent`]s the [`Exchange`] has collected: confirmed
|
||||
/// Drains the [`NodeEvent`]s the `Exchange` has collected: confirmed
|
||||
/// deliveries first (oldest-first), then one [`NodeEvent::Dropped`] per
|
||||
/// counted drop.
|
||||
///
|
||||
|
||||
@@ -35,6 +35,11 @@ pub struct Dest {
|
||||
}
|
||||
|
||||
/// An observation published by a node, with content-derived identity.
|
||||
///
|
||||
/// `#[non_exhaustive]`: a future 1.x may add fields, so build it via the
|
||||
/// constructors on [`Observation`] rather than a struct literal outside
|
||||
/// `tines-core`.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Observation {
|
||||
pub observer: NodeId,
|
||||
|
||||
@@ -13,6 +13,10 @@ use crate::observation::{Observation, ObservationId, Tick};
|
||||
/// Rules are applied in the order listed here: per-topic retention, then a
|
||||
/// global age cutoff, then a global count cap. Any field left as `None` is
|
||||
/// ignored.
|
||||
///
|
||||
/// `#[non_exhaustive]`: build via [`TrimPolicy::default`] and set fields, so a
|
||||
/// future 1.x can add policy knobs without a breaking change.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TrimPolicy {
|
||||
/// Keep the `N` youngest (lowest current age) observations per exact topic.
|
||||
@@ -24,6 +28,7 @@ pub struct TrimPolicy {
|
||||
}
|
||||
|
||||
/// Errors returned by [`Store`] operations.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum StoreError {
|
||||
/// The store is full and cannot accept new observations.
|
||||
|
||||
@@ -15,6 +15,7 @@ pub type FrameBuf = heapless::Vec<u8, { consts::MAX_FRAME }>;
|
||||
/// `Contact`/`Lost`; the `large_enum_variant` lint is intentionally suppressed
|
||||
/// to keep `FrameBuf` off the heap in the `no_std` core.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Event<A> {
|
||||
/// A peer became reachable.
|
||||
@@ -26,6 +27,7 @@ pub enum Event<A> {
|
||||
}
|
||||
|
||||
/// Reasons a `Transport::send` call can fail.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SendError {
|
||||
/// The transport is busy with an in-flight send and cannot accept another.
|
||||
|
||||
@@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
|
||||
/// large, so the informative `large_enum_variant` lint is suppressed here.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Frame {
|
||||
pub(crate) enum Frame {
|
||||
Summary {
|
||||
from: NodeId,
|
||||
salt: u32,
|
||||
@@ -85,7 +85,7 @@ impl Frame {
|
||||
|
||||
/// Peek the [`NodeId`] a wire frame was sent by, without fully decoding it.
|
||||
///
|
||||
/// A [`Frame`] envelope is a `WIRE_VERSION` byte, then an externally-tagged
|
||||
/// A `Frame` envelope is a `WIRE_VERSION` byte, then an externally-tagged
|
||||
/// postcard enum: `bytes[0]` is the version, `bytes[1]` the variant tag, and
|
||||
/// `from` (the first field) occupies the 16 bytes at `bytes[2..18]`. This skips
|
||||
/// the two leading bytes, reads those 16 bytes, and returns `None` for anything
|
||||
@@ -114,7 +114,7 @@ pub fn frame_sender(bytes: &[u8]) -> Option<NodeId> {
|
||||
|
||||
/// Errors produced while (de)serializing frames on the wire.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum WireError {
|
||||
pub(crate) enum WireError {
|
||||
/// The output buffer was too small to hold the serialized frame.
|
||||
TooLarge,
|
||||
/// The input bytes were malformed, truncated, or not a valid `Frame`.
|
||||
|
||||
Reference in New Issue
Block a user