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