Add FMP node profile negotiation with mixed-profile integration tests

NodeProfile enum (Full/NonRouting/Leaf) with FMP feature bitfield:
bits 0-2 profile, bits 3-6 MMP wants/provides, bit 7 bloom filter
size negotiable. Bloom size TLV always sent with min=max=1KB.

Config mapping: leaf_only -> Leaf, disable_routing -> NonRouting.
Profile and agreed bloom size stored on PeerConnection and
ActivePeer. Handshake msg2/msg3 carry FMP negotiation payload
with profile validation and bloom size agreement.

MMP report sending gated by profile wants/provides. Parent
selection and routing constraints skip non-full peers. One-way
bloom filters for non-routing peers (F inserts N as dependent).
Leaf mode: single-peer enforcement, suppress tree announces and
discovery forwarding.

Mixed-profile integration test: A(Full) + B(Full) + C(NonRouting)
+ D(Leaf) with 9 connectivity assertions.
This commit is contained in:
Johnathan Corgan
2026-04-11 08:16:01 +00:00
parent 8357200b0e
commit f4278f1dd7
20 changed files with 1323 additions and 299 deletions
+9 -3
View File
@@ -30,11 +30,15 @@ impl Node {
/// Send a TreeAnnounce to a specific peer, respecting rate limits.
///
/// If the peer is rate-limited, the announce is marked pending for
/// delivery on the next tick cycle.
/// delivery on the next tick cycle. Leaf nodes do not send tree
/// announces (they don't participate in the spanning tree).
pub(super) async fn send_tree_announce_to_peer(
&mut self,
peer_addr: &NodeAddr,
) -> Result<(), NodeError> {
if self.node_profile == crate::protocol::NodeProfile::Leaf {
return Ok(());
}
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
@@ -221,7 +225,8 @@ impl Node {
.filter(|(_, peer)| peer.has_srtt())
.map(|(addr, peer)| (*addr, peer.link_cost()))
.collect();
if let Some(new_parent) = self.tree_state.evaluate_parent(&peer_costs) {
let skip = self.non_full_peers();
if let Some(new_parent) = self.tree_state.evaluate_parent(&peer_costs, &skip) {
let new_seq = self.tree_state.my_declaration().sequence() + 1;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@@ -372,7 +377,8 @@ impl Node {
.map(|(addr, peer)| (*addr, peer.link_cost()))
.collect();
if let Some(new_parent) = self.tree_state.evaluate_parent(&peer_costs) {
let skip = self.non_full_peers();
if let Some(new_parent) = self.tree_state.evaluate_parent(&peer_costs, &skip) {
let new_seq = self.tree_state.my_declaration().sequence() + 1;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)