proto: no_std hygiene for the fmt/alloc imports and the filter math

Bring the proto tree closer to a std+alloc-only posture, behavior unchanged on
every decision path:

- Sweep the remaining std::fmt and std::collections imports over to core::fmt
  and alloc::collections across the wire codecs and their tests. Subsystem files
  that shadow the core name with a child core module use the leading-colon
  ::core::fmt form. Imports only.

- Replace the two f64::powi calls (bloom false-positive-rate, FMP backoff timer)
  with a shared core-only square-and-multiply helper in proto/math, bit-identical
  to std::powi (a guard test pins this against every exponent the codecs reach),
  and route the diagnostic estimated-count natural log through libm::log. The FPR
  reject decision and the backoff timer are bit-for-bit unchanged; only the
  debug-only count estimate may differ by at most one ULP.
This commit is contained in:
Johnathan Corgan
2026-07-08 19:00:25 +00:00
parent 5d13090d8f
commit 2b009196b5
14 changed files with 69 additions and 11 deletions

1
Cargo.lock generated
View File

@@ -1087,6 +1087,7 @@ dependencies = [
"hex",
"hkdf",
"libc",
"libm",
"mdns-sd",
"nostr",
"nostr-sdk",

View File

@@ -17,6 +17,7 @@ secp256k1 = { version = "0.30", features = ["rand", "global-context"] }
sha2 = "0.10"
hkdf = "0.12"
ring = "0.17"
libm = "0.2"
rand = "0.10.1"
crossbeam-channel = "0.5"
thiserror = "2.0"

View File

@@ -141,7 +141,7 @@ impl BloomFilter {
/// Current false-positive rate: fill ratio raised to the hash count.
pub fn fpr(&self) -> f64 {
self.fill_ratio().powi(self.hash_count as i32)
crate::proto::math::powi(self.fill_ratio(), self.hash_count as u32)
}
/// Estimate the number of elements in the filter.
@@ -170,7 +170,7 @@ impl BloomFilter {
return None;
}
Some(-(m / k) * (1.0 - fill).ln())
Some(-(m / k) * libm::log(1.0 - fill))
}
/// Check if the filter is empty.

View File

@@ -1,6 +1,6 @@
//! Tests for `BloomState` (announcement state management).
use std::collections::BTreeMap;
use alloc::collections::BTreeMap;
use crate::proto::bloom::{BloomFilter, BloomState};
use crate::testutil::make_node_addr;

View File

@@ -640,5 +640,5 @@ impl Fmp {
/// arithmetic (the exponent is the resend count *after* this attempt).
fn next_resend_at_ms(now_ms: u64, interval_ms: u64, backoff: f64, prior_count: u32) -> u64 {
let count = prior_count + 1;
now_ms + (interval_ms as f64 * backoff.powi(count as i32)) as u64
now_ms + (interval_ms as f64 * crate::proto::math::powi(backoff, count)) as u64
}

View File

@@ -9,7 +9,7 @@
use crate::proto::Error;
use crate::proto::codec::Reader;
use crate::proto::link::LinkMessageType;
use std::fmt;
use ::core::fmt;
/// Handshake message type identifiers.
///

View File

@@ -9,7 +9,7 @@ use crate::proto::fsp::core::{
use crate::proto::fsp::limits::FSP_CUTOVER_DELAY_MS;
use crate::proto::stp::TreeCoordinate;
use crate::testutil::make_node_addr;
use std::collections::VecDeque;
use alloc::collections::VecDeque;
fn coords(byte: u8) -> TreeCoordinate {
TreeCoordinate::from_addrs(vec![make_node_addr(byte)]).unwrap()

View File

@@ -35,7 +35,7 @@
use crate::proto::Error;
use crate::proto::codec::{Reader, Writer};
use crate::proto::stp::{TreeCoordinate, decode_coords, decode_optional_coords, encode_coords};
use std::fmt;
use ::core::fmt;
// ============================================================================
// Constants

View File

@@ -2,7 +2,7 @@
use crate::NodeAddr;
use crate::proto::Error;
use std::fmt;
use core::fmt;
// ============================================================================
// Link-Layer Message Types

55
src/proto/math.rs Normal file
View File

@@ -0,0 +1,55 @@
//! no_std-shaped numeric helpers for the protocol cores.
//!
//! `f64::powi` and the transcendental methods live in `std`, not `core`. The
//! helpers here keep the small amount of protocol math the codecs need in a
//! `core`-only shape so the cores stay portable, without changing any result.
/// Raise `base` to a non-negative integer power via square-and-multiply.
///
/// Bit-identical to `f64::powi` for the exponents the codecs use: it mirrors the
/// compiler-rt `__powidf2` multiply order (multiply-then-square, skipping the
/// final square), so — floating-point multiplication being non-associative — it
/// reproduces `powi` exactly rather than a naive left-to-right product. The
/// `powi_bit_identical_to_std` test pins this. Keeping it bit-identical matters:
/// the bloom false-positive-rate feeds a reject comparison and the FMP backoff
/// feeds a `u64` timer, so any drift could change a decision.
pub(crate) fn powi(base: f64, exp: u32) -> f64 {
let mut result = 1.0_f64;
let mut b = base;
let mut e = exp;
loop {
if e & 1 == 1 {
result *= b;
}
e >>= 1;
if e == 0 {
break;
}
b *= b;
}
result
}
#[cfg(test)]
mod tests {
use super::powi;
#[test]
fn powi_bit_identical_to_std() {
// The core square-and-multiply must match f64::powi bit-for-bit across
// representative bases and every exponent the protocol math reaches, so
// the FPR reject decision and the backoff timer are unchanged.
let bases = [
0.0, 1.0, 0.5, 0.5469, 0.5493, 0.5508, 0.9999, 1.5, 2.0, 3.7, 0.1, 1e-3,
];
for &base in &bases {
for exp in 0u32..=64 {
assert_eq!(
powi(base, exp).to_bits(),
base.powi(exp as i32).to_bits(),
"powi mismatch at base={base}, exp={exp}"
);
}
}
}
}

View File

@@ -13,6 +13,7 @@ pub(crate) mod discovery;
pub(crate) mod fmp;
pub(crate) mod fsp;
pub(crate) mod link;
pub(crate) mod math;
pub(crate) mod mmp;
pub(crate) mod rate_limit;
pub(crate) mod routing;

View File

@@ -1,6 +1,6 @@
//! Flap dampening / hold-down unit tests.
use std::collections::{BTreeMap, BTreeSet};
use alloc::collections::{BTreeMap, BTreeSet};
use super::util::{make_coords, make_costs, make_node_addr};
use crate::proto::stp::{ParentDeclaration, ParentEval, TreeState};

View File

@@ -1,6 +1,6 @@
//! ParentDeclaration, TreeState, parent-selection, and find_next_hop unit tests.
use std::collections::{BTreeMap, BTreeSet};
use alloc::collections::{BTreeMap, BTreeSet};
use super::util::{add_peer, make_coords, make_costs, make_node_addr, make_tree_state};
use crate::proto::stp::{ParentDeclaration, ParentEval, TreeCoordinate, TreeState};

View File

@@ -1,6 +1,6 @@
//! Shared test helpers for the STP primitive unit tests.
use std::collections::BTreeMap;
use alloc::collections::BTreeMap;
use crate::NodeAddr;
use crate::proto::stp::{ParentDeclaration, TreeCoordinate, TreeState};