refactor(core): make local constants crate-private
Decision K (spec §5): the local capacities and timings are defaults that will become configuration, so they are `pub(crate)`. The wire constants stay public. - MAX_PEERS, SUMMARY_INTERVAL_MS, REASSEMBLY_TIMEOUT_MS, SEND_QUEUE_CAP, MIN_MTU, EVENT_QUEUE_CAP, DROP_TABLE_CAP, BCAST_SENT_CAP, ACK_BUFFER_CAP and PEER_IDLE_MS are now `pub(crate)`; a compile_fail doctest pins it. - MIN_MTU's doc claimed it was the floor below which a fragment has no room. It is only the frag sweep's lower bound (BLE ATT's 23). The real floors are now stated on `Transport::mtu` and pinned by `consts::tests::mtu_floors`: 85 to carry every valid observation, 66 to fragment at all, 35 for an Ack, 26 for a summary. - tines-bt caps live connections with its own MAX_PEERS (8). - tines-sim's props mirror MAX_PEERS/SEND_QUEUE_CAP, checked against core through the hidden `conformance` module; the fuzz twin reads it there. - node.rs: drain_events' rustdoc no longer links the now-private consts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,7 +53,7 @@ use bluer::{
|
|||||||
Adapter, Address, AddressType, Session, Uuid, UuidExt,
|
Adapter, Address, AddressType, Session, Uuid, UuidExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use tines_core::consts::{MAX_FRAME, MAX_PEERS};
|
use tines_core::consts::MAX_FRAME;
|
||||||
use tines_core::frame_sender;
|
use tines_core::frame_sender;
|
||||||
use tines_core::observation::NodeId;
|
use tines_core::observation::NodeId;
|
||||||
use tines_core::transport::{Event, FrameBuf, SendError, Transport};
|
use tines_core::transport::{Event, FrameBuf, SendError, Transport};
|
||||||
@@ -230,6 +230,12 @@ impl Transport for BtTransport {
|
|||||||
/// Smallest MTU we are willing to report (ATT's default minimum is 23).
|
/// Smallest MTU we are willing to report (ATT's default minimum is 23).
|
||||||
const MIN_MTU: usize = 23;
|
const MIN_MTU: usize = 23;
|
||||||
|
|
||||||
|
/// Most live connections at once. Kept equal to tines-core's peer-table
|
||||||
|
/// capacity (8, a crate-private local constant since tines-core 1.0, decision
|
||||||
|
/// K): a connection beyond it would carry frames core's exchange has no slot
|
||||||
|
/// for. Both become configuration in a later release.
|
||||||
|
const MAX_PEERS: usize = 8;
|
||||||
|
|
||||||
/// L2CAP PSM the tines byte pipe runs on. Shared with the peer (agreed
|
/// L2CAP PSM the tines byte pipe runs on. Shared with the peer (agreed
|
||||||
/// out-of-band): both sides connect/listen on this PSM.
|
/// out-of-band): both sides connect/listen on this PSM.
|
||||||
const TINES_L2CAP_PSM: u16 = 0x008D;
|
const TINES_L2CAP_PSM: u16 = 0x008D;
|
||||||
|
|||||||
+86
-14
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
/// WIRE. The first byte of every frame on the wire; changing it is a
|
/// WIRE. The first byte of every frame on the wire; changing it is a
|
||||||
/// `WIRE_VERSION` bump. `crate::wire::Frame::decode` rejects a leading byte
|
/// `WIRE_VERSION` bump. `crate::wire::Frame::decode` rejects a leading byte
|
||||||
/// that differs (`WireError::VersionMismatch`), and [`crate::wire::frame_sender`]
|
/// that differs (`WireError::VersionMismatch`), and [`crate::frame_sender`]
|
||||||
/// emits this. `4` is the first value in the `4..=0x7F` range that fits a
|
/// emits this. `4` is the first value in the `4..=0x7F` range that fits a
|
||||||
/// single-byte postcard enum tag and sits above the 0.x tag values (`0..=3`).
|
/// single-byte postcard enum tag and sits above the 0.x tag values (`0..=3`).
|
||||||
pub const WIRE_VERSION: u8 = 4;
|
pub const WIRE_VERSION: u8 = 4;
|
||||||
@@ -51,40 +51,112 @@ pub const ACK_IDS_CAP: usize = 90;
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// LOCAL. Node-local peer table; never sent on the wire.
|
/// LOCAL. Node-local peer table; never sent on the wire.
|
||||||
pub const MAX_PEERS: usize = 8;
|
///
|
||||||
|
/// Local constants are crate-private (decision K):
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// let _ = tines_core::consts::MAX_PEERS;
|
||||||
|
/// ```
|
||||||
|
pub(crate) const MAX_PEERS: usize = 8;
|
||||||
|
|
||||||
/// LOCAL. Emission cadence; local scheduling only.
|
/// LOCAL. Emission cadence; local scheduling only.
|
||||||
pub const SUMMARY_INTERVAL_MS: u64 = 2000;
|
pub(crate) const SUMMARY_INTERVAL_MS: u64 = 2000;
|
||||||
|
|
||||||
/// LOCAL. Inbound reassembly timer; local only.
|
/// LOCAL. Inbound reassembly timer; local only.
|
||||||
pub const REASSEMBLY_TIMEOUT_MS: u64 = 5000;
|
pub(crate) const REASSEMBLY_TIMEOUT_MS: u64 = 5000;
|
||||||
|
|
||||||
/// LOCAL. Outbound delivery buffer size; local only.
|
/// LOCAL. Outbound delivery buffer size; local only.
|
||||||
pub const SEND_QUEUE_CAP: usize = 256;
|
pub(crate) const SEND_QUEUE_CAP: usize = 256;
|
||||||
|
|
||||||
/// LOCAL. BLE floor / MTU-sweep lower bound: below this a fragment header
|
/// LOCAL. Lower bound of the fragmentation MTU sweep (`crate::frag`): the
|
||||||
/// leaves no room for payload. Used by the frag sweep
|
/// BLE ATT default MTU. It is **not** the smallest MTU core can use: that is
|
||||||
/// (`crate::frag`) and the BLE transport clamp; never carried on the wire.
|
/// 85 bytes (see [`crate::Transport::mtu`] for every floor, pinned by
|
||||||
pub const MIN_MTU: usize = 23;
|
/// `tests::mtu_floors`). Between 23 and 85 core still never emits an
|
||||||
|
/// oversized frame; it just can't send everything. Only tests and the hidden
|
||||||
|
/// `conformance` module use it.
|
||||||
|
#[cfg(any(test, feature = "conformance"))]
|
||||||
|
pub(crate) const MIN_MTU: usize = 23;
|
||||||
|
|
||||||
/// LOCAL. In-node `Exchange` `events` queue of `NodeEvent`s; never leaves the
|
/// LOCAL. In-node `Exchange` `events` queue of `NodeEvent`s; never leaves the
|
||||||
/// node (unlike the wire `ACK_IDS_CAP`).
|
/// node (unlike the wire `ACK_IDS_CAP`).
|
||||||
pub const EVENT_QUEUE_CAP: usize = 64;
|
pub(crate) const EVENT_QUEUE_CAP: usize = 64;
|
||||||
|
|
||||||
/// LOCAL. `Exchange` counted-drop table of distinct `(addr, DropReason)`
|
/// LOCAL. `Exchange` counted-drop table of distinct `(addr, DropReason)`
|
||||||
/// pairs. Once full, further counted drops fold into a single overflow counter
|
/// pairs. Once full, further counted drops fold into a single overflow counter
|
||||||
/// (reported with no address); see [`crate::exchange::NodeEvent::Dropped`].
|
/// (reported with no address); see [`crate::exchange::NodeEvent::Dropped`].
|
||||||
pub const DROP_TABLE_CAP: usize = 32;
|
pub(crate) const DROP_TABLE_CAP: usize = 32;
|
||||||
|
|
||||||
/// LOCAL. `Exchange` per-peer `bcast_sent` table of broadcast ids dispatched
|
/// LOCAL. `Exchange` per-peer `bcast_sent` table of broadcast ids dispatched
|
||||||
/// to a peer.
|
/// to a peer.
|
||||||
pub const BCAST_SENT_CAP: usize = 256;
|
pub(crate) const BCAST_SENT_CAP: usize = 256;
|
||||||
|
|
||||||
/// LOCAL. Pending ack-id buffer (`acks` for addressed, `bcast_acks` for
|
/// LOCAL. Pending ack-id buffer (`acks` for addressed, `bcast_acks` for
|
||||||
/// broadcast) before the next Ack is emitted; overflow drops the oldest entry.
|
/// broadcast) before the next Ack is emitted; overflow drops the oldest entry.
|
||||||
pub const ACK_BUFFER_CAP: usize = 256;
|
pub(crate) const ACK_BUFFER_CAP: usize = 256;
|
||||||
|
|
||||||
/// LOCAL. Idle eviction window: a peer with no inbound frame for longer than
|
/// LOCAL. Idle eviction window: a peer with no inbound frame for longer than
|
||||||
/// this many ticks is removed as if Lost (`crate::exchange`). Never crosses
|
/// this many ticks is removed as if Lost (`crate::exchange`). Never crosses
|
||||||
/// the wire; local only.
|
/// the wire; local only.
|
||||||
pub const PEER_IDLE_MS: u64 = 30_000;
|
pub(crate) const PEER_IDLE_MS: u64 = 30_000;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{MAX_FRAME, MAX_PAYLOAD, MAX_SIG, MAX_TOPIC};
|
||||||
|
use crate::frag::{fragment, plan, FRAG_OVERHEAD};
|
||||||
|
use crate::wire::Frame;
|
||||||
|
use crate::{Dest, Observation, Payload, SigAlgo, Signature, Topic};
|
||||||
|
|
||||||
|
/// The MTU floors `Transport::mtu`'s rustdoc states (decision K: it
|
||||||
|
/// "states the smallest MTU core can use").
|
||||||
|
#[test]
|
||||||
|
fn mtu_floors() {
|
||||||
|
let mut buf = [0u8; MAX_FRAME];
|
||||||
|
|
||||||
|
// A Summary with a 1-byte filter and a worst-case (5-byte varint)
|
||||||
|
// salt is 25 bytes, so summaries need mtu >= 26.
|
||||||
|
let summary = Frame::Summary {
|
||||||
|
from: [0xFF; 16],
|
||||||
|
salt: u32::MAX,
|
||||||
|
bits: heapless::Vec::from_slice(&[0xFF]).unwrap(),
|
||||||
|
};
|
||||||
|
assert_eq!(summary.encode(&mut buf), Ok(25));
|
||||||
|
|
||||||
|
// An Ack naming one id is 35 bytes.
|
||||||
|
let ack = Frame::Ack {
|
||||||
|
from: [0xFF; 16],
|
||||||
|
ids: heapless::Vec::from_slice(&[[0xFF; 16]]).unwrap(),
|
||||||
|
};
|
||||||
|
assert_eq!(ack.encode(&mut buf), Ok(35));
|
||||||
|
|
||||||
|
// Fragmentation needs room past the fragment header.
|
||||||
|
assert_eq!(plan(1, FRAG_OVERHEAD), None);
|
||||||
|
assert_eq!(plan(1, FRAG_OVERHEAD + 1), Some((1, 1)));
|
||||||
|
assert_eq!(FRAG_OVERHEAD + 1, 66);
|
||||||
|
|
||||||
|
// The largest valid observation (signed, addressed, max topic and
|
||||||
|
// payload, worst-case varints) encodes to 1233 bytes and first fits
|
||||||
|
// in MAX_FRAGMENTS fragments at mtu 85.
|
||||||
|
let worst = Observation {
|
||||||
|
observer: [0xFF; 16],
|
||||||
|
seq: u64::MAX,
|
||||||
|
age_ms: u64::MAX,
|
||||||
|
observed_at: Some(u64::MAX),
|
||||||
|
topic: Topic::from_slice(&[0xFF; MAX_TOPIC]).unwrap(),
|
||||||
|
payload: Payload::from_slice(&[0xFF; MAX_PAYLOAD]).unwrap(),
|
||||||
|
dest: Some(Dest {
|
||||||
|
to: [0xFF; 16],
|
||||||
|
lifetime_ms: u64::MAX,
|
||||||
|
}),
|
||||||
|
sig: Some(Signature {
|
||||||
|
version: u8::MAX,
|
||||||
|
algo: SigAlgo::Ed25519,
|
||||||
|
bytes: heapless::Vec::from_slice(&[0xFF; MAX_SIG]).unwrap(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let mut obs_buf = [0u8; MAX_FRAME * 2];
|
||||||
|
let len = postcard::to_slice(&worst, &mut obs_buf).unwrap().len();
|
||||||
|
assert_eq!(len, 1233);
|
||||||
|
assert!(fragment([0xFF; 16], &worst, 84, &mut |_| {}).is_err());
|
||||||
|
assert!(fragment([0xFF; 16], &worst, 85, &mut |_| {}).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -171,8 +171,8 @@ impl<S: Store, T: Transport> Node<S, T> {
|
|||||||
/// counted drop.
|
/// counted drop.
|
||||||
///
|
///
|
||||||
/// The returned [`heapless::Vec`] is capped at
|
/// The returned [`heapless::Vec`] is capped at
|
||||||
/// [`consts::EVENT_QUEUE_CAP`] confirmed deliveries plus the entire
|
/// `EVENT_QUEUE_CAP` (64) confirmed deliveries plus the entire
|
||||||
/// [`consts::DROP_TABLE_CAP`]-entry drop table and its overflow slot. Index
|
/// `DROP_TABLE_CAP` (32)-entry drop table and its overflow slot. Index
|
||||||
/// it directly — a [`NodeEvent::Delivered`] names the peer that held our
|
/// it directly — a [`NodeEvent::Delivered`] names the peer that held our
|
||||||
/// broadcast and the broadcast's content-ID; a [`NodeEvent::Dropped`] names
|
/// broadcast and the broadcast's content-ID; a [`NodeEvent::Dropped`] names
|
||||||
/// the peer and reason a dropped frame was rejected with.
|
/// the peer and reason a dropped frame was rejected with.
|
||||||
|
|||||||
@@ -50,6 +50,17 @@ pub trait Transport {
|
|||||||
fn send(&mut self, to: &Self::Addr, frame: &[u8]) -> Result<(), SendError>;
|
fn send(&mut self, to: &Self::Addr, frame: &[u8]) -> Result<(), SendError>;
|
||||||
|
|
||||||
/// Maximum frame size in bytes this transport can send (must be `<= MAX_FRAME`).
|
/// Maximum frame size in bytes this transport can send (must be `<= MAX_FRAME`).
|
||||||
|
///
|
||||||
|
/// The smallest MTU core can fully use is **85 bytes**: at 85 every valid
|
||||||
|
/// observation, up to a maximum-size, signed, addressed one, fits in the
|
||||||
|
/// 64 fragments a peer will reassemble. Below that core never emits an
|
||||||
|
/// oversized frame, but it sends less:
|
||||||
|
/// - below 85, the largest observations can't be sent;
|
||||||
|
/// - below 66, nothing is fragmented, so only observations that fit
|
||||||
|
/// whole in one frame go out;
|
||||||
|
/// - below 35, no receipt fits, so none is sent and every sender keeps
|
||||||
|
/// custody of what it sent;
|
||||||
|
/// - below 26, no summary fits.
|
||||||
fn mtu(&self) -> usize;
|
fn mtu(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
mod fuzz;
|
mod fuzz;
|
||||||
|
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use tines_core::consts::{MAX_FRAME, MAX_PAYLOAD, MAX_PEERS, MAX_TOPIC, SUMMARY_BYTES};
|
use tines_core::conformance::MAX_PEERS;
|
||||||
|
use tines_core::consts::{MAX_FRAME, MAX_PAYLOAD, MAX_TOPIC, SUMMARY_BYTES};
|
||||||
use tines_core::Store;
|
use tines_core::Store;
|
||||||
|
|
||||||
// A single bounded case count keeps the twin fast under `cargo test` while
|
// A single bounded case count keeps the twin fast under `cargo test` while
|
||||||
|
|||||||
@@ -23,12 +23,28 @@
|
|||||||
//! addressed candidate a turn. Pins §6's overflow-safety guarantee.
|
//! addressed candidate a turn. Pins §6's overflow-safety guarantee.
|
||||||
|
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use tines_core::{
|
use tines_core::{FrameBuf, Store};
|
||||||
consts::{MAX_PEERS, SEND_QUEUE_CAP},
|
|
||||||
FrameBuf, Store,
|
|
||||||
};
|
|
||||||
use tines_sim::{CappedStore, Contact, ContactTrace, Sim, WorldConfig};
|
use tines_sim::{CappedStore, Contact, ContactTrace, Sim, WorldConfig};
|
||||||
|
|
||||||
|
/// Mirrors tines-core's peer-table capacity, a crate-private local constant
|
||||||
|
/// since 1.0 (decision K). `local_caps_match_core` checks the mirror.
|
||||||
|
const MAX_PEERS: usize = 8;
|
||||||
|
|
||||||
|
/// Mirrors tines-core's send-queue / bounce-table capacity, a crate-private
|
||||||
|
/// local constant since 1.0 (decision K). `local_caps_match_core` checks the
|
||||||
|
/// mirror.
|
||||||
|
const SEND_QUEUE_CAP: usize = 256;
|
||||||
|
|
||||||
|
/// The mirrored local caps still equal core's. Runs with the `conformance`
|
||||||
|
/// feature (as `scripts/ci.sh` does), which exposes core's values through its
|
||||||
|
/// hidden `conformance` module.
|
||||||
|
#[cfg(feature = "conformance")]
|
||||||
|
#[test]
|
||||||
|
fn local_caps_match_core() {
|
||||||
|
assert_eq!(MAX_PEERS, tines_core::conformance::MAX_PEERS);
|
||||||
|
assert_eq!(SEND_QUEUE_CAP, tines_core::conformance::SEND_QUEUE_CAP);
|
||||||
|
}
|
||||||
|
|
||||||
/// A fixed set of short observation topics the tests reuse so the generated
|
/// A fixed set of short observation topics the tests reuse so the generated
|
||||||
/// observations round-trip cleanly through the core (`MAX_TOPIC` is 64 bytes).
|
/// observations round-trip cleanly through the core (`MAX_TOPIC` is 64 bytes).
|
||||||
const TOPICS: [&[u8]; 8] = [
|
const TOPICS: [&[u8]; 8] = [
|
||||||
@@ -166,7 +182,7 @@ fn addressed_scenario() -> impl Strategy<Value = (usize, usize, ContactTrace)> {
|
|||||||
/// graph plus a flood of distinct addressed observations past [`SEND_QUEUE_CAP`].
|
/// graph plus a flood of distinct addressed observations past [`SEND_QUEUE_CAP`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BounceScenario {
|
struct BounceScenario {
|
||||||
/// Node count (fixed at [`consts::MAX_PEERS`]).
|
/// Node count (fixed at [`MAX_PEERS`]).
|
||||||
nodes: usize,
|
nodes: usize,
|
||||||
/// Fully-connected trace: every pair in contact during one long window.
|
/// Fully-connected trace: every pair in contact during one long window.
|
||||||
trace: ContactTrace,
|
trace: ContactTrace,
|
||||||
|
|||||||
Reference in New Issue
Block a user