Skip to main content

saorsa_core/
security.rs

1// Copyright 2024 Saorsa Labs Limited
2//
3// This software is dual-licensed under:
4// - GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
5// - Commercial License
6//
7// For AGPL-3.0 license, see LICENSE-AGPL-3.0
8// For commercial licensing, contact: david@saorsalabs.com
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under these licenses is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
14//! Security module
15//!
16//! This module provides cryptographic functionality and Sybil protection for the P2P network.
17//! It implements IPv6-based node ID generation and IP diversity enforcement to prevent
18//! large-scale Sybil attacks while maintaining network openness.
19
20use crate::PeerId;
21use crate::quantum_crypto::ant_quic_integration::{
22    MlDsaPublicKey, MlDsaSecretKey, MlDsaSignature, ml_dsa_sign, ml_dsa_verify,
23};
24use anyhow::{Result, anyhow};
25use lru::LruCache;
26use serde::{Deserialize, Serialize};
27use sha2::{Digest, Sha256};
28use std::collections::HashMap;
29use std::fmt::Debug;
30use std::net::{Ipv4Addr, Ipv6Addr};
31use std::num::NonZeroUsize;
32use std::time::{Duration, SystemTime, UNIX_EPOCH};
33
34use std::sync::Arc;
35
36/// Maximum subnet tracking entries before evicting oldest (prevents memory DoS)
37const MAX_SUBNET_TRACKING: usize = 50_000;
38
39// ============================================================================
40// Generic IP Address Trait
41// ============================================================================
42
43/// Trait for IP addresses that can be used in node ID generation
44pub trait NodeIpAddress: Debug + Clone + Send + Sync + 'static {
45    /// Get the octets of this IP address for hashing
46    fn octets_vec(&self) -> Vec<u8>;
47}
48
49impl NodeIpAddress for Ipv6Addr {
50    fn octets_vec(&self) -> Vec<u8> {
51        self.octets().to_vec()
52    }
53}
54
55impl NodeIpAddress for Ipv4Addr {
56    fn octets_vec(&self) -> Vec<u8> {
57        self.octets().to_vec()
58    }
59}
60
61// ============================================================================
62// Generic IP-Based Node Identity
63// ============================================================================
64
65/// Generic IP-based node identity that binds node ID to network location
66///
67/// This struct provides a unified implementation for both IPv4 and IPv6
68/// node identities, reducing code duplication while maintaining type safety.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct GenericIpNodeID<A: NodeIpAddress> {
71    /// Derived node ID (SHA256 of ip_addr + public_key + salt + timestamp)
72    pub node_id: Vec<u8>,
73    /// IP address this node ID is bound to
74    pub ip_addr: A,
75    /// ML-DSA public key for signatures
76    pub public_key: Vec<u8>,
77    /// Signature proving ownership of the IP address and keys
78    pub signature: Vec<u8>,
79    /// Timestamp when this ID was generated (seconds since epoch)
80    pub timestamp_secs: u64,
81    /// Salt used in node ID generation (for freshness)
82    pub salt: Vec<u8>,
83}
84
85impl<A: NodeIpAddress> GenericIpNodeID<A> {
86    /// ML-DSA-65 signature length
87    const SIGNATURE_LENGTH: usize = 3309;
88
89    /// Generate a new IP-based node ID
90    pub fn generate(ip_addr: A, secret: &MlDsaSecretKey, public: &MlDsaPublicKey) -> Result<Self> {
91        let mut rng = rand::thread_rng();
92        let mut salt = vec![0u8; 16];
93        rand::RngCore::fill_bytes(&mut rng, &mut salt);
94
95        let timestamp = SystemTime::now();
96        let timestamp_secs = timestamp.duration_since(UNIX_EPOCH)?.as_secs();
97        let public_key = public.as_bytes().to_vec();
98        let ip_octets = ip_addr.octets_vec();
99
100        // Generate node ID: SHA256(ip_address || public_key || salt || timestamp)
101        let node_id = Self::compute_node_id(&ip_octets, &public_key, &salt, timestamp_secs);
102
103        // Create signature proving ownership
104        let message_to_sign = Self::build_message(&ip_octets, &public_key, &salt, timestamp_secs);
105        let sig = ml_dsa_sign(secret, &message_to_sign)
106            .map_err(|e| anyhow!("ML-DSA sign failed: {:?}", e))?;
107        let signature = sig.0.to_vec();
108
109        Ok(Self {
110            node_id,
111            ip_addr,
112            public_key,
113            signature,
114            timestamp_secs,
115            salt,
116        })
117    }
118
119    /// Verify that this node ID is valid and properly signed
120    pub fn verify(&self) -> Result<bool> {
121        let ip_octets = self.ip_addr.octets_vec();
122
123        // Reconstruct and verify node ID
124        let expected_node_id = Self::compute_node_id(
125            &ip_octets,
126            &self.public_key,
127            &self.salt,
128            self.timestamp_secs,
129        );
130
131        if expected_node_id != self.node_id {
132            return Ok(false);
133        }
134
135        // Verify signature
136        let public_key = MlDsaPublicKey::from_bytes(&self.public_key)
137            .map_err(|e| anyhow!("Invalid ML-DSA public key: {:?}", e))?;
138
139        if self.signature.len() != Self::SIGNATURE_LENGTH {
140            return Ok(false);
141        }
142
143        let mut sig_bytes = [0u8; 3309];
144        sig_bytes.copy_from_slice(&self.signature);
145        let signature = MlDsaSignature(Box::new(sig_bytes));
146
147        let message_to_verify = Self::build_message(
148            &ip_octets,
149            &self.public_key,
150            &self.salt,
151            self.timestamp_secs,
152        );
153
154        let ok = ml_dsa_verify(&public_key, &message_to_verify, &signature)
155            .map_err(|e| anyhow!("ML-DSA verify error: {:?}", e))?;
156        Ok(ok)
157    }
158
159    /// Get the age of this node ID in seconds
160    pub fn age_secs(&self) -> u64 {
161        let now = SystemTime::now()
162            .duration_since(UNIX_EPOCH)
163            .map(|d| d.as_secs())
164            .unwrap_or(0);
165        now.saturating_sub(self.timestamp_secs)
166    }
167
168    /// Check if the node ID has expired (older than max_age)
169    pub fn is_expired(&self, max_age: Duration) -> bool {
170        self.age_secs() > max_age.as_secs()
171    }
172
173    // Internal helpers
174
175    #[inline]
176    fn compute_node_id(
177        ip_octets: &[u8],
178        public_key: &[u8],
179        salt: &[u8],
180        timestamp_secs: u64,
181    ) -> Vec<u8> {
182        let mut hasher = Sha256::new();
183        hasher.update(ip_octets);
184        hasher.update(public_key);
185        hasher.update(salt);
186        hasher.update(timestamp_secs.to_le_bytes());
187        hasher.finalize().to_vec()
188    }
189
190    #[inline]
191    fn build_message(
192        ip_octets: &[u8],
193        public_key: &[u8],
194        salt: &[u8],
195        timestamp_secs: u64,
196    ) -> Vec<u8> {
197        let mut message = Vec::with_capacity(ip_octets.len() + public_key.len() + salt.len() + 8);
198        message.extend_from_slice(ip_octets);
199        message.extend_from_slice(public_key);
200        message.extend_from_slice(salt);
201        message.extend_from_slice(&timestamp_secs.to_le_bytes());
202        message
203    }
204}
205
206// ============================================================================
207// Backward-Compatible Type Aliases and Wrappers
208// ============================================================================
209
210/// IPv6-based node identity that binds node ID to actual network location
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct IPv6NodeID {
213    /// Derived node ID (SHA256 of ipv6_addr + public_key + salt)
214    pub node_id: Vec<u8>,
215    /// IPv6 address this node ID is bound to
216    pub ipv6_addr: Ipv6Addr,
217    /// ML-DSA public key for signatures
218    pub public_key: Vec<u8>,
219    /// Signature proving ownership of the IPv6 address and keys
220    pub signature: Vec<u8>,
221    /// Timestamp when this ID was generated (seconds since epoch)
222    pub timestamp_secs: u64,
223    /// Salt used in node ID generation (for freshness)
224    pub salt: Vec<u8>,
225}
226
227/// Configuration for IP diversity enforcement at multiple subnet levels
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct IPDiversityConfig {
230    // === IPv6 subnet limits (existing) ===
231    /// Maximum nodes per /64 subnet (default: 1)
232    pub max_nodes_per_64: usize,
233    /// Maximum nodes per /48 allocation (default: 3)
234    pub max_nodes_per_48: usize,
235    /// Maximum nodes per /32 region (default: 10)
236    pub max_nodes_per_32: usize,
237
238    // === IPv4 subnet limits (new) ===
239    /// Maximum nodes per single IPv4 address (/32) - dynamic by default
240    pub max_nodes_per_ipv4_32: usize,
241    /// Maximum nodes per /24 subnet (Class C) - default: 3x per-IP
242    pub max_nodes_per_ipv4_24: usize,
243    /// Maximum nodes per /16 subnet (Class B) - default: 10x per-IP
244    pub max_nodes_per_ipv4_16: usize,
245
246    // === Network-relative limits (new) ===
247    /// Absolute maximum nodes allowed per single IP (default: 50)
248    pub max_per_ip_cap: usize,
249    /// Maximum fraction of network any single IP can represent (default: 0.005 = 0.5%)
250    pub max_network_fraction: f64,
251
252    // === ASN and GeoIP (existing) ===
253    /// Maximum nodes per AS number (default: 20)
254    pub max_nodes_per_asn: usize,
255    /// Enable GeoIP-based diversity checks
256    pub enable_geolocation_check: bool,
257    /// Minimum number of different countries required
258    pub min_geographic_diversity: usize,
259}
260
261/// Analysis of an IPv6 address for diversity enforcement
262#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
263pub struct IPAnalysis {
264    /// /64 subnet (host allocation)
265    pub subnet_64: Ipv6Addr,
266    /// /48 subnet (site allocation)
267    pub subnet_48: Ipv6Addr,
268    /// /32 subnet (ISP allocation)
269    pub subnet_32: Ipv6Addr,
270    /// Autonomous System Number (if available)
271    pub asn: Option<u32>,
272    /// Country code from GeoIP lookup
273    pub country: Option<String>,
274    /// Whether this is a known hosting/VPS provider
275    pub is_hosting_provider: bool,
276    /// Whether this is a known VPN provider
277    pub is_vpn_provider: bool,
278    /// Historical reputation score for this IP range
279    pub reputation_score: f64,
280}
281
282/// Analysis of an IPv4 address for diversity enforcement
283#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
284pub struct IPv4Analysis {
285    /// The exact IPv4 address
286    pub ip_addr: Ipv4Addr,
287    /// /24 subnet (Class C equivalent)
288    pub subnet_24: Ipv4Addr,
289    /// /16 subnet (Class B equivalent)
290    pub subnet_16: Ipv4Addr,
291    /// /8 subnet (Class A equivalent)
292    pub subnet_8: Ipv4Addr,
293    /// Autonomous System Number (if available)
294    pub asn: Option<u32>,
295    /// Country code from GeoIP lookup
296    pub country: Option<String>,
297    /// Whether this is a known hosting/VPS provider
298    pub is_hosting_provider: bool,
299    /// Whether this is a known VPN provider
300    pub is_vpn_provider: bool,
301    /// Historical reputation score for this IP range
302    pub reputation_score: f64,
303}
304
305/// Unified IP analysis that handles both IPv4 and IPv6 addresses
306#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
307pub enum UnifiedIPAnalysis {
308    /// IPv4 address analysis
309    IPv4(IPv4Analysis),
310    /// IPv6 address analysis
311    IPv6(IPAnalysis),
312}
313
314/// Node reputation tracking for security-aware routing
315#[derive(Debug, Clone)]
316pub struct NodeReputation {
317    /// Peer ID
318    pub peer_id: PeerId,
319    /// Fraction of queries answered successfully
320    pub response_rate: f64,
321    /// Average response time
322    pub response_time: Duration,
323    /// Consistency of provided data (0.0-1.0)
324    pub consistency_score: f64,
325    /// Estimated continuous uptime
326    pub uptime_estimate: Duration,
327    /// Accuracy of routing information provided
328    pub routing_accuracy: f64,
329    /// Last time this node was seen
330    pub last_seen: SystemTime,
331    /// Total number of interactions
332    pub interaction_count: u64,
333}
334
335impl Default for IPDiversityConfig {
336    fn default() -> Self {
337        Self {
338            // IPv6 limits
339            max_nodes_per_64: 1,
340            max_nodes_per_48: 3,
341            max_nodes_per_32: 10,
342            // IPv4 limits (defaults based on network-relative formula)
343            max_nodes_per_ipv4_32: 1,  // Will be dynamically adjusted
344            max_nodes_per_ipv4_24: 3,  // 3x per-IP limit
345            max_nodes_per_ipv4_16: 10, // 10x per-IP limit
346            // Network-relative limits
347            max_per_ip_cap: 50,          // Hard cap of 50 nodes per IP
348            max_network_fraction: 0.005, // 0.5% of network max
349            // ASN and GeoIP
350            max_nodes_per_asn: 20,
351            enable_geolocation_check: true,
352            min_geographic_diversity: 3,
353        }
354    }
355}
356
357impl IPDiversityConfig {
358    /// Create a testnet configuration with relaxed diversity requirements.
359    ///
360    /// This is useful for testing environments like Digital Ocean where all nodes
361    /// share the same ASN (AS14061). The relaxed limits allow many nodes from the
362    /// same provider while still maintaining some diversity tracking.
363    ///
364    /// # Warning
365    ///
366    /// This configuration should NEVER be used in production as it significantly
367    /// weakens Sybil attack protection.
368    #[must_use]
369    pub fn testnet() -> Self {
370        Self {
371            // IPv6 relaxed limits
372            max_nodes_per_64: 100,  // Allow many nodes per /64 subnet
373            max_nodes_per_48: 500,  // Allow many nodes per /48 allocation
374            max_nodes_per_32: 1000, // Allow many nodes per /32 region
375            // IPv4 relaxed limits
376            max_nodes_per_ipv4_32: 100,  // Allow many nodes per IPv4
377            max_nodes_per_ipv4_24: 500,  // Allow many nodes per /24
378            max_nodes_per_ipv4_16: 1000, // Allow many nodes per /16
379            // Network-relative limits (relaxed for testnet)
380            max_per_ip_cap: 100,       // Higher cap for testing
381            max_network_fraction: 0.1, // Allow 10% of network from one IP (relaxed from 0.5%)
382            // ASN and GeoIP
383            max_nodes_per_asn: 5000, // Allow many nodes from same ASN (e.g., Digital Ocean)
384            enable_geolocation_check: false, // Disable geo checks for testing
385            min_geographic_diversity: 1, // Single region is acceptable for testing
386        }
387    }
388
389    /// Create a permissive configuration that effectively disables diversity checks.
390    ///
391    /// This is useful for local development and unit testing where all nodes
392    /// run on localhost or the same machine.
393    #[must_use]
394    pub fn permissive() -> Self {
395        Self {
396            // IPv6 - effectively disabled
397            max_nodes_per_64: usize::MAX,
398            max_nodes_per_48: usize::MAX,
399            max_nodes_per_32: usize::MAX,
400            // IPv4 - effectively disabled
401            max_nodes_per_ipv4_32: usize::MAX,
402            max_nodes_per_ipv4_24: usize::MAX,
403            max_nodes_per_ipv4_16: usize::MAX,
404            // Network-relative - effectively disabled
405            max_per_ip_cap: usize::MAX,
406            max_network_fraction: 1.0, // Allow 100% of network
407            // ASN and GeoIP
408            max_nodes_per_asn: usize::MAX,
409            enable_geolocation_check: false,
410            min_geographic_diversity: 0,
411        }
412    }
413
414    /// Check if this is a testnet or permissive configuration.
415    #[must_use]
416    pub fn is_relaxed(&self) -> bool {
417        self.max_nodes_per_asn > 100 || !self.enable_geolocation_check
418    }
419}
420
421impl IPv6NodeID {
422    /// Generate a new IPv6-based node ID
423    ///
424    /// Delegates to `GenericIpNodeID` for the core generation logic.
425    pub fn generate(
426        ipv6_addr: Ipv6Addr,
427        secret: &MlDsaSecretKey,
428        public: &MlDsaPublicKey,
429    ) -> Result<Self> {
430        let generic = GenericIpNodeID::generate(ipv6_addr, secret, public)?;
431        Ok(Self::from_generic(generic))
432    }
433
434    /// Verify that this node ID is valid and properly signed
435    pub fn verify(&self) -> Result<bool> {
436        self.to_generic().verify()
437    }
438
439    /// Extract /64 subnet from IPv6 address
440    pub fn extract_subnet_64(&self) -> Ipv6Addr {
441        let octets = self.ipv6_addr.octets();
442        let mut subnet = [0u8; 16];
443        subnet[..8].copy_from_slice(&octets[..8]);
444        Ipv6Addr::from(subnet)
445    }
446
447    /// Extract /48 subnet from IPv6 address
448    pub fn extract_subnet_48(&self) -> Ipv6Addr {
449        let octets = self.ipv6_addr.octets();
450        let mut subnet = [0u8; 16];
451        subnet[..6].copy_from_slice(&octets[..6]);
452        Ipv6Addr::from(subnet)
453    }
454
455    /// Extract /32 subnet from IPv6 address
456    pub fn extract_subnet_32(&self) -> Ipv6Addr {
457        let octets = self.ipv6_addr.octets();
458        let mut subnet = [0u8; 16];
459        subnet[..4].copy_from_slice(&octets[..4]);
460        Ipv6Addr::from(subnet)
461    }
462
463    // Conversion helpers for delegation
464
465    fn from_generic(g: GenericIpNodeID<Ipv6Addr>) -> Self {
466        Self {
467            node_id: g.node_id,
468            ipv6_addr: g.ip_addr,
469            public_key: g.public_key,
470            signature: g.signature,
471            timestamp_secs: g.timestamp_secs,
472            salt: g.salt,
473        }
474    }
475
476    fn to_generic(&self) -> GenericIpNodeID<Ipv6Addr> {
477        GenericIpNodeID {
478            node_id: self.node_id.clone(),
479            ip_addr: self.ipv6_addr,
480            public_key: self.public_key.clone(),
481            signature: self.signature.clone(),
482            timestamp_secs: self.timestamp_secs,
483            salt: self.salt.clone(),
484        }
485    }
486}
487
488/// IPv4-based node identity that binds node ID to actual network location
489/// Mirrors IPv6NodeID for security parity on IPv4 networks
490#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct IPv4NodeID {
492    /// Derived node ID (SHA256 of ipv4_addr + public_key + salt + timestamp)
493    pub node_id: Vec<u8>,
494    /// IPv4 address this node ID is bound to
495    pub ipv4_addr: Ipv4Addr,
496    /// ML-DSA public key for signatures
497    pub public_key: Vec<u8>,
498    /// Signature proving ownership of the IPv4 address and keys
499    pub signature: Vec<u8>,
500    /// Timestamp when this ID was generated (seconds since epoch)
501    pub timestamp_secs: u64,
502    /// Salt used in node ID generation (for freshness)
503    pub salt: Vec<u8>,
504}
505
506impl IPv4NodeID {
507    /// Generate a new IPv4-based node ID
508    ///
509    /// Delegates to `GenericIpNodeID` for the core generation logic.
510    pub fn generate(
511        ipv4_addr: Ipv4Addr,
512        secret: &MlDsaSecretKey,
513        public: &MlDsaPublicKey,
514    ) -> Result<Self> {
515        let generic = GenericIpNodeID::generate(ipv4_addr, secret, public)?;
516        Ok(Self::from_generic(generic))
517    }
518
519    /// Verify that this node ID is valid and properly signed
520    pub fn verify(&self) -> Result<bool> {
521        self.to_generic().verify()
522    }
523
524    /// Extract /24 subnet from IPv4 address (Class C / most ISP allocations)
525    pub fn extract_subnet_24(&self) -> Ipv4Addr {
526        let octets = self.ipv4_addr.octets();
527        Ipv4Addr::new(octets[0], octets[1], octets[2], 0)
528    }
529
530    /// Extract /16 subnet from IPv4 address (Class B / large ISP allocations)
531    pub fn extract_subnet_16(&self) -> Ipv4Addr {
532        let octets = self.ipv4_addr.octets();
533        Ipv4Addr::new(octets[0], octets[1], 0, 0)
534    }
535
536    /// Extract /8 subnet from IPv4 address (Class A / regional allocations)
537    pub fn extract_subnet_8(&self) -> Ipv4Addr {
538        let octets = self.ipv4_addr.octets();
539        Ipv4Addr::new(octets[0], 0, 0, 0)
540    }
541
542    /// Get the age of this node ID in seconds
543    pub fn age_secs(&self) -> u64 {
544        self.to_generic().age_secs()
545    }
546
547    /// Check if the node ID has expired (older than max_age)
548    pub fn is_expired(&self, max_age: Duration) -> bool {
549        self.to_generic().is_expired(max_age)
550    }
551
552    // Conversion helpers for delegation
553
554    fn from_generic(g: GenericIpNodeID<Ipv4Addr>) -> Self {
555        Self {
556            node_id: g.node_id,
557            ipv4_addr: g.ip_addr,
558            public_key: g.public_key,
559            signature: g.signature,
560            timestamp_secs: g.timestamp_secs,
561            salt: g.salt,
562        }
563    }
564
565    fn to_generic(&self) -> GenericIpNodeID<Ipv4Addr> {
566        GenericIpNodeID {
567            node_id: self.node_id.clone(),
568            ip_addr: self.ipv4_addr,
569            public_key: self.public_key.clone(),
570            signature: self.signature.clone(),
571            timestamp_secs: self.timestamp_secs,
572            salt: self.salt.clone(),
573        }
574    }
575}
576
577/// IP diversity enforcement system
578#[derive(Debug)]
579pub struct IPDiversityEnforcer {
580    config: IPDiversityConfig,
581    // IPv6 tracking (LRU caches with max 50k entries to prevent memory DoS)
582    subnet_64_counts: LruCache<Ipv6Addr, usize>,
583    subnet_48_counts: LruCache<Ipv6Addr, usize>,
584    subnet_32_counts: LruCache<Ipv6Addr, usize>,
585    // IPv4 tracking (LRU caches with max 50k entries to prevent memory DoS)
586    ipv4_32_counts: LruCache<Ipv4Addr, usize>, // Per exact IP
587    ipv4_24_counts: LruCache<Ipv4Addr, usize>, // Per /24 subnet
588    ipv4_16_counts: LruCache<Ipv4Addr, usize>, // Per /16 subnet
589    // Shared tracking (LRU caches with max 50k entries to prevent memory DoS)
590    asn_counts: LruCache<u32, usize>,
591    country_counts: LruCache<String, usize>,
592    geo_provider: Option<Arc<dyn GeoProvider + Send + Sync>>,
593    // Network size for dynamic limits
594    network_size: usize,
595}
596
597impl IPDiversityEnforcer {
598    /// Create a new IP diversity enforcer
599    pub fn new(config: IPDiversityConfig) -> Self {
600        let cache_size = NonZeroUsize::new(MAX_SUBNET_TRACKING).unwrap_or(NonZeroUsize::MIN);
601        Self {
602            config,
603            // IPv6 (LRU caches with bounded size)
604            subnet_64_counts: LruCache::new(cache_size),
605            subnet_48_counts: LruCache::new(cache_size),
606            subnet_32_counts: LruCache::new(cache_size),
607            // IPv4 (LRU caches with bounded size)
608            ipv4_32_counts: LruCache::new(cache_size),
609            ipv4_24_counts: LruCache::new(cache_size),
610            ipv4_16_counts: LruCache::new(cache_size),
611            // Shared (LRU caches with bounded size)
612            asn_counts: LruCache::new(cache_size),
613            country_counts: LruCache::new(cache_size),
614            geo_provider: None,
615            network_size: 0,
616        }
617    }
618
619    /// Create a new IP diversity enforcer with a GeoIP/ASN provider
620    pub fn with_geo_provider(
621        config: IPDiversityConfig,
622        provider: Arc<dyn GeoProvider + Send + Sync>,
623    ) -> Self {
624        let mut s = Self::new(config);
625        s.geo_provider = Some(provider);
626        s
627    }
628
629    /// Analyze an IPv6 address for diversity enforcement
630    pub fn analyze_ip(&self, ipv6_addr: Ipv6Addr) -> Result<IPAnalysis> {
631        let subnet_64 = Self::extract_subnet_prefix(ipv6_addr, 64);
632        let subnet_48 = Self::extract_subnet_prefix(ipv6_addr, 48);
633        let subnet_32 = Self::extract_subnet_prefix(ipv6_addr, 32);
634
635        // GeoIP/ASN lookup via provider if available
636        let (asn, country, is_hosting_provider, is_vpn_provider) =
637            if let Some(p) = &self.geo_provider {
638                let info = p.lookup(ipv6_addr);
639                (
640                    info.asn,
641                    info.country,
642                    info.is_hosting_provider,
643                    info.is_vpn_provider,
644                )
645            } else {
646                (None, None, false, false)
647            };
648
649        // Default reputation for new IPs
650        let reputation_score = 0.5;
651
652        Ok(IPAnalysis {
653            subnet_64,
654            subnet_48,
655            subnet_32,
656            asn,
657            country,
658            is_hosting_provider,
659            is_vpn_provider,
660            reputation_score,
661        })
662    }
663
664    /// Check if a new node can be accepted based on IP diversity constraints
665    pub fn can_accept_node(&self, ip_analysis: &IPAnalysis) -> bool {
666        // Determine limits based on hosting provider status
667        let (limit_64, limit_48, limit_32, limit_asn) =
668            if ip_analysis.is_hosting_provider || ip_analysis.is_vpn_provider {
669                // Stricter limits for hosting providers (halved)
670                (
671                    std::cmp::max(1, self.config.max_nodes_per_64 / 2),
672                    std::cmp::max(1, self.config.max_nodes_per_48 / 2),
673                    std::cmp::max(1, self.config.max_nodes_per_32 / 2),
674                    std::cmp::max(1, self.config.max_nodes_per_asn / 2),
675                )
676            } else {
677                // Regular limits for normal nodes
678                (
679                    self.config.max_nodes_per_64,
680                    self.config.max_nodes_per_48,
681                    self.config.max_nodes_per_32,
682                    self.config.max_nodes_per_asn,
683                )
684            };
685
686        // Check /64 subnet limit (use peek() for read-only access)
687        if let Some(&count) = self.subnet_64_counts.peek(&ip_analysis.subnet_64)
688            && count >= limit_64
689        {
690            return false;
691        }
692
693        // Check /48 subnet limit (use peek() for read-only access)
694        if let Some(&count) = self.subnet_48_counts.peek(&ip_analysis.subnet_48)
695            && count >= limit_48
696        {
697            return false;
698        }
699
700        // Check /32 subnet limit (use peek() for read-only access)
701        if let Some(&count) = self.subnet_32_counts.peek(&ip_analysis.subnet_32)
702            && count >= limit_32
703        {
704            return false;
705        }
706
707        // Check ASN limit (use peek() for read-only access)
708        if let Some(asn) = ip_analysis.asn
709            && let Some(&count) = self.asn_counts.peek(&asn)
710            && count >= limit_asn
711        {
712            return false;
713        }
714
715        true
716    }
717
718    /// Add a node to the diversity tracking
719    pub fn add_node(&mut self, ip_analysis: &IPAnalysis) -> Result<()> {
720        if !self.can_accept_node(ip_analysis) {
721            return Err(anyhow!("IP diversity limits exceeded"));
722        }
723
724        // Update counts (optimized: single hash lookup per cache)
725        let count_64 = self
726            .subnet_64_counts
727            .get(&ip_analysis.subnet_64)
728            .copied()
729            .unwrap_or(0)
730            + 1;
731        self.subnet_64_counts.put(ip_analysis.subnet_64, count_64);
732
733        let count_48 = self
734            .subnet_48_counts
735            .get(&ip_analysis.subnet_48)
736            .copied()
737            .unwrap_or(0)
738            + 1;
739        self.subnet_48_counts.put(ip_analysis.subnet_48, count_48);
740
741        let count_32 = self
742            .subnet_32_counts
743            .get(&ip_analysis.subnet_32)
744            .copied()
745            .unwrap_or(0)
746            + 1;
747        self.subnet_32_counts.put(ip_analysis.subnet_32, count_32);
748
749        if let Some(asn) = ip_analysis.asn {
750            let count = self.asn_counts.get(&asn).copied().unwrap_or(0) + 1;
751            self.asn_counts.put(asn, count);
752        }
753
754        if let Some(ref country) = ip_analysis.country {
755            let count = self.country_counts.get(country).copied().unwrap_or(0) + 1;
756            self.country_counts.put(country.clone(), count);
757        }
758
759        Ok(())
760    }
761
762    /// Remove a node from diversity tracking
763    pub fn remove_node(&mut self, ip_analysis: &IPAnalysis) {
764        // Optimized: pop removes and returns value in single hash operation
765        if let Some(count) = self.subnet_64_counts.pop(&ip_analysis.subnet_64) {
766            let new_count = count.saturating_sub(1);
767            if new_count > 0 {
768                self.subnet_64_counts.put(ip_analysis.subnet_64, new_count);
769            }
770        }
771
772        if let Some(count) = self.subnet_48_counts.pop(&ip_analysis.subnet_48) {
773            let new_count = count.saturating_sub(1);
774            if new_count > 0 {
775                self.subnet_48_counts.put(ip_analysis.subnet_48, new_count);
776            }
777        }
778
779        if let Some(count) = self.subnet_32_counts.pop(&ip_analysis.subnet_32) {
780            let new_count = count.saturating_sub(1);
781            if new_count > 0 {
782                self.subnet_32_counts.put(ip_analysis.subnet_32, new_count);
783            }
784        }
785
786        if let Some(asn) = ip_analysis.asn
787            && let Some(count) = self.asn_counts.pop(&asn)
788        {
789            let new_count = count.saturating_sub(1);
790            if new_count > 0 {
791                self.asn_counts.put(asn, new_count);
792            }
793        }
794
795        if let Some(ref country) = ip_analysis.country
796            && let Some(count) = self.country_counts.pop(country)
797        {
798            let new_count = count.saturating_sub(1);
799            if new_count > 0 {
800                self.country_counts.put(country.clone(), new_count);
801            }
802        }
803    }
804
805    /// Extract network prefix of specified length from IPv6 address
806    pub fn extract_subnet_prefix(addr: Ipv6Addr, prefix_len: u8) -> Ipv6Addr {
807        let octets = addr.octets();
808        let mut subnet = [0u8; 16];
809
810        let bytes_to_copy = (prefix_len / 8) as usize;
811        let remaining_bits = prefix_len % 8;
812
813        // Copy full bytes
814        if bytes_to_copy < 16 {
815            subnet[..bytes_to_copy].copy_from_slice(&octets[..bytes_to_copy]);
816        } else {
817            subnet.copy_from_slice(&octets);
818        }
819
820        // Handle partial byte
821        if remaining_bits > 0 && bytes_to_copy < 16 {
822            let mask = 0xFF << (8 - remaining_bits);
823            subnet[bytes_to_copy] = octets[bytes_to_copy] & mask;
824        }
825
826        Ipv6Addr::from(subnet)
827    }
828
829    /// Get diversity statistics
830    pub fn get_diversity_stats(&self) -> DiversityStats {
831        // LRU cache API: use iter() instead of values()
832        let max_nodes_per_64 = self
833            .subnet_64_counts
834            .iter()
835            .map(|(_, &v)| v)
836            .max()
837            .unwrap_or(0);
838        let max_nodes_per_48 = self
839            .subnet_48_counts
840            .iter()
841            .map(|(_, &v)| v)
842            .max()
843            .unwrap_or(0);
844        let max_nodes_per_32 = self
845            .subnet_32_counts
846            .iter()
847            .map(|(_, &v)| v)
848            .max()
849            .unwrap_or(0);
850        let max_nodes_per_ipv4_32 = self
851            .ipv4_32_counts
852            .iter()
853            .map(|(_, &v)| v)
854            .max()
855            .unwrap_or(0);
856        let max_nodes_per_ipv4_24 = self
857            .ipv4_24_counts
858            .iter()
859            .map(|(_, &v)| v)
860            .max()
861            .unwrap_or(0);
862        let max_nodes_per_ipv4_16 = self
863            .ipv4_16_counts
864            .iter()
865            .map(|(_, &v)| v)
866            .max()
867            .unwrap_or(0);
868
869        DiversityStats {
870            total_64_subnets: self.subnet_64_counts.len(),
871            total_48_subnets: self.subnet_48_counts.len(),
872            total_32_subnets: self.subnet_32_counts.len(),
873            total_asns: self.asn_counts.len(),
874            total_countries: self.country_counts.len(),
875            max_nodes_per_64,
876            max_nodes_per_48,
877            max_nodes_per_32,
878            // IPv4 stats
879            total_ipv4_32: self.ipv4_32_counts.len(),
880            total_ipv4_24_subnets: self.ipv4_24_counts.len(),
881            total_ipv4_16_subnets: self.ipv4_16_counts.len(),
882            max_nodes_per_ipv4_32,
883            max_nodes_per_ipv4_24,
884            max_nodes_per_ipv4_16,
885        }
886    }
887
888    // === IPv4 Methods (new) ===
889
890    /// Set the current network size for dynamic limit calculation
891    pub fn set_network_size(&mut self, size: usize) {
892        self.network_size = size;
893    }
894
895    /// Get the current network size
896    pub fn get_network_size(&self) -> usize {
897        self.network_size
898    }
899
900    /// Calculate the dynamic per-IP limit: min(cap, floor(network_size * fraction))
901    /// Formula: min(50, floor(network_size * 0.005))
902    pub fn get_per_ip_limit(&self) -> usize {
903        let fraction_limit =
904            (self.network_size as f64 * self.config.max_network_fraction).floor() as usize;
905        std::cmp::min(self.config.max_per_ip_cap, std::cmp::max(1, fraction_limit))
906    }
907
908    /// Extract /24 subnet from IPv4 address
909    fn extract_ipv4_subnet_24(addr: Ipv4Addr) -> Ipv4Addr {
910        let octets = addr.octets();
911        Ipv4Addr::new(octets[0], octets[1], octets[2], 0)
912    }
913
914    /// Extract /16 subnet from IPv4 address
915    fn extract_ipv4_subnet_16(addr: Ipv4Addr) -> Ipv4Addr {
916        let octets = addr.octets();
917        Ipv4Addr::new(octets[0], octets[1], 0, 0)
918    }
919
920    /// Extract /8 subnet from IPv4 address
921    fn extract_ipv4_subnet_8(addr: Ipv4Addr) -> Ipv4Addr {
922        let octets = addr.octets();
923        Ipv4Addr::new(octets[0], 0, 0, 0)
924    }
925
926    /// Analyze an IPv4 address for diversity enforcement
927    pub fn analyze_ipv4(&self, ipv4_addr: Ipv4Addr) -> Result<IPv4Analysis> {
928        let subnet_24 = Self::extract_ipv4_subnet_24(ipv4_addr);
929        let subnet_16 = Self::extract_ipv4_subnet_16(ipv4_addr);
930        let subnet_8 = Self::extract_ipv4_subnet_8(ipv4_addr);
931
932        // For IPv4, we don't have GeoIP lookup yet (would need IPv4 support in GeoProvider)
933        // Using defaults for now
934        let asn = None;
935        let country = None;
936        let is_hosting_provider = false;
937        let is_vpn_provider = false;
938        let reputation_score = 0.5;
939
940        Ok(IPv4Analysis {
941            ip_addr: ipv4_addr,
942            subnet_24,
943            subnet_16,
944            subnet_8,
945            asn,
946            country,
947            is_hosting_provider,
948            is_vpn_provider,
949            reputation_score,
950        })
951    }
952
953    /// Analyze any IP address (IPv4 or IPv6) for diversity enforcement
954    pub fn analyze_unified(&self, addr: std::net::IpAddr) -> Result<UnifiedIPAnalysis> {
955        match addr {
956            std::net::IpAddr::V4(ipv4) => {
957                let analysis = self.analyze_ipv4(ipv4)?;
958                Ok(UnifiedIPAnalysis::IPv4(analysis))
959            }
960            std::net::IpAddr::V6(ipv6) => {
961                let analysis = self.analyze_ip(ipv6)?;
962                Ok(UnifiedIPAnalysis::IPv6(analysis))
963            }
964        }
965    }
966
967    /// Check if a node can be accepted based on unified IP diversity constraints
968    pub fn can_accept_unified(&self, analysis: &UnifiedIPAnalysis) -> bool {
969        match analysis {
970            UnifiedIPAnalysis::IPv4(ipv4_analysis) => self.can_accept_ipv4(ipv4_analysis),
971            UnifiedIPAnalysis::IPv6(ipv6_analysis) => self.can_accept_node(ipv6_analysis),
972        }
973    }
974
975    /// Check if an IPv4 node can be accepted based on diversity constraints
976    fn can_accept_ipv4(&self, analysis: &IPv4Analysis) -> bool {
977        // Get dynamic per-IP limit
978        let per_ip_limit = self.get_per_ip_limit();
979
980        // Determine multipliers for subnet limits
981        let limit_32 = per_ip_limit;
982        let limit_24 = std::cmp::min(self.config.max_nodes_per_ipv4_24, per_ip_limit * 3);
983        let limit_16 = std::cmp::min(self.config.max_nodes_per_ipv4_16, per_ip_limit * 10);
984
985        // Apply stricter limits for hosting/VPN providers
986        let (limit_32, limit_24, limit_16) =
987            if analysis.is_hosting_provider || analysis.is_vpn_provider {
988                (
989                    std::cmp::max(1, limit_32 / 2),
990                    std::cmp::max(1, limit_24 / 2),
991                    std::cmp::max(1, limit_16 / 2),
992                )
993            } else {
994                (limit_32, limit_24, limit_16)
995            };
996
997        // Check /32 (exact IP) limit (use peek() for read-only access)
998        if let Some(&count) = self.ipv4_32_counts.peek(&analysis.ip_addr)
999            && count >= limit_32
1000        {
1001            return false;
1002        }
1003
1004        // Check /24 subnet limit (use peek() for read-only access)
1005        if let Some(&count) = self.ipv4_24_counts.peek(&analysis.subnet_24)
1006            && count >= limit_24
1007        {
1008            return false;
1009        }
1010
1011        // Check /16 subnet limit (use peek() for read-only access)
1012        if let Some(&count) = self.ipv4_16_counts.peek(&analysis.subnet_16)
1013            && count >= limit_16
1014        {
1015            return false;
1016        }
1017
1018        // Check ASN limit (shared with IPv6, use peek() for read-only access)
1019        if let Some(asn) = analysis.asn
1020            && let Some(&count) = self.asn_counts.peek(&asn)
1021            && count >= self.config.max_nodes_per_asn
1022        {
1023            return false;
1024        }
1025
1026        true
1027    }
1028
1029    /// Add a unified node to the diversity tracking
1030    pub fn add_unified(&mut self, analysis: &UnifiedIPAnalysis) -> Result<()> {
1031        match analysis {
1032            UnifiedIPAnalysis::IPv4(ipv4_analysis) => self.add_ipv4(ipv4_analysis),
1033            UnifiedIPAnalysis::IPv6(ipv6_analysis) => self.add_node(ipv6_analysis),
1034        }
1035    }
1036
1037    /// Add an IPv4 node to diversity tracking
1038    fn add_ipv4(&mut self, analysis: &IPv4Analysis) -> Result<()> {
1039        if !self.can_accept_ipv4(analysis) {
1040            return Err(anyhow!("IPv4 diversity limits exceeded"));
1041        }
1042
1043        // Update counts (optimized: single hash lookup per cache)
1044        let count_32 = self
1045            .ipv4_32_counts
1046            .get(&analysis.ip_addr)
1047            .copied()
1048            .unwrap_or(0)
1049            + 1;
1050        self.ipv4_32_counts.put(analysis.ip_addr, count_32);
1051
1052        let count_24 = self
1053            .ipv4_24_counts
1054            .get(&analysis.subnet_24)
1055            .copied()
1056            .unwrap_or(0)
1057            + 1;
1058        self.ipv4_24_counts.put(analysis.subnet_24, count_24);
1059
1060        let count_16 = self
1061            .ipv4_16_counts
1062            .get(&analysis.subnet_16)
1063            .copied()
1064            .unwrap_or(0)
1065            + 1;
1066        self.ipv4_16_counts.put(analysis.subnet_16, count_16);
1067
1068        if let Some(asn) = analysis.asn {
1069            let count = self.asn_counts.get(&asn).copied().unwrap_or(0) + 1;
1070            self.asn_counts.put(asn, count);
1071        }
1072
1073        if let Some(ref country) = analysis.country {
1074            let count = self.country_counts.get(country).copied().unwrap_or(0) + 1;
1075            self.country_counts.put(country.clone(), count);
1076        }
1077
1078        Ok(())
1079    }
1080
1081    /// Remove a unified node from diversity tracking
1082    pub fn remove_unified(&mut self, analysis: &UnifiedIPAnalysis) {
1083        match analysis {
1084            UnifiedIPAnalysis::IPv4(ipv4_analysis) => self.remove_ipv4(ipv4_analysis),
1085            UnifiedIPAnalysis::IPv6(ipv6_analysis) => self.remove_node(ipv6_analysis),
1086        }
1087    }
1088
1089    /// Remove an IPv4 node from diversity tracking
1090    fn remove_ipv4(&mut self, analysis: &IPv4Analysis) {
1091        // Optimized: pop removes and returns value in single hash operation
1092        if let Some(count) = self.ipv4_32_counts.pop(&analysis.ip_addr) {
1093            let new_count = count.saturating_sub(1);
1094            if new_count > 0 {
1095                self.ipv4_32_counts.put(analysis.ip_addr, new_count);
1096            }
1097        }
1098
1099        if let Some(count) = self.ipv4_24_counts.pop(&analysis.subnet_24) {
1100            let new_count = count.saturating_sub(1);
1101            if new_count > 0 {
1102                self.ipv4_24_counts.put(analysis.subnet_24, new_count);
1103            }
1104        }
1105
1106        if let Some(count) = self.ipv4_16_counts.pop(&analysis.subnet_16) {
1107            let new_count = count.saturating_sub(1);
1108            if new_count > 0 {
1109                self.ipv4_16_counts.put(analysis.subnet_16, new_count);
1110            }
1111        }
1112
1113        if let Some(asn) = analysis.asn
1114            && let Some(count) = self.asn_counts.pop(&asn)
1115        {
1116            let new_count = count.saturating_sub(1);
1117            if new_count > 0 {
1118                self.asn_counts.put(asn, new_count);
1119            }
1120        }
1121
1122        if let Some(ref country) = analysis.country
1123            && let Some(count) = self.country_counts.pop(country)
1124        {
1125            let new_count = count.saturating_sub(1);
1126            if new_count > 0 {
1127                self.country_counts.put(country.clone(), new_count);
1128            }
1129        }
1130    }
1131}
1132
1133#[cfg(test)]
1134impl IPDiversityEnforcer {
1135    pub fn config(&self) -> &IPDiversityConfig {
1136        &self.config
1137    }
1138}
1139
1140/// GeoIP/ASN provider trait
1141pub trait GeoProvider: std::fmt::Debug {
1142    fn lookup(&self, ip: Ipv6Addr) -> GeoInfo;
1143}
1144
1145/// Geo information
1146#[derive(Debug, Clone)]
1147pub struct GeoInfo {
1148    pub asn: Option<u32>,
1149    pub country: Option<String>,
1150    pub is_hosting_provider: bool,
1151    pub is_vpn_provider: bool,
1152}
1153
1154/// A simple in-memory caching wrapper for a GeoProvider
1155#[derive(Debug)]
1156pub struct CachedGeoProvider<P: GeoProvider> {
1157    inner: P,
1158    cache: parking_lot::RwLock<HashMap<Ipv6Addr, GeoInfo>>,
1159}
1160
1161impl<P: GeoProvider> CachedGeoProvider<P> {
1162    pub fn new(inner: P) -> Self {
1163        Self {
1164            inner,
1165            cache: parking_lot::RwLock::new(HashMap::new()),
1166        }
1167    }
1168}
1169
1170impl<P: GeoProvider> GeoProvider for CachedGeoProvider<P> {
1171    fn lookup(&self, ip: Ipv6Addr) -> GeoInfo {
1172        if let Some(info) = self.cache.read().get(&ip).cloned() {
1173            return info;
1174        }
1175        let info = self.inner.lookup(ip);
1176        self.cache.write().insert(ip, info.clone());
1177        info
1178    }
1179}
1180
1181/// Stub provider returning no ASN/GeoIP info
1182#[derive(Debug)]
1183pub struct StubGeoProvider;
1184impl GeoProvider for StubGeoProvider {
1185    fn lookup(&self, _ip: Ipv6Addr) -> GeoInfo {
1186        GeoInfo {
1187            asn: None,
1188            country: None,
1189            is_hosting_provider: false,
1190            is_vpn_provider: false,
1191        }
1192    }
1193}
1194
1195/// Diversity statistics for monitoring
1196#[derive(Debug, Clone, Serialize, Deserialize)]
1197pub struct DiversityStats {
1198    // === IPv6 stats ===
1199    /// Number of unique /64 subnets represented
1200    pub total_64_subnets: usize,
1201    /// Number of unique /48 subnets represented
1202    pub total_48_subnets: usize,
1203    /// Number of unique /32 subnets represented
1204    pub total_32_subnets: usize,
1205    /// Maximum nodes in any single /64 subnet
1206    pub max_nodes_per_64: usize,
1207    /// Maximum nodes in any single /48 subnet
1208    pub max_nodes_per_48: usize,
1209    /// Maximum nodes in any single /32 subnet
1210    pub max_nodes_per_32: usize,
1211
1212    // === IPv4 stats (new) ===
1213    /// Number of unique IPv4 addresses (/32)
1214    pub total_ipv4_32: usize,
1215    /// Number of unique /24 subnets represented
1216    pub total_ipv4_24_subnets: usize,
1217    /// Number of unique /16 subnets represented
1218    pub total_ipv4_16_subnets: usize,
1219    /// Maximum nodes from any single IPv4 address
1220    pub max_nodes_per_ipv4_32: usize,
1221    /// Maximum nodes in any single /24 subnet
1222    pub max_nodes_per_ipv4_24: usize,
1223    /// Maximum nodes in any single /16 subnet
1224    pub max_nodes_per_ipv4_16: usize,
1225
1226    // === Shared stats ===
1227    /// Number of unique ASNs represented
1228    pub total_asns: usize,
1229    /// Number of unique countries represented
1230    pub total_countries: usize,
1231}
1232
1233/// Reputation manager for tracking node behavior
1234#[derive(Debug)]
1235pub struct ReputationManager {
1236    reputations: HashMap<PeerId, NodeReputation>,
1237    reputation_decay: f64,
1238    min_reputation: f64,
1239}
1240
1241impl ReputationManager {
1242    /// Create a new reputation manager
1243    pub fn new(reputation_decay: f64, min_reputation: f64) -> Self {
1244        Self {
1245            reputations: HashMap::new(),
1246            reputation_decay,
1247            min_reputation,
1248        }
1249    }
1250
1251    /// Get reputation for a peer
1252    pub fn get_reputation(&self, peer_id: &PeerId) -> Option<&NodeReputation> {
1253        self.reputations.get(peer_id)
1254    }
1255
1256    /// Update reputation based on interaction
1257    pub fn update_reputation(&mut self, peer_id: &PeerId, success: bool, response_time: Duration) {
1258        let reputation =
1259            self.reputations
1260                .entry(peer_id.clone())
1261                .or_insert_with(|| NodeReputation {
1262                    peer_id: peer_id.clone(),
1263                    response_rate: 0.5,
1264                    response_time: Duration::from_millis(500),
1265                    consistency_score: 0.5,
1266                    uptime_estimate: Duration::from_secs(0),
1267                    routing_accuracy: 0.5,
1268                    last_seen: SystemTime::now(),
1269                    interaction_count: 0,
1270                });
1271
1272        // Use higher learning rate for faster convergence in tests
1273        let alpha = 0.3; // Increased from 0.1 for better test convergence
1274
1275        if success {
1276            reputation.response_rate = reputation.response_rate * (1.0 - alpha) + alpha;
1277        } else {
1278            reputation.response_rate *= 1.0 - alpha;
1279        }
1280
1281        // Update response time
1282        let response_time_ms = response_time.as_millis() as f64;
1283        let current_response_ms = reputation.response_time.as_millis() as f64;
1284        let new_response_ms = current_response_ms * (1.0 - alpha) + response_time_ms * alpha;
1285        reputation.response_time = Duration::from_millis(new_response_ms as u64);
1286
1287        reputation.last_seen = SystemTime::now();
1288        reputation.interaction_count += 1;
1289    }
1290
1291    /// Apply time-based reputation decay
1292    pub fn apply_decay(&mut self) {
1293        let now = SystemTime::now();
1294
1295        self.reputations.retain(|_, reputation| {
1296            if let Ok(elapsed) = now.duration_since(reputation.last_seen) {
1297                // Decay reputation over time
1298                let decay_factor = (-elapsed.as_secs_f64() / 3600.0 * self.reputation_decay).exp();
1299                reputation.response_rate *= decay_factor;
1300                reputation.consistency_score *= decay_factor;
1301                reputation.routing_accuracy *= decay_factor;
1302
1303                // Remove nodes with very low reputation
1304                reputation.response_rate > self.min_reputation / 10.0
1305            } else {
1306                true
1307            }
1308        });
1309    }
1310}
1311
1312// Ed25519 compatibility removed
1313
1314#[cfg(test)]
1315mod tests {
1316    use super::*;
1317    use crate::quantum_crypto::generate_ml_dsa_keypair;
1318
1319    fn create_test_keypair() -> (MlDsaPublicKey, MlDsaSecretKey) {
1320        generate_ml_dsa_keypair().expect("Failed to generate test keypair")
1321    }
1322
1323    fn create_test_ipv6() -> Ipv6Addr {
1324        Ipv6Addr::new(
1325            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7334,
1326        )
1327    }
1328
1329    fn create_test_diversity_config() -> IPDiversityConfig {
1330        IPDiversityConfig {
1331            // IPv6 limits
1332            max_nodes_per_64: 1,
1333            max_nodes_per_48: 3,
1334            max_nodes_per_32: 10,
1335            // IPv4 limits
1336            max_nodes_per_ipv4_32: 1,
1337            max_nodes_per_ipv4_24: 3,
1338            max_nodes_per_ipv4_16: 10,
1339            // Network-relative limits
1340            max_per_ip_cap: 50,
1341            max_network_fraction: 0.005,
1342            // ASN and GeoIP
1343            max_nodes_per_asn: 20,
1344            enable_geolocation_check: true,
1345            min_geographic_diversity: 3,
1346        }
1347    }
1348
1349    #[test]
1350    fn test_ipv6_node_id_generation() -> Result<()> {
1351        let (public_key, secret_key) = create_test_keypair();
1352        let ipv6_addr = create_test_ipv6();
1353
1354        let node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
1355
1356        assert_eq!(node_id.ipv6_addr, ipv6_addr);
1357        assert_eq!(node_id.public_key.len(), 1952); // ML-DSA-65 public key size
1358        assert_eq!(node_id.signature.len(), 3309); // ML-DSA-65 signature size
1359        assert_eq!(node_id.node_id.len(), 32); // SHA256 output
1360        assert_eq!(node_id.salt.len(), 16);
1361        assert!(node_id.timestamp_secs > 0);
1362
1363        Ok(())
1364    }
1365
1366    #[test]
1367    fn test_ipv6_node_id_verification() -> Result<()> {
1368        let (public_key, secret_key) = create_test_keypair();
1369        let ipv6_addr = create_test_ipv6();
1370
1371        let node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
1372        let is_valid = node_id.verify()?;
1373
1374        assert!(is_valid);
1375
1376        Ok(())
1377    }
1378
1379    #[test]
1380    fn test_ipv6_node_id_verification_fails_with_wrong_data() -> Result<()> {
1381        let (public_key, secret_key) = create_test_keypair();
1382        let ipv6_addr = create_test_ipv6();
1383
1384        let mut node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
1385
1386        // Tamper with the node ID
1387        node_id.node_id[0] ^= 0xFF;
1388        let is_valid = node_id.verify()?;
1389        assert!(!is_valid);
1390
1391        // Test with wrong signature length
1392        let mut node_id2 = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
1393        node_id2.signature = vec![0u8; 32]; // Wrong length (should be 3309 for ML-DSA)
1394        let is_valid2 = node_id2.verify()?;
1395        assert!(!is_valid2);
1396
1397        // Test with wrong public key length
1398        let mut node_id3 = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
1399        node_id3.public_key = vec![0u8; 16]; // Wrong length (should be 1952 for ML-DSA-65)
1400        let is_valid3 = node_id3.verify()?;
1401        assert!(!is_valid3);
1402
1403        Ok(())
1404    }
1405
1406    #[test]
1407    fn test_ipv6_subnet_extraction() -> Result<()> {
1408        let (public_key, secret_key) = create_test_keypair();
1409        let ipv6_addr = Ipv6Addr::new(
1410            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
1411        );
1412
1413        let node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
1414
1415        // Test /64 subnet extraction
1416        let subnet_64 = node_id.extract_subnet_64();
1417        let expected_64 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0);
1418        assert_eq!(subnet_64, expected_64);
1419
1420        // Test /48 subnet extraction
1421        let subnet_48 = node_id.extract_subnet_48();
1422        let expected_48 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0);
1423        assert_eq!(subnet_48, expected_48);
1424
1425        // Test /32 subnet extraction
1426        let subnet_32 = node_id.extract_subnet_32();
1427        let expected_32 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
1428        assert_eq!(subnet_32, expected_32);
1429
1430        Ok(())
1431    }
1432
1433    // =========== IPv4 Node ID Tests ===========
1434
1435    fn create_test_ipv4() -> Ipv4Addr {
1436        Ipv4Addr::new(192, 168, 1, 100)
1437    }
1438
1439    #[test]
1440    fn test_ipv4_node_id_generation() -> Result<()> {
1441        let (public_key, secret_key) = create_test_keypair();
1442        let ipv4_addr = create_test_ipv4();
1443
1444        let node_id = IPv4NodeID::generate(ipv4_addr, &secret_key, &public_key)?;
1445
1446        assert_eq!(node_id.ipv4_addr, ipv4_addr);
1447        assert_eq!(node_id.public_key.len(), 1952); // ML-DSA-65 public key size
1448        assert_eq!(node_id.signature.len(), 3309); // ML-DSA-65 signature size
1449        assert_eq!(node_id.node_id.len(), 32); // SHA256 output
1450        assert_eq!(node_id.salt.len(), 16);
1451        assert!(node_id.timestamp_secs > 0);
1452
1453        Ok(())
1454    }
1455
1456    #[test]
1457    fn test_ipv4_node_id_verification() -> Result<()> {
1458        let (public_key, secret_key) = create_test_keypair();
1459        let ipv4_addr = create_test_ipv4();
1460
1461        let node_id = IPv4NodeID::generate(ipv4_addr, &secret_key, &public_key)?;
1462        let is_valid = node_id.verify()?;
1463
1464        assert!(is_valid);
1465
1466        Ok(())
1467    }
1468
1469    #[test]
1470    fn test_ipv4_node_id_verification_fails_with_wrong_data() -> Result<()> {
1471        let (public_key, secret_key) = create_test_keypair();
1472        let ipv4_addr = create_test_ipv4();
1473
1474        let mut node_id = IPv4NodeID::generate(ipv4_addr, &secret_key, &public_key)?;
1475
1476        // Tamper with the node ID
1477        node_id.node_id[0] ^= 0xFF;
1478        let is_valid = node_id.verify()?;
1479        assert!(!is_valid);
1480
1481        // Test with wrong signature length
1482        let mut node_id2 = IPv4NodeID::generate(ipv4_addr, &secret_key, &public_key)?;
1483        node_id2.signature = vec![0u8; 32]; // Wrong length (should be 3309 for ML-DSA)
1484        let is_valid2 = node_id2.verify()?;
1485        assert!(!is_valid2);
1486
1487        // Test with wrong public key length
1488        let mut node_id3 = IPv4NodeID::generate(ipv4_addr, &secret_key, &public_key)?;
1489        node_id3.public_key = vec![0u8; 16]; // Wrong length (should be 1952 for ML-DSA-65)
1490        let is_valid3 = node_id3.verify()?;
1491        assert!(!is_valid3);
1492
1493        Ok(())
1494    }
1495
1496    #[test]
1497    fn test_ipv4_subnet_extraction() -> Result<()> {
1498        let (public_key, secret_key) = create_test_keypair();
1499        let ipv4_addr = Ipv4Addr::new(192, 168, 42, 100);
1500
1501        let node_id = IPv4NodeID::generate(ipv4_addr, &secret_key, &public_key)?;
1502
1503        // Test /24 subnet extraction
1504        let subnet_24 = node_id.extract_subnet_24();
1505        let expected_24 = Ipv4Addr::new(192, 168, 42, 0);
1506        assert_eq!(subnet_24, expected_24);
1507
1508        // Test /16 subnet extraction
1509        let subnet_16 = node_id.extract_subnet_16();
1510        let expected_16 = Ipv4Addr::new(192, 168, 0, 0);
1511        assert_eq!(subnet_16, expected_16);
1512
1513        // Test /8 subnet extraction
1514        let subnet_8 = node_id.extract_subnet_8();
1515        let expected_8 = Ipv4Addr::new(192, 0, 0, 0);
1516        assert_eq!(subnet_8, expected_8);
1517
1518        Ok(())
1519    }
1520
1521    #[test]
1522    fn test_ipv4_node_id_age() -> Result<()> {
1523        let (public_key, secret_key) = create_test_keypair();
1524        let ipv4_addr = create_test_ipv4();
1525
1526        let node_id = IPv4NodeID::generate(ipv4_addr, &secret_key, &public_key)?;
1527
1528        // Age should be very small (just created)
1529        assert!(node_id.age_secs() < 5);
1530
1531        // Not expired with a 1 hour max age
1532        assert!(!node_id.is_expired(Duration::from_secs(3600)));
1533
1534        // A freshly created node with 0 age is NOT expired (0 > 0 is false)
1535        // This is correct behavior - a 0-second-old node is not older than 0 seconds
1536        assert!(!node_id.is_expired(Duration::from_secs(0)));
1537
1538        Ok(())
1539    }
1540
1541    #[test]
1542    fn test_ipv4_different_addresses_different_node_ids() -> Result<()> {
1543        let (public_key, secret_key) = create_test_keypair();
1544        let addr1 = Ipv4Addr::new(192, 168, 1, 1);
1545        let addr2 = Ipv4Addr::new(192, 168, 1, 2);
1546
1547        let node_id1 = IPv4NodeID::generate(addr1, &secret_key, &public_key)?;
1548        let node_id2 = IPv4NodeID::generate(addr2, &secret_key, &public_key)?;
1549
1550        // Different addresses should produce different node IDs
1551        assert_ne!(node_id1.node_id, node_id2.node_id);
1552
1553        // Both should verify successfully
1554        assert!(node_id1.verify()?);
1555        assert!(node_id2.verify()?);
1556
1557        Ok(())
1558    }
1559
1560    // =========== End IPv4 Tests ===========
1561
1562    #[test]
1563    fn test_ip_diversity_config_default() {
1564        let config = IPDiversityConfig::default();
1565
1566        assert_eq!(config.max_nodes_per_64, 1);
1567        assert_eq!(config.max_nodes_per_48, 3);
1568        assert_eq!(config.max_nodes_per_32, 10);
1569        assert_eq!(config.max_nodes_per_asn, 20);
1570        assert!(config.enable_geolocation_check);
1571        assert_eq!(config.min_geographic_diversity, 3);
1572    }
1573
1574    #[test]
1575    fn test_ip_diversity_enforcer_creation() {
1576        let config = create_test_diversity_config();
1577        let enforcer = IPDiversityEnforcer::new(config.clone());
1578
1579        assert_eq!(enforcer.config.max_nodes_per_64, config.max_nodes_per_64);
1580        assert_eq!(enforcer.subnet_64_counts.len(), 0);
1581        assert_eq!(enforcer.subnet_48_counts.len(), 0);
1582        assert_eq!(enforcer.subnet_32_counts.len(), 0);
1583    }
1584
1585    #[test]
1586    fn test_ip_analysis() -> Result<()> {
1587        let config = create_test_diversity_config();
1588        let enforcer = IPDiversityEnforcer::new(config);
1589
1590        let ipv6_addr = create_test_ipv6();
1591        let analysis = enforcer.analyze_ip(ipv6_addr)?;
1592
1593        assert_eq!(
1594            analysis.subnet_64,
1595            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 64)
1596        );
1597        assert_eq!(
1598            analysis.subnet_48,
1599            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 48)
1600        );
1601        assert_eq!(
1602            analysis.subnet_32,
1603            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 32)
1604        );
1605        assert!(analysis.asn.is_none()); // Not implemented in test
1606        assert!(analysis.country.is_none()); // Not implemented in test
1607        assert!(!analysis.is_hosting_provider);
1608        assert!(!analysis.is_vpn_provider);
1609        assert_eq!(analysis.reputation_score, 0.5);
1610
1611        Ok(())
1612    }
1613
1614    #[test]
1615    fn test_can_accept_node_basic() -> Result<()> {
1616        let config = create_test_diversity_config();
1617        let enforcer = IPDiversityEnforcer::new(config);
1618
1619        let ipv6_addr = create_test_ipv6();
1620        let analysis = enforcer.analyze_ip(ipv6_addr)?;
1621
1622        // Should accept first node
1623        assert!(enforcer.can_accept_node(&analysis));
1624
1625        Ok(())
1626    }
1627
1628    #[test]
1629    fn test_add_and_remove_node() -> Result<()> {
1630        let config = create_test_diversity_config();
1631        let mut enforcer = IPDiversityEnforcer::new(config);
1632
1633        let ipv6_addr = create_test_ipv6();
1634        let analysis = enforcer.analyze_ip(ipv6_addr)?;
1635
1636        // Add node
1637        enforcer.add_node(&analysis)?;
1638        assert_eq!(enforcer.subnet_64_counts.get(&analysis.subnet_64), Some(&1));
1639        assert_eq!(enforcer.subnet_48_counts.get(&analysis.subnet_48), Some(&1));
1640        assert_eq!(enforcer.subnet_32_counts.get(&analysis.subnet_32), Some(&1));
1641
1642        // Remove node
1643        enforcer.remove_node(&analysis);
1644        assert_eq!(enforcer.subnet_64_counts.get(&analysis.subnet_64), None);
1645        assert_eq!(enforcer.subnet_48_counts.get(&analysis.subnet_48), None);
1646        assert_eq!(enforcer.subnet_32_counts.get(&analysis.subnet_32), None);
1647
1648        Ok(())
1649    }
1650
1651    #[test]
1652    fn test_diversity_limits_enforcement() -> Result<()> {
1653        let config = create_test_diversity_config();
1654        let mut enforcer = IPDiversityEnforcer::new(config);
1655
1656        let ipv6_addr1 = Ipv6Addr::new(
1657            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
1658        );
1659        let ipv6_addr2 = Ipv6Addr::new(
1660            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7335,
1661        ); // Same /64
1662
1663        let analysis1 = enforcer.analyze_ip(ipv6_addr1)?;
1664        let analysis2 = enforcer.analyze_ip(ipv6_addr2)?;
1665
1666        // First node should be accepted
1667        assert!(enforcer.can_accept_node(&analysis1));
1668        enforcer.add_node(&analysis1)?;
1669
1670        // Second node in same /64 should be rejected (max_nodes_per_64 = 1)
1671        assert!(!enforcer.can_accept_node(&analysis2));
1672
1673        // But adding should fail
1674        let result = enforcer.add_node(&analysis2);
1675        assert!(result.is_err());
1676        assert!(
1677            result
1678                .unwrap_err()
1679                .to_string()
1680                .contains("IP diversity limits exceeded")
1681        );
1682
1683        Ok(())
1684    }
1685
1686    #[test]
1687    fn test_hosting_provider_stricter_limits() -> Result<()> {
1688        let config = IPDiversityConfig {
1689            max_nodes_per_64: 4, // Set higher limit for regular nodes
1690            max_nodes_per_48: 8,
1691            ..create_test_diversity_config()
1692        };
1693        let mut enforcer = IPDiversityEnforcer::new(config);
1694
1695        let ipv6_addr = create_test_ipv6();
1696        let mut analysis = enforcer.analyze_ip(ipv6_addr)?;
1697        analysis.is_hosting_provider = true;
1698
1699        // Should accept first hosting provider node
1700        assert!(enforcer.can_accept_node(&analysis));
1701        enforcer.add_node(&analysis)?;
1702
1703        // Add second hosting provider node in same /64 (should be accepted with limit=2)
1704        let ipv6_addr2 = Ipv6Addr::new(
1705            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7335,
1706        );
1707        let mut analysis2 = enforcer.analyze_ip(ipv6_addr2)?;
1708        analysis2.is_hosting_provider = true;
1709        analysis2.subnet_64 = analysis.subnet_64; // Force same subnet
1710
1711        assert!(enforcer.can_accept_node(&analysis2));
1712        enforcer.add_node(&analysis2)?;
1713
1714        // Should reject third hosting provider node in same /64 (exceeds limit=2)
1715        let ipv6_addr3 = Ipv6Addr::new(
1716            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7336,
1717        );
1718        let mut analysis3 = enforcer.analyze_ip(ipv6_addr3)?;
1719        analysis3.is_hosting_provider = true;
1720        analysis3.subnet_64 = analysis.subnet_64; // Force same subnet
1721
1722        assert!(!enforcer.can_accept_node(&analysis3));
1723
1724        Ok(())
1725    }
1726
1727    #[test]
1728    fn test_diversity_stats() -> Result<()> {
1729        let config = create_test_diversity_config();
1730        let mut enforcer = IPDiversityEnforcer::new(config);
1731
1732        // Add some nodes with different subnets
1733        let addresses = [
1734            Ipv6Addr::new(
1735                0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
1736            ),
1737            Ipv6Addr::new(
1738                0x2001, 0xdb8, 0x85a4, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
1739            ), // Different /48
1740            Ipv6Addr::new(
1741                0x2002, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
1742            ), // Different /32
1743        ];
1744
1745        for addr in addresses {
1746            let analysis = enforcer.analyze_ip(addr)?;
1747            enforcer.add_node(&analysis)?;
1748        }
1749
1750        let stats = enforcer.get_diversity_stats();
1751        assert_eq!(stats.total_64_subnets, 3);
1752        assert_eq!(stats.total_48_subnets, 3);
1753        assert_eq!(stats.total_32_subnets, 2); // Two /32 prefixes
1754        assert_eq!(stats.max_nodes_per_64, 1);
1755        assert_eq!(stats.max_nodes_per_48, 1);
1756        assert_eq!(stats.max_nodes_per_32, 2); // 2001:db8 has 2 nodes
1757
1758        Ok(())
1759    }
1760
1761    #[test]
1762    fn test_extract_subnet_prefix() {
1763        let addr = Ipv6Addr::new(
1764            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
1765        );
1766
1767        // Test /64 prefix
1768        let prefix_64 = IPDiversityEnforcer::extract_subnet_prefix(addr, 64);
1769        let expected_64 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0);
1770        assert_eq!(prefix_64, expected_64);
1771
1772        // Test /48 prefix
1773        let prefix_48 = IPDiversityEnforcer::extract_subnet_prefix(addr, 48);
1774        let expected_48 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0);
1775        assert_eq!(prefix_48, expected_48);
1776
1777        // Test /32 prefix
1778        let prefix_32 = IPDiversityEnforcer::extract_subnet_prefix(addr, 32);
1779        let expected_32 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
1780        assert_eq!(prefix_32, expected_32);
1781
1782        // Test /56 prefix (partial byte)
1783        let prefix_56 = IPDiversityEnforcer::extract_subnet_prefix(addr, 56);
1784        let expected_56 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1200, 0, 0, 0, 0);
1785        assert_eq!(prefix_56, expected_56);
1786
1787        // Test /128 prefix (full address)
1788        let prefix_128 = IPDiversityEnforcer::extract_subnet_prefix(addr, 128);
1789        assert_eq!(prefix_128, addr);
1790    }
1791
1792    #[test]
1793    fn test_reputation_manager_creation() {
1794        let manager = ReputationManager::new(0.1, 0.1);
1795        assert_eq!(manager.reputation_decay, 0.1);
1796        assert_eq!(manager.min_reputation, 0.1);
1797        assert_eq!(manager.reputations.len(), 0);
1798    }
1799
1800    #[test]
1801    fn test_reputation_get_nonexistent() {
1802        let manager = ReputationManager::new(0.1, 0.1);
1803        let peer_id = "test_peer".to_string();
1804
1805        let reputation = manager.get_reputation(&peer_id);
1806        assert!(reputation.is_none());
1807    }
1808
1809    #[test]
1810    fn test_reputation_update_creates_entry() {
1811        let mut manager = ReputationManager::new(0.1, 0.1);
1812        let peer_id = "test_peer".to_string();
1813
1814        manager.update_reputation(&peer_id, true, Duration::from_millis(100));
1815
1816        let reputation = manager.get_reputation(&peer_id);
1817        assert!(reputation.is_some());
1818
1819        let rep = reputation.unwrap();
1820        assert_eq!(rep.peer_id, peer_id);
1821        assert!(rep.response_rate > 0.5); // Should increase from initial 0.5
1822        assert_eq!(rep.interaction_count, 1);
1823    }
1824
1825    #[test]
1826    fn test_reputation_update_success_improves_rate() {
1827        let mut manager = ReputationManager::new(0.1, 0.1);
1828        let peer_id = "test_peer".to_string();
1829
1830        // Multiple successful interactions
1831        for _ in 0..15 {
1832            manager.update_reputation(&peer_id, true, Duration::from_millis(100));
1833        }
1834
1835        let reputation = manager.get_reputation(&peer_id).unwrap();
1836        assert!(reputation.response_rate > 0.85); // Should be very high with higher learning rate
1837        assert_eq!(reputation.interaction_count, 15);
1838    }
1839
1840    #[test]
1841    fn test_reputation_update_failure_decreases_rate() {
1842        let mut manager = ReputationManager::new(0.1, 0.1);
1843        let peer_id = "test_peer".to_string();
1844
1845        // Multiple failed interactions
1846        for _ in 0..15 {
1847            manager.update_reputation(&peer_id, false, Duration::from_millis(1000));
1848        }
1849
1850        let reputation = manager.get_reputation(&peer_id).unwrap();
1851        assert!(reputation.response_rate < 0.15); // Should be very low with higher learning rate
1852        assert_eq!(reputation.interaction_count, 15);
1853    }
1854
1855    #[test]
1856    fn test_reputation_response_time_tracking() {
1857        let mut manager = ReputationManager::new(0.1, 0.1);
1858        let peer_id = "test_peer".to_string();
1859
1860        // Update with specific response time
1861        manager.update_reputation(&peer_id, true, Duration::from_millis(200));
1862
1863        let reputation = manager.get_reputation(&peer_id).unwrap();
1864        // Response time should be between initial 500ms and new 200ms
1865        assert!(reputation.response_time.as_millis() > 200);
1866        assert!(reputation.response_time.as_millis() < 500);
1867    }
1868
1869    #[test]
1870    fn test_reputation_decay() {
1871        let mut manager = ReputationManager::new(1.0, 0.01); // High decay rate
1872        let peer_id = "test_peer".to_string();
1873
1874        // Create a reputation entry
1875        manager.update_reputation(&peer_id, true, Duration::from_millis(100));
1876
1877        // Manually set last_seen to past
1878        if let Some(reputation) = manager.reputations.get_mut(&peer_id) {
1879            reputation.last_seen = SystemTime::now() - Duration::from_secs(7200); // 2 hours ago
1880        }
1881
1882        let original_rate = manager.get_reputation(&peer_id).unwrap().response_rate;
1883
1884        // Apply decay
1885        manager.apply_decay();
1886
1887        let reputation = manager.get_reputation(&peer_id);
1888        if let Some(rep) = reputation {
1889            // Should have decayed
1890            assert!(rep.response_rate < original_rate);
1891        } // else the reputation was removed due to low score
1892    }
1893
1894    #[test]
1895    fn test_reputation_decay_removes_low_reputation() {
1896        let mut manager = ReputationManager::new(0.1, 0.5); // High min reputation
1897        let peer_id = "test_peer".to_string();
1898
1899        // Create a low reputation entry
1900        for _ in 0..10 {
1901            manager.update_reputation(&peer_id, false, Duration::from_millis(1000));
1902        }
1903
1904        // Manually set last_seen to past
1905        if let Some(reputation) = manager.reputations.get_mut(&peer_id) {
1906            reputation.last_seen = SystemTime::now() - Duration::from_secs(3600); // 1 hour ago
1907            reputation.response_rate = 0.01; // Very low
1908        }
1909
1910        // Apply decay
1911        manager.apply_decay();
1912
1913        // Should be removed
1914        assert!(manager.get_reputation(&peer_id).is_none());
1915    }
1916
1917    #[test]
1918    fn test_security_types_keypair() {
1919        let (public_key, secret_key) =
1920            generate_ml_dsa_keypair().expect("Failed to generate keypair");
1921
1922        let public_key_bytes = public_key.as_bytes();
1923        assert_eq!(public_key_bytes.len(), 1952); // ML-DSA-65 public key size
1924
1925        let message = b"test message";
1926        let signature = ml_dsa_sign(&secret_key, message).expect("Failed to sign message");
1927        assert_eq!(signature.as_bytes().len(), 3309); // ML-DSA-65 signature size
1928
1929        // Verify the signature
1930        assert!(ml_dsa_verify(&public_key, message, &signature).is_ok());
1931    }
1932
1933    #[test]
1934    fn test_node_reputation_structure() {
1935        let peer_id = "test_peer".to_string();
1936        let reputation = NodeReputation {
1937            peer_id: peer_id.clone(),
1938            response_rate: 0.85,
1939            response_time: Duration::from_millis(150),
1940            consistency_score: 0.9,
1941            uptime_estimate: Duration::from_secs(86400),
1942            routing_accuracy: 0.8,
1943            last_seen: SystemTime::now(),
1944            interaction_count: 42,
1945        };
1946
1947        assert_eq!(reputation.peer_id, peer_id);
1948        assert_eq!(reputation.response_rate, 0.85);
1949        assert_eq!(reputation.response_time, Duration::from_millis(150));
1950        assert_eq!(reputation.consistency_score, 0.9);
1951        assert_eq!(reputation.uptime_estimate, Duration::from_secs(86400));
1952        assert_eq!(reputation.routing_accuracy, 0.8);
1953        assert_eq!(reputation.interaction_count, 42);
1954    }
1955
1956    #[test]
1957    fn test_ip_analysis_structure() {
1958        let analysis = IPAnalysis {
1959            subnet_64: Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0),
1960            subnet_48: Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0),
1961            subnet_32: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
1962            asn: Some(64512),
1963            country: Some("US".to_string()),
1964            is_hosting_provider: true,
1965            is_vpn_provider: false,
1966            reputation_score: 0.75,
1967        };
1968
1969        assert_eq!(analysis.asn, Some(64512));
1970        assert_eq!(analysis.country, Some("US".to_string()));
1971        assert!(analysis.is_hosting_provider);
1972        assert!(!analysis.is_vpn_provider);
1973        assert_eq!(analysis.reputation_score, 0.75);
1974    }
1975
1976    #[test]
1977    fn test_diversity_stats_structure() {
1978        let stats = DiversityStats {
1979            // IPv6 stats
1980            total_64_subnets: 100,
1981            total_48_subnets: 50,
1982            total_32_subnets: 25,
1983            max_nodes_per_64: 1,
1984            max_nodes_per_48: 3,
1985            max_nodes_per_32: 10,
1986            // IPv4 stats
1987            total_ipv4_32: 80,
1988            total_ipv4_24_subnets: 40,
1989            total_ipv4_16_subnets: 20,
1990            max_nodes_per_ipv4_32: 1,
1991            max_nodes_per_ipv4_24: 3,
1992            max_nodes_per_ipv4_16: 10,
1993            // Shared stats
1994            total_asns: 15,
1995            total_countries: 8,
1996        };
1997
1998        // IPv6 assertions
1999        assert_eq!(stats.total_64_subnets, 100);
2000        assert_eq!(stats.total_48_subnets, 50);
2001        assert_eq!(stats.total_32_subnets, 25);
2002        assert_eq!(stats.max_nodes_per_64, 1);
2003        assert_eq!(stats.max_nodes_per_48, 3);
2004        assert_eq!(stats.max_nodes_per_32, 10);
2005        // IPv4 assertions
2006        assert_eq!(stats.total_ipv4_32, 80);
2007        assert_eq!(stats.total_ipv4_24_subnets, 40);
2008        assert_eq!(stats.total_ipv4_16_subnets, 20);
2009        assert_eq!(stats.max_nodes_per_ipv4_32, 1);
2010        assert_eq!(stats.max_nodes_per_ipv4_24, 3);
2011        assert_eq!(stats.max_nodes_per_ipv4_16, 10);
2012        // Shared assertions
2013        assert_eq!(stats.total_asns, 15);
2014        assert_eq!(stats.total_countries, 8);
2015    }
2016
2017    #[test]
2018    fn test_multiple_same_subnet_nodes() -> Result<()> {
2019        let config = IPDiversityConfig {
2020            max_nodes_per_64: 3, // Allow more nodes in same /64
2021            max_nodes_per_48: 5,
2022            max_nodes_per_32: 10,
2023            ..create_test_diversity_config()
2024        };
2025        let mut enforcer = IPDiversityEnforcer::new(config);
2026
2027        let _base_addr = Ipv6Addr::new(
2028            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x0000,
2029        );
2030
2031        // Add 3 nodes in same /64 subnet
2032        for i in 1..=3 {
2033            let addr = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, i);
2034            let analysis = enforcer.analyze_ip(addr)?;
2035            assert!(enforcer.can_accept_node(&analysis));
2036            enforcer.add_node(&analysis)?;
2037        }
2038
2039        // 4th node should be rejected
2040        let addr4 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 4);
2041        let analysis4 = enforcer.analyze_ip(addr4)?;
2042        assert!(!enforcer.can_accept_node(&analysis4));
2043
2044        let stats = enforcer.get_diversity_stats();
2045        assert_eq!(stats.total_64_subnets, 1);
2046        assert_eq!(stats.max_nodes_per_64, 3);
2047
2048        Ok(())
2049    }
2050
2051    #[test]
2052    fn test_asn_and_country_tracking() -> Result<()> {
2053        let config = create_test_diversity_config();
2054        let mut enforcer = IPDiversityEnforcer::new(config);
2055
2056        // Create analysis with ASN and country
2057        let ipv6_addr = create_test_ipv6();
2058        let mut analysis = enforcer.analyze_ip(ipv6_addr)?;
2059        analysis.asn = Some(64512);
2060        analysis.country = Some("US".to_string());
2061
2062        enforcer.add_node(&analysis)?;
2063
2064        assert_eq!(enforcer.asn_counts.get(&64512), Some(&1));
2065        assert_eq!(enforcer.country_counts.get("US"), Some(&1));
2066
2067        // Remove and check cleanup
2068        enforcer.remove_node(&analysis);
2069        assert!(!enforcer.asn_counts.contains(&64512));
2070        assert!(!enforcer.country_counts.contains("US"));
2071
2072        Ok(())
2073    }
2074
2075    #[test]
2076    fn test_reputation_mixed_interactions() {
2077        let mut manager = ReputationManager::new(0.1, 0.1);
2078        let peer_id = "test_peer".to_string();
2079
2080        // Mix of successful and failed interactions
2081        for i in 0..15 {
2082            let success = i % 3 != 0; // 2/3 success rate
2083            manager.update_reputation(&peer_id, success, Duration::from_millis(100 + i * 10));
2084        }
2085
2086        let reputation = manager.get_reputation(&peer_id).unwrap();
2087        // Should converge closer to 2/3 with more iterations and higher learning rate
2088        // With alpha=0.3 and 2/3 success rate, convergence may be higher
2089        assert!(reputation.response_rate > 0.55);
2090        assert!(reputation.response_rate < 0.85);
2091        assert_eq!(reputation.interaction_count, 15);
2092    }
2093}