use anyhow::Result;
use ed25519_dalek::Keypair;
use p2p_foundation::security::*;
use std::collections::HashSet;
use std::net::Ipv6Addr;
use std::str::FromStr;
use std::time::Duration;
#[tokio::test]
async fn test_ipv6_node_id_generation_comprehensive() -> Result<()> {
let mut csprng = rand::rngs::OsRng {};
let keypair = Keypair::generate(&mut csprng);
let test_addresses = [
"2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8:85a3::8a2e:370:7334", "::1", "fe80::1", "2001:db8::1", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ];
let mut generated_ids = HashSet::new();
for addr_str in &test_addresses {
let ipv6_addr = Ipv6Addr::from_str(addr_str)?;
let node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
assert_eq!(node_id.ipv6_addr, ipv6_addr);
assert_eq!(node_id.public_key.len(), 32);
assert_eq!(node_id.signature.len(), 64);
assert_eq!(node_id.salt.len(), 16);
assert_eq!(node_id.node_id.len(), 32);
assert!(node_id.verify()?);
assert!(generated_ids.insert(node_id.node_id.clone()),
"Generated duplicate node ID for address: {}", addr_str);
}
Ok(())
}
#[tokio::test]
async fn test_node_id_verification_edge_cases() -> Result<()> {
let mut csprng = rand::rngs::OsRng {};
let keypair = Keypair::generate(&mut csprng);
let ipv6_addr = Ipv6Addr::from_str("2001:0db8:85a3:1234:5678:8a2e:0370:7334")?;
let node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
let test_cases: Vec<(&str, Box<dyn Fn(IPv6NodeID) -> IPv6NodeID>)> = vec![
("corrupted_signature_byte_0", Box::new(|mut n: IPv6NodeID| { n.signature[0] ^= 0xFF; n })),
("corrupted_signature_byte_63", Box::new(|mut n: IPv6NodeID| { n.signature[63] ^= 0xFF; n })),
("corrupted_node_id_byte_0", Box::new(|mut n: IPv6NodeID| { n.node_id[0] ^= 0xFF; n })),
("corrupted_node_id_byte_31", Box::new(|mut n: IPv6NodeID| { n.node_id[31] ^= 0xFF; n })),
("corrupted_public_key_byte_0", Box::new(|mut n: IPv6NodeID| { n.public_key[0] ^= 0xFF; n })),
("corrupted_public_key_byte_31", Box::new(|mut n: IPv6NodeID| { n.public_key[31] ^= 0xFF; n })),
("corrupted_salt", Box::new(|mut n: IPv6NodeID| { n.salt[0] ^= 0xFF; n })),
("corrupted_timestamp", Box::new(|mut n: IPv6NodeID| { n.timestamp_secs += 1; n })),
("empty_signature", Box::new(|mut n: IPv6NodeID| { n.signature = vec![]; n })),
("short_signature", Box::new(|mut n: IPv6NodeID| { n.signature = vec![0u8; 32]; n })),
("long_signature", Box::new(|mut n: IPv6NodeID| { n.signature = vec![0u8; 128]; n })),
("empty_public_key", Box::new(|mut n: IPv6NodeID| { n.public_key = vec![]; n })),
("short_public_key", Box::new(|mut n: IPv6NodeID| { n.public_key = vec![0u8; 16]; n })),
("long_public_key", Box::new(|mut n: IPv6NodeID| { n.public_key = vec![0u8; 64]; n })),
("empty_node_id", Box::new(|mut n: IPv6NodeID| { n.node_id = vec![]; n })),
("short_node_id", Box::new(|mut n: IPv6NodeID| { n.node_id = vec![0u8; 16]; n })),
("different_ipv6", Box::new(|mut n: IPv6NodeID| {
n.ipv6_addr = Ipv6Addr::from_str("2001:0db8:85a3:1234:5678:8a2e:0370:7335").unwrap();
n
})),
];
for (test_name, corruption_fn) in test_cases {
let corrupted_node_id = corruption_fn(node_id.clone());
match corrupted_node_id.verify() {
Ok(false) => {
println!("✓ Correctly detected corruption: {}", test_name);
},
Ok(true) => {
panic!("Verification should fail for corruption: {}", test_name);
},
Err(_) => {
println!("✓ Verification error (invalid format) for: {}", test_name);
}
}
}
Ok(())
}
#[tokio::test]
async fn test_subnet_extraction_comprehensive() -> Result<()> {
let test_cases = vec![
("2001:0db8:85a3:1234:5678:8a2e:0370:7334", "2001:db8:85a3:1234::", "2001:db8:85a3::", "2001:db8::"),
("::", "::", "::", "::"),
("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff::", "ffff:ffff:ffff::", "ffff:ffff::"),
("fe80::1", "fe80::", "fe80::", "fe80::"),
("2001:0db8:85a3:0000::", "2001:db8:85a3::", "2001:db8:85a3::", "2001:db8::"),
("2001:0db8::", "2001:db8::", "2001:db8::", "2001:db8::"),
];
for (input, expected_64, expected_48, expected_32) in test_cases {
let addr = Ipv6Addr::from_str(input)?;
let prefix_64 = IPDiversityEnforcer::extract_subnet_prefix(addr, 64);
let prefix_48 = IPDiversityEnforcer::extract_subnet_prefix(addr, 48);
let prefix_32 = IPDiversityEnforcer::extract_subnet_prefix(addr, 32);
assert_eq!(prefix_64, Ipv6Addr::from_str(expected_64)?,
"Failed /64 extraction for {}", input);
assert_eq!(prefix_48, Ipv6Addr::from_str(expected_48)?,
"Failed /48 extraction for {}", input);
assert_eq!(prefix_32, Ipv6Addr::from_str(expected_32)?,
"Failed /32 extraction for {}", input);
}
let addr = Ipv6Addr::from_str("2001:0db8:85a3:1234:5678:8a2e:0370:7334")?;
let test_prefixes = vec![
(8, "2000::"), (16, "2001::"), (24, "2001:d00::"), (32, "2001:db8::"), (40, "2001:db8:8500::"), (48, "2001:db8:85a3::"), (56, "2001:db8:85a3:1200::"), (64, "2001:db8:85a3:1234::"), (96, "2001:db8:85a3:1234:5678:8a2e::"), (128, "2001:db8:85a3:1234:5678:8a2e:370:7334"), ];
for (prefix_len, expected) in test_prefixes {
let result = IPDiversityEnforcer::extract_subnet_prefix(addr, prefix_len);
assert_eq!(result, Ipv6Addr::from_str(expected)?,
"Failed /{} extraction", prefix_len);
}
Ok(())
}
#[tokio::test]
async fn test_ip_diversity_comprehensive() -> Result<()> {
let config = IPDiversityConfig {
max_nodes_per_64: 2,
max_nodes_per_48: 4,
max_nodes_per_32: 8,
max_nodes_per_asn: 16,
enable_geolocation_check: false,
min_geographic_diversity: 1,
};
let mut enforcer = IPDiversityEnforcer::new(config);
let base_addresses = vec![
"2001:0db8:1234:0001:0001:8a2e:0370:7334",
"2001:0db8:1234:0001:0002:8a2e:0370:7334", "2001:0db8:1234:0002:0001:8a2e:0370:7334", "2001:0db8:1234:0002:0002:8a2e:0370:7334", "2001:0db8:1234:0003:0001:8a2e:0370:7334", "2001:0db8:1234:0003:0002:8a2e:0370:7334", "2001:0db8:1234:0004:0001:8a2e:0370:7334", "2001:0db8:1234:0004:0002:8a2e:0370:7334", "2001:0db8:1234:0005:0001:8a2e:0370:7334",
"2001:0db8:5678:0001:0001:8a2e:0370:7334",
"2001:0db8:5678:0001:0002:8a2e:0370:7334", "2001:0db8:5678:0002:0001:8a2e:0370:7334", ];
let mut accepted_count = 0;
let mut rejected_count = 0;
for addr_str in &base_addresses {
let addr = Ipv6Addr::from_str(addr_str)?;
let analysis = enforcer.analyze_ip(addr)?;
if enforcer.can_accept_node(&analysis) {
enforcer.add_node(&analysis)?;
accepted_count += 1;
println!("✅ Accepted: {}", addr_str);
} else {
rejected_count += 1;
println!("❌ Rejected: {}", addr_str);
}
}
assert!(accepted_count <= 8, "Should not accept more than 8 nodes total (2 per /64, max 4 /64s per /48)");
assert!(rejected_count > 0, "Should reject some nodes due to limits");
let stats = enforcer.get_diversity_stats();
assert!(stats.total_64_subnets <= 8, "Should have at most 8 /64 subnets");
assert!(stats.total_48_subnets <= 2, "Should have at most 2 /48 subnets");
assert!(stats.total_32_subnets <= 1, "Should have at most 1 /32 subnet");
assert_eq!(stats.max_nodes_per_64, 2, "Should have exactly 2 nodes per /64 at max");
println!("Final stats: {:?}", stats);
Ok(())
}
#[tokio::test]
async fn test_node_removal_and_readd() -> Result<()> {
let config = IPDiversityConfig {
max_nodes_per_64: 1,
max_nodes_per_48: 2,
..Default::default()
};
let mut enforcer = IPDiversityEnforcer::new(config);
let addr1 = Ipv6Addr::from_str("2001:0db8:85a3:1234:5678:8a2e:0370:7334")?;
let addr2 = Ipv6Addr::from_str("2001:0db8:85a3:1234:abcd:8a2e:0370:7334")?; let addr3 = Ipv6Addr::from_str("2001:0db8:85a3:5678:5678:8a2e:0370:7334")?;
let analysis1 = enforcer.analyze_ip(addr1)?;
let analysis2 = enforcer.analyze_ip(addr2)?;
let analysis3 = enforcer.analyze_ip(addr3)?;
assert!(enforcer.can_accept_node(&analysis1));
enforcer.add_node(&analysis1)?;
assert!(!enforcer.can_accept_node(&analysis2));
assert!(enforcer.can_accept_node(&analysis3));
enforcer.add_node(&analysis3)?;
enforcer.remove_node(&analysis1);
assert!(enforcer.can_accept_node(&analysis2));
enforcer.add_node(&analysis2)?;
let stats = enforcer.get_diversity_stats();
assert_eq!(stats.total_64_subnets, 2);
assert_eq!(stats.max_nodes_per_64, 1);
Ok(())
}
#[tokio::test]
async fn test_reputation_system_comprehensive() -> Result<()> {
use p2p_foundation::PeerId;
let mut reputation_manager = ReputationManager::new(0.1, 0.1);
let peer_id = PeerId::from("test-peer-12345");
assert!(reputation_manager.get_reputation(&peer_id).is_none());
let response_times = vec![50, 75, 60, 80, 45, 55, 70, 65];
for (i, &time_ms) in response_times.iter().enumerate() {
let success = i % 4 != 3; reputation_manager.update_reputation(&peer_id, success, Duration::from_millis(time_ms));
}
let reputation = reputation_manager.get_reputation(&peer_id).unwrap();
println!("After mixed interactions: response_rate={:.3}, response_time={:?}, interactions={}",
reputation.response_rate, reputation.response_time, reputation.interaction_count);
assert!(reputation.response_rate > 0.55 && reputation.response_rate < 0.75);
assert_eq!(reputation.interaction_count, 8);
assert!(reputation.response_time.as_millis() > 40 && reputation.response_time.as_millis() < 300);
for _ in 0..10 {
reputation_manager.update_reputation(&peer_id, false, Duration::from_millis(2000));
}
let reputation = reputation_manager.get_reputation(&peer_id).unwrap();
println!("After failures: response_rate={:.3}", reputation.response_rate);
assert!(reputation.response_rate < 0.5, "Response rate should decrease significantly");
for _ in 0..20 {
reputation_manager.update_reputation(&peer_id, true, Duration::from_millis(30));
}
let reputation = reputation_manager.get_reputation(&peer_id).unwrap();
println!("After recovery: response_rate={:.3}, response_time={:?}",
reputation.response_rate, reputation.response_time);
assert!(reputation.response_rate > 0.7, "Should recover with consistent success");
assert!(reputation.response_time.as_millis() < 500, "Response time should improve from failure level");
Ok(())
}
#[tokio::test]
async fn test_reputation_decay() -> Result<()> {
use p2p_foundation::PeerId;
let mut reputation_manager = ReputationManager::new(2.0, 0.1); let peer_id = PeerId::from("test-peer-decay");
for _ in 0..10 {
reputation_manager.update_reputation(&peer_id, true, Duration::from_millis(50));
}
let initial_reputation = reputation_manager.get_reputation(&peer_id).unwrap().response_rate;
println!("Initial reputation: {:.3}", initial_reputation);
for i in 0..5 {
reputation_manager.apply_decay();
if let Some(reputation) = reputation_manager.get_reputation(&peer_id) {
println!("After decay cycle {}: {:.3}", i + 1, reputation.response_rate);
} else {
println!("Reputation removed after decay cycle {}", i + 1);
break;
}
}
if let Some(reputation) = reputation_manager.get_reputation(&peer_id) {
assert!(reputation.response_rate <= initial_reputation,
"Reputation should not increase from decay");
}
Ok(())
}
#[tokio::test]
async fn test_sybil_attack_simulation() -> Result<()> {
let config = IPDiversityConfig {
max_nodes_per_64: 1,
max_nodes_per_48: 3,
max_nodes_per_32: 5,
max_nodes_per_asn: 10,
..Default::default()
};
let mut enforcer = IPDiversityEnforcer::new(config);
let mut attacker_success = 0;
let mut attacker_blocked = 0;
for i in 0..100 {
let addr_str = format!("2001:0db8:85a3:{:04x}:5678:8a2e:0370:7334", i);
let addr = Ipv6Addr::from_str(&addr_str)?;
let analysis = enforcer.analyze_ip(addr)?;
if enforcer.can_accept_node(&analysis) {
enforcer.add_node(&analysis)?;
attacker_success += 1;
} else {
attacker_blocked += 1;
}
}
println!("Single /48 attack results: {} accepted, {} blocked", attacker_success, attacker_blocked);
assert!(attacker_success <= 3, "Should block most nodes from same /48");
assert!(attacker_blocked >= 97, "Should block at least 97 nodes");
let mut multi48_success = 0;
let mut multi48_blocked = 0;
for i in 4..20 { let addr_str = format!("2001:0db8:{:04x}:0001:5678:8a2e:0370:7334", i);
let addr = Ipv6Addr::from_str(&addr_str)?;
let analysis = enforcer.analyze_ip(addr)?;
if enforcer.can_accept_node(&analysis) {
enforcer.add_node(&analysis)?;
multi48_success += 1;
} else {
multi48_blocked += 1;
}
}
println!("Multiple /48 attack results: {} accepted, {} blocked", multi48_success, multi48_blocked);
assert!(attacker_success + multi48_success <= 5, "Should enforce /32 limits");
let final_stats = enforcer.get_diversity_stats();
println!("Final attack simulation stats: {:?}", final_stats);
assert!(final_stats.total_32_subnets <= 1, "All nodes should be in same /32");
assert!(final_stats.max_nodes_per_32 <= 5, "Should not exceed /32 limit");
Ok(())
}
#[tokio::test]
async fn test_performance_benchmarks() -> Result<()> {
use std::time::Instant;
let mut csprng = rand::rngs::OsRng {};
let keypair = Keypair::generate(&mut csprng);
let ipv6_addr = Ipv6Addr::from_str("2001:0db8:85a3:1234:5678:8a2e:0370:7334")?;
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let _node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
}
let generation_time = start.elapsed();
let avg_generation = generation_time / iterations;
println!("Node ID generation: {} iterations in {:?} (avg: {:?})",
iterations, generation_time, avg_generation);
assert!(avg_generation < Duration::from_millis(10),
"Node ID generation too slow: {:?}", avg_generation);
let node_ids: Vec<_> = (0..iterations).map(|_| {
IPv6NodeID::generate(ipv6_addr, &keypair).unwrap()
}).collect();
let start = Instant::now();
for node_id in &node_ids {
assert!(node_id.verify().unwrap());
}
let verification_time = start.elapsed();
let avg_verification = verification_time / iterations;
println!("Node ID verification: {} iterations in {:?} (avg: {:?})",
iterations, verification_time, avg_verification);
assert!(avg_verification < Duration::from_millis(10),
"Node ID verification too slow: {:?}", avg_verification);
let config = IPDiversityConfig::default();
let mut enforcer = IPDiversityEnforcer::new(config);
let start = Instant::now();
let node_count = 1000;
for i in 0..node_count {
let addr_str = format!("2001:{:04x}:85a3:1234:5678:8a2e:0370:7334", i);
let addr = Ipv6Addr::from_str(&addr_str)?;
let analysis = enforcer.analyze_ip(addr)?;
if enforcer.can_accept_node(&analysis) {
enforcer.add_node(&analysis)?;
}
}
let enforcement_time = start.elapsed();
println!("IP diversity enforcement: {} nodes processed in {:?}",
node_count, enforcement_time);
assert!(enforcement_time < Duration::from_secs(1),
"IP diversity enforcement too slow: {:?}", enforcement_time);
let stats = enforcer.get_diversity_stats();
println!("Performance test final stats: {:?}", stats);
Ok(())
}
#[tokio::test]
async fn test_concurrent_operations() -> Result<()> {
use std::sync::Arc;
use tokio::sync::Mutex;
let config = IPDiversityConfig::default();
let enforcer = Arc::new(Mutex::new(IPDiversityEnforcer::new(config)));
let mut handles = Vec::new();
for i in 0..10 {
let enforcer_clone = enforcer.clone();
let handle = tokio::spawn(async move {
let addr_str = format!("2001:0db8:{:04x}:1234:5678:8a2e:0370:7334", i);
let addr = Ipv6Addr::from_str(&addr_str).unwrap();
let mut guard = enforcer_clone.lock().await;
let analysis = guard.analyze_ip(addr).unwrap();
if guard.can_accept_node(&analysis) {
guard.add_node(&analysis).unwrap();
true
} else {
false
}
});
handles.push(handle);
}
let results: Vec<bool> = futures::future::join_all(handles)
.await
.into_iter()
.map(|r| r.unwrap())
.collect();
let accepted_count = results.iter().filter(|&&accepted| accepted).count();
println!("Concurrent operations: {} accepted out of 10", accepted_count);
assert_eq!(accepted_count, 10, "All concurrent operations should succeed");
let guard = enforcer.lock().await;
let stats = guard.get_diversity_stats();
assert_eq!(stats.total_48_subnets, 10);
Ok(())
}
#[tokio::test]
async fn test_serialization() -> Result<()> {
let mut csprng = rand::rngs::OsRng {};
let keypair = Keypair::generate(&mut csprng);
let ipv6_addr = Ipv6Addr::from_str("2001:0db8:85a3:1234:5678:8a2e:0370:7334")?;
let original_node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
let json_data = serde_json::to_string(&original_node_id)?;
let deserialized_node_id: IPv6NodeID = serde_json::from_str(&json_data)?;
assert_eq!(deserialized_node_id.node_id, original_node_id.node_id);
assert_eq!(deserialized_node_id.ipv6_addr, original_node_id.ipv6_addr);
assert_eq!(deserialized_node_id.public_key, original_node_id.public_key);
assert_eq!(deserialized_node_id.signature, original_node_id.signature);
assert_eq!(deserialized_node_id.timestamp_secs, original_node_id.timestamp_secs);
assert_eq!(deserialized_node_id.salt, original_node_id.salt);
assert!(deserialized_node_id.verify()?);
let config = IPDiversityConfig::default();
let enforcer = IPDiversityEnforcer::new(config);
let stats = enforcer.get_diversity_stats();
let stats_json = serde_json::to_string(&stats)?;
let deserialized_stats: DiversityStats = serde_json::from_str(&stats_json)?;
assert_eq!(deserialized_stats.total_64_subnets, stats.total_64_subnets);
assert_eq!(deserialized_stats.total_48_subnets, stats.total_48_subnets);
Ok(())
}
#[tokio::test]
async fn test_memory_management() -> Result<()> {
let config = IPDiversityConfig::default();
let mut enforcer = IPDiversityEnforcer::new(config);
let mut analyses = Vec::new();
for i in 0..100 {
let addr_str = format!("2001:{:04x}:85a3:1234:5678:8a2e:0370:7334", i);
let addr = Ipv6Addr::from_str(&addr_str)?;
let analysis = enforcer.analyze_ip(addr)?;
if enforcer.can_accept_node(&analysis) {
enforcer.add_node(&analysis)?;
analyses.push(analysis);
}
}
let initial_stats = enforcer.get_diversity_stats();
println!("After adding nodes: {:?}", initial_stats);
for analysis in &analyses {
enforcer.remove_node(analysis);
}
let final_stats = enforcer.get_diversity_stats();
println!("After removing nodes: {:?}", final_stats);
assert_eq!(final_stats.total_64_subnets, 0);
assert_eq!(final_stats.total_48_subnets, 0);
assert_eq!(final_stats.total_32_subnets, 0);
assert_eq!(final_stats.max_nodes_per_64, 0);
assert_eq!(final_stats.max_nodes_per_48, 0);
assert_eq!(final_stats.max_nodes_per_32, 0);
Ok(())
}
#[tokio::test]
async fn test_time_edge_cases() -> Result<()> {
let mut csprng = rand::rngs::OsRng {};
let keypair = Keypair::generate(&mut csprng);
let ipv6_addr = Ipv6Addr::from_str("2001:0db8:85a3:1234:5678:8a2e:0370:7334")?;
let mut node_ids = Vec::new();
let mut timestamps = HashSet::new();
for _ in 0..10 {
let node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
assert!(node_id.verify()?);
timestamps.insert(node_id.timestamp_secs);
node_ids.push(node_id);
}
let unique_node_ids: HashSet<_> = node_ids.iter().map(|n| &n.node_id).collect();
assert_eq!(unique_node_ids.len(), node_ids.len(),
"All node IDs should be unique even with same timestamp");
println!("Generated {} unique timestamps out of {} node IDs",
timestamps.len(), node_ids.len());
Ok(())
}