Files
fips/src/proto/bloom/tests/wire.rs
T
Johnathan Corgan 4ed674ea8b proto/bloom: relocate bloom filter into sans-IO layout
Migrate the v1 bloom filter subsystem into src/proto/bloom/, matching the
discovery/routing/fmp/mmp/stp reference layout and completing the proto/
relocation series for the data/wire subsystems.

- wire.rs: the FilterAnnounce (0x20) codec, moved from protocol/filter.rs
- core.rs: the pure BloomFilter algorithm (hash/insert/contains/merge/
  as_bytes/from_bytes/estimated_count), moved from bloom/filter.rs
- state.rs: BloomState (per-peer inbound store, compute_outgoing_filter,
  the injected-clock send debounce), moved from bloom/state.rs
- limits.rs: the v1 sizing constants
- mod.rs: module wiring + the BloomError enum
- tests/: the unit suite split by target (core/state/wire), no inline tests

no_std+alloc hygiene: core::fmt over std::fmt, the tracing dependency
dropped from the pure filter, and std collections replaced with
BTreeMap/BTreeSet (NodeAddr: Ord) for deterministic iteration. The pure
filter combination stays a BloomState method; the two irreducible shell
gathers (peer_inbound_filters, build_filter_announce) remain in the async
shell. Wire bytes and observable behavior are unchanged; full local CI
green (36/36) including the bloom-storm chaos gate.
2026-07-07 23:10:12 +00:00

100 lines
3.1 KiB
Rust

//! Tests for the bloom wire codec (`FilterAnnounce`).
use crate::proto::bloom::BloomFilter;
use crate::proto::bloom::FilterAnnounce;
use crate::protocol::LinkMessageType;
use crate::testutil::make_node_addr;
#[test]
fn test_filter_announce_size_class() {
let filter = BloomFilter::new();
let announce = FilterAnnounce::new(filter.clone(), 100);
// v1 defaults
assert_eq!(announce.size_class, 1);
assert_eq!(announce.hash_count, 5);
assert!(announce.is_v1_compliant());
assert!(announce.is_valid());
assert_eq!(announce.filter_size_bytes(), 1024);
}
#[test]
fn test_filter_announce_with_size_class() {
let filter = BloomFilter::with_params(2048 * 8, 7).unwrap();
let announce = FilterAnnounce::with_size_class(filter, 100, 2);
assert_eq!(announce.size_class, 2);
assert_eq!(announce.hash_count, 7);
assert!(!announce.is_v1_compliant());
assert!(announce.is_valid());
assert_eq!(announce.filter_size_bytes(), 2048);
}
#[test]
fn test_filter_announce_encode_decode_roundtrip() {
let mut filter = BloomFilter::new();
filter.insert(&make_node_addr(42));
filter.insert(&make_node_addr(99));
let announce = FilterAnnounce::new(filter, 500);
let encoded = announce.encode().unwrap();
// msg_type(1) + sequence(8) + hash_count(1) + size_class(1) + filter(1024)
assert_eq!(encoded.len(), 1035);
assert_eq!(encoded[0], LinkMessageType::FilterAnnounce.to_byte());
// Decode strips msg_type (as dispatcher does)
let decoded = FilterAnnounce::decode(&encoded[1..]).unwrap();
assert_eq!(decoded.sequence, 500);
assert_eq!(decoded.hash_count, 5);
assert_eq!(decoded.size_class, 1);
assert!(decoded.is_valid());
assert!(decoded.is_v1_compliant());
// Filter contents preserved
assert!(decoded.filter.contains(&make_node_addr(42)));
assert!(decoded.filter.contains(&make_node_addr(99)));
assert!(!decoded.filter.contains(&make_node_addr(1)));
}
#[test]
fn test_filter_announce_decode_rejects_bad_size_class() {
let filter = BloomFilter::new();
let announce = FilterAnnounce::new(filter, 100);
let mut encoded = announce.encode().unwrap();
// Corrupt size_class byte (offset: 1 msg_type + 8 seq + 1 hash = 10)
encoded[10] = 5; // invalid size_class > MAX_SIZE_CLASS
let result = FilterAnnounce::decode(&encoded[1..]);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("invalid size_class")
);
}
#[test]
fn test_filter_announce_decode_rejects_non_v1_size_class() {
// Build a size_class=0 payload manually (valid range but not v1)
let filter = BloomFilter::with_params(512 * 8, 5).unwrap();
let announce = FilterAnnounce::with_size_class(filter, 100, 0);
let encoded = announce.encode().unwrap();
let result = FilterAnnounce::decode(&encoded[1..]);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("unsupported size_class")
);
}
#[test]
fn test_filter_announce_decode_rejects_truncated() {
let result = FilterAnnounce::decode(&[0u8; 5]);
assert!(result.is_err());
}