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