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: saorsalabs@gmail.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 anyhow::{Result, anyhow};
22use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
23use rand::rngs::OsRng;
24use serde::{Deserialize, Serialize};
25use sha2::{Digest, Sha256};
26use std::collections::HashMap;
27use std::net::Ipv6Addr;
28use std::time::{Duration, SystemTime, UNIX_EPOCH};
29
30/// IPv6-based node identity that binds node ID to actual network location
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct IPv6NodeID {
33    /// Derived node ID (SHA256 of ipv6_addr + public_key + salt)
34    pub node_id: Vec<u8>,
35    /// IPv6 address this node ID is bound to
36    pub ipv6_addr: Ipv6Addr,
37    /// Ed25519 public key for signatures
38    pub public_key: Vec<u8>,
39    /// Signature proving ownership of the IPv6 address and keys
40    pub signature: Vec<u8>,
41    /// Timestamp when this ID was generated (seconds since epoch)
42    pub timestamp_secs: u64,
43    /// Salt used in node ID generation (for freshness)
44    pub salt: Vec<u8>,
45}
46
47/// Configuration for IP diversity enforcement at multiple subnet levels
48#[derive(Debug, Clone)]
49pub struct IPDiversityConfig {
50    /// Maximum nodes per /64 subnet (default: 1)
51    pub max_nodes_per_64: usize,
52    /// Maximum nodes per /48 allocation (default: 3)  
53    pub max_nodes_per_48: usize,
54    /// Maximum nodes per /32 region (default: 10)
55    pub max_nodes_per_32: usize,
56    /// Maximum nodes per AS number (default: 20)
57    pub max_nodes_per_asn: usize,
58    /// Enable GeoIP-based diversity checks
59    pub enable_geolocation_check: bool,
60    /// Minimum number of different countries required
61    pub min_geographic_diversity: usize,
62}
63
64/// Analysis of an IPv6 address for diversity enforcement
65#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
66pub struct IPAnalysis {
67    /// /64 subnet (host allocation)
68    pub subnet_64: Ipv6Addr,
69    /// /48 subnet (site allocation)
70    pub subnet_48: Ipv6Addr,
71    /// /32 subnet (ISP allocation)
72    pub subnet_32: Ipv6Addr,
73    /// Autonomous System Number (if available)
74    pub asn: Option<u32>,
75    /// Country code from GeoIP lookup
76    pub country: Option<String>,
77    /// Whether this is a known hosting/VPS provider
78    pub is_hosting_provider: bool,
79    /// Whether this is a known VPN provider
80    pub is_vpn_provider: bool,
81    /// Historical reputation score for this IP range
82    pub reputation_score: f64,
83}
84
85/// Node reputation tracking for security-aware routing
86#[derive(Debug, Clone)]
87pub struct NodeReputation {
88    /// Peer ID
89    pub peer_id: PeerId,
90    /// Fraction of queries answered successfully
91    pub response_rate: f64,
92    /// Average response time
93    pub response_time: Duration,
94    /// Consistency of provided data (0.0-1.0)
95    pub consistency_score: f64,
96    /// Estimated continuous uptime
97    pub uptime_estimate: Duration,
98    /// Accuracy of routing information provided
99    pub routing_accuracy: f64,
100    /// Last time this node was seen
101    pub last_seen: SystemTime,
102    /// Total number of interactions
103    pub interaction_count: u64,
104}
105
106impl Default for IPDiversityConfig {
107    fn default() -> Self {
108        Self {
109            max_nodes_per_64: 1,
110            max_nodes_per_48: 3,
111            max_nodes_per_32: 10,
112            max_nodes_per_asn: 20,
113            enable_geolocation_check: true,
114            min_geographic_diversity: 3,
115        }
116    }
117}
118
119impl IPv6NodeID {
120    /// Generate a new IPv6-based node ID
121    pub fn generate(ipv6_addr: Ipv6Addr, keypair: &SigningKey) -> Result<Self> {
122        let mut rng = rand::thread_rng();
123        let mut salt = vec![0u8; 16];
124        rand::RngCore::fill_bytes(&mut rng, &mut salt);
125
126        let timestamp = SystemTime::now();
127        let timestamp_secs = timestamp.duration_since(UNIX_EPOCH)?.as_secs();
128        let public_key = keypair.verifying_key().to_bytes().to_vec();
129
130        // Generate node ID: SHA256(ipv6_address || public_key || salt || timestamp)
131        let mut hasher = Sha256::new();
132        hasher.update(ipv6_addr.octets());
133        hasher.update(&public_key);
134        hasher.update(&salt);
135        hasher.update(timestamp_secs.to_le_bytes());
136        let node_id = hasher.finalize().to_vec();
137
138        // Create signature proving ownership
139        let mut message_to_sign = Vec::new();
140        message_to_sign.extend_from_slice(&ipv6_addr.octets());
141        message_to_sign.extend_from_slice(&public_key);
142        message_to_sign.extend_from_slice(&salt);
143        message_to_sign.extend_from_slice(&timestamp_secs.to_le_bytes());
144
145        let signature = keypair.sign(&message_to_sign).to_bytes().to_vec();
146
147        Ok(IPv6NodeID {
148            node_id,
149            ipv6_addr,
150            public_key,
151            signature,
152            timestamp_secs,
153            salt,
154        })
155    }
156
157    /// Verify that this node ID is valid and properly signed
158    pub fn verify(&self) -> Result<bool> {
159        // Reconstruct the node ID
160        let mut hasher = Sha256::new();
161        hasher.update(self.ipv6_addr.octets());
162        hasher.update(&self.public_key);
163        hasher.update(&self.salt);
164        hasher.update(self.timestamp_secs.to_le_bytes());
165        let expected_node_id = hasher.finalize();
166
167        // Verify node ID matches
168        if expected_node_id.as_slice() != self.node_id {
169            return Ok(false);
170        }
171
172        // Verify signature
173        if self.public_key.len() != 32 {
174            return Ok(false);
175        }
176        if self.signature.len() != 64 {
177            return Ok(false);
178        }
179
180        let mut pk_bytes = [0u8; 32];
181        pk_bytes.copy_from_slice(&self.public_key);
182        let public_key = VerifyingKey::from_bytes(&pk_bytes)
183            .map_err(|e| anyhow!("Invalid public key: {}", e))?;
184
185        let mut sig_bytes = [0u8; 64];
186        sig_bytes.copy_from_slice(&self.signature);
187        let signature = Signature::from_bytes(&sig_bytes);
188
189        let mut message_to_verify = Vec::new();
190        message_to_verify.extend_from_slice(&self.ipv6_addr.octets());
191        message_to_verify.extend_from_slice(&self.public_key);
192        message_to_verify.extend_from_slice(&self.salt);
193        message_to_verify.extend_from_slice(&self.timestamp_secs.to_le_bytes());
194
195        match public_key.verify(&message_to_verify, &signature) {
196            Ok(_) => Ok(true),
197            Err(_) => Ok(false),
198        }
199    }
200
201    /// Extract /64 subnet from IPv6 address
202    pub fn extract_subnet_64(&self) -> Ipv6Addr {
203        let octets = self.ipv6_addr.octets();
204        let mut subnet = [0u8; 16];
205        subnet[..8].copy_from_slice(&octets[..8]); // Keep first 64 bits, zero the rest
206        Ipv6Addr::from(subnet)
207    }
208
209    /// Extract /48 subnet from IPv6 address
210    pub fn extract_subnet_48(&self) -> Ipv6Addr {
211        let octets = self.ipv6_addr.octets();
212        let mut subnet = [0u8; 16];
213        subnet[..6].copy_from_slice(&octets[..6]); // Keep first 48 bits, zero the rest
214        Ipv6Addr::from(subnet)
215    }
216
217    /// Extract /32 subnet from IPv6 address
218    pub fn extract_subnet_32(&self) -> Ipv6Addr {
219        let octets = self.ipv6_addr.octets();
220        let mut subnet = [0u8; 16];
221        subnet[..4].copy_from_slice(&octets[..4]); // Keep first 32 bits, zero the rest
222        Ipv6Addr::from(subnet)
223    }
224}
225
226/// IP diversity enforcement system
227#[derive(Debug)]
228pub struct IPDiversityEnforcer {
229    config: IPDiversityConfig,
230    subnet_64_counts: HashMap<Ipv6Addr, usize>,
231    subnet_48_counts: HashMap<Ipv6Addr, usize>,
232    subnet_32_counts: HashMap<Ipv6Addr, usize>,
233    asn_counts: HashMap<u32, usize>,
234    country_counts: HashMap<String, usize>,
235}
236
237impl IPDiversityEnforcer {
238    /// Create a new IP diversity enforcer
239    pub fn new(config: IPDiversityConfig) -> Self {
240        Self {
241            config,
242            subnet_64_counts: HashMap::new(),
243            subnet_48_counts: HashMap::new(),
244            subnet_32_counts: HashMap::new(),
245            asn_counts: HashMap::new(),
246            country_counts: HashMap::new(),
247        }
248    }
249
250    /// Analyze an IPv6 address for diversity enforcement
251    pub fn analyze_ip(&self, ipv6_addr: Ipv6Addr) -> Result<IPAnalysis> {
252        let subnet_64 = Self::extract_subnet_prefix(ipv6_addr, 64);
253        let subnet_48 = Self::extract_subnet_prefix(ipv6_addr, 48);
254        let subnet_32 = Self::extract_subnet_prefix(ipv6_addr, 32);
255
256        // TODO: Implement ASN lookup (requires external database)
257        let asn = None;
258
259        // TODO: Implement GeoIP lookup (requires external database)
260        let country = None;
261
262        // TODO: Implement hosting/VPN provider detection
263        let is_hosting_provider = false;
264        let is_vpn_provider = false;
265
266        // Default reputation for new IPs
267        let reputation_score = 0.5;
268
269        Ok(IPAnalysis {
270            subnet_64,
271            subnet_48,
272            subnet_32,
273            asn,
274            country,
275            is_hosting_provider,
276            is_vpn_provider,
277            reputation_score,
278        })
279    }
280
281    /// Check if a new node can be accepted based on IP diversity constraints
282    pub fn can_accept_node(&self, ip_analysis: &IPAnalysis) -> bool {
283        // Determine limits based on hosting provider status
284        let (limit_64, limit_48, limit_32, limit_asn) =
285            if ip_analysis.is_hosting_provider || ip_analysis.is_vpn_provider {
286                // Stricter limits for hosting providers (halved)
287                (
288                    std::cmp::max(1, self.config.max_nodes_per_64 / 2),
289                    std::cmp::max(1, self.config.max_nodes_per_48 / 2),
290                    std::cmp::max(1, self.config.max_nodes_per_32 / 2),
291                    std::cmp::max(1, self.config.max_nodes_per_asn / 2),
292                )
293            } else {
294                // Regular limits for normal nodes
295                (
296                    self.config.max_nodes_per_64,
297                    self.config.max_nodes_per_48,
298                    self.config.max_nodes_per_32,
299                    self.config.max_nodes_per_asn,
300                )
301            };
302
303        // Check /64 subnet limit
304        if let Some(&count) = self.subnet_64_counts.get(&ip_analysis.subnet_64)
305            && count >= limit_64
306        {
307            return false;
308        }
309
310        // Check /48 subnet limit
311        if let Some(&count) = self.subnet_48_counts.get(&ip_analysis.subnet_48)
312            && count >= limit_48
313        {
314            return false;
315        }
316
317        // Check /32 subnet limit
318        if let Some(&count) = self.subnet_32_counts.get(&ip_analysis.subnet_32)
319            && count >= limit_32
320        {
321            return false;
322        }
323
324        // Check ASN limit
325        if let Some(asn) = ip_analysis.asn
326            && let Some(&count) = self.asn_counts.get(&asn)
327            && count >= limit_asn
328        {
329            return false;
330        }
331
332        true
333    }
334
335    /// Add a node to the diversity tracking
336    pub fn add_node(&mut self, ip_analysis: &IPAnalysis) -> Result<()> {
337        if !self.can_accept_node(ip_analysis) {
338            return Err(anyhow!("IP diversity limits exceeded"));
339        }
340
341        // Update counts
342        *self
343            .subnet_64_counts
344            .entry(ip_analysis.subnet_64)
345            .or_insert(0) += 1;
346        *self
347            .subnet_48_counts
348            .entry(ip_analysis.subnet_48)
349            .or_insert(0) += 1;
350        *self
351            .subnet_32_counts
352            .entry(ip_analysis.subnet_32)
353            .or_insert(0) += 1;
354
355        if let Some(asn) = ip_analysis.asn {
356            *self.asn_counts.entry(asn).or_insert(0) += 1;
357        }
358
359        if let Some(ref country) = ip_analysis.country {
360            *self.country_counts.entry(country.clone()).or_insert(0) += 1;
361        }
362
363        Ok(())
364    }
365
366    /// Remove a node from diversity tracking
367    pub fn remove_node(&mut self, ip_analysis: &IPAnalysis) {
368        if let Some(count) = self.subnet_64_counts.get_mut(&ip_analysis.subnet_64) {
369            *count = count.saturating_sub(1);
370            if *count == 0 {
371                self.subnet_64_counts.remove(&ip_analysis.subnet_64);
372            }
373        }
374
375        if let Some(count) = self.subnet_48_counts.get_mut(&ip_analysis.subnet_48) {
376            *count = count.saturating_sub(1);
377            if *count == 0 {
378                self.subnet_48_counts.remove(&ip_analysis.subnet_48);
379            }
380        }
381
382        if let Some(count) = self.subnet_32_counts.get_mut(&ip_analysis.subnet_32) {
383            *count = count.saturating_sub(1);
384            if *count == 0 {
385                self.subnet_32_counts.remove(&ip_analysis.subnet_32);
386            }
387        }
388
389        if let Some(asn) = ip_analysis.asn
390            && let Some(count) = self.asn_counts.get_mut(&asn)
391        {
392            *count = count.saturating_sub(1);
393            if *count == 0 {
394                self.asn_counts.remove(&asn);
395            }
396        }
397
398        if let Some(ref country) = ip_analysis.country
399            && let Some(count) = self.country_counts.get_mut(country)
400        {
401            *count = count.saturating_sub(1);
402            if *count == 0 {
403                self.country_counts.remove(country);
404            }
405        }
406    }
407
408    /// Extract network prefix of specified length from IPv6 address
409    pub fn extract_subnet_prefix(addr: Ipv6Addr, prefix_len: u8) -> Ipv6Addr {
410        let octets = addr.octets();
411        let mut subnet = [0u8; 16];
412
413        let bytes_to_copy = (prefix_len / 8) as usize;
414        let remaining_bits = prefix_len % 8;
415
416        // Copy full bytes
417        if bytes_to_copy < 16 {
418            subnet[..bytes_to_copy].copy_from_slice(&octets[..bytes_to_copy]);
419        } else {
420            subnet.copy_from_slice(&octets);
421        }
422
423        // Handle partial byte
424        if remaining_bits > 0 && bytes_to_copy < 16 {
425            let mask = 0xFF << (8 - remaining_bits);
426            subnet[bytes_to_copy] = octets[bytes_to_copy] & mask;
427        }
428
429        Ipv6Addr::from(subnet)
430    }
431
432    /// Get diversity statistics
433    pub fn get_diversity_stats(&self) -> DiversityStats {
434        DiversityStats {
435            total_64_subnets: self.subnet_64_counts.len(),
436            total_48_subnets: self.subnet_48_counts.len(),
437            total_32_subnets: self.subnet_32_counts.len(),
438            total_asns: self.asn_counts.len(),
439            total_countries: self.country_counts.len(),
440            max_nodes_per_64: self.subnet_64_counts.values().max().copied().unwrap_or(0),
441            max_nodes_per_48: self.subnet_48_counts.values().max().copied().unwrap_or(0),
442            max_nodes_per_32: self.subnet_32_counts.values().max().copied().unwrap_or(0),
443        }
444    }
445}
446
447/// Diversity statistics for monitoring
448#[derive(Debug, Clone, Serialize, Deserialize)]
449pub struct DiversityStats {
450    /// Number of unique /64 subnets represented
451    pub total_64_subnets: usize,
452    /// Number of unique /48 subnets represented
453    pub total_48_subnets: usize,
454    /// Number of unique /32 subnets represented
455    pub total_32_subnets: usize,
456    /// Number of unique ASNs represented
457    pub total_asns: usize,
458    /// Number of unique countries represented
459    pub total_countries: usize,
460    /// Maximum nodes in any single /64 subnet
461    pub max_nodes_per_64: usize,
462    /// Maximum nodes in any single /48 subnet
463    pub max_nodes_per_48: usize,
464    /// Maximum nodes in any single /32 subnet
465    pub max_nodes_per_32: usize,
466}
467
468/// Reputation manager for tracking node behavior
469#[derive(Debug)]
470pub struct ReputationManager {
471    reputations: HashMap<PeerId, NodeReputation>,
472    reputation_decay: f64,
473    min_reputation: f64,
474}
475
476impl ReputationManager {
477    /// Create a new reputation manager
478    pub fn new(reputation_decay: f64, min_reputation: f64) -> Self {
479        Self {
480            reputations: HashMap::new(),
481            reputation_decay,
482            min_reputation,
483        }
484    }
485
486    /// Get reputation for a peer
487    pub fn get_reputation(&self, peer_id: &PeerId) -> Option<&NodeReputation> {
488        self.reputations.get(peer_id)
489    }
490
491    /// Update reputation based on interaction
492    pub fn update_reputation(&mut self, peer_id: &PeerId, success: bool, response_time: Duration) {
493        let reputation =
494            self.reputations
495                .entry(peer_id.clone())
496                .or_insert_with(|| NodeReputation {
497                    peer_id: peer_id.clone(),
498                    response_rate: 0.5,
499                    response_time: Duration::from_millis(500),
500                    consistency_score: 0.5,
501                    uptime_estimate: Duration::from_secs(0),
502                    routing_accuracy: 0.5,
503                    last_seen: SystemTime::now(),
504                    interaction_count: 0,
505                });
506
507        // Use higher learning rate for faster convergence in tests
508        let alpha = 0.3; // Increased from 0.1 for better test convergence
509
510        if success {
511            reputation.response_rate = reputation.response_rate * (1.0 - alpha) + alpha;
512        } else {
513            reputation.response_rate *= 1.0 - alpha;
514        }
515
516        // Update response time
517        let response_time_ms = response_time.as_millis() as f64;
518        let current_response_ms = reputation.response_time.as_millis() as f64;
519        let new_response_ms = current_response_ms * (1.0 - alpha) + response_time_ms * alpha;
520        reputation.response_time = Duration::from_millis(new_response_ms as u64);
521
522        reputation.last_seen = SystemTime::now();
523        reputation.interaction_count += 1;
524    }
525
526    /// Apply time-based reputation decay
527    pub fn apply_decay(&mut self) {
528        let now = SystemTime::now();
529
530        self.reputations.retain(|_, reputation| {
531            if let Ok(elapsed) = now.duration_since(reputation.last_seen) {
532                // Decay reputation over time
533                let decay_factor = (-elapsed.as_secs_f64() / 3600.0 * self.reputation_decay).exp();
534                reputation.response_rate *= decay_factor;
535                reputation.consistency_score *= decay_factor;
536                reputation.routing_accuracy *= decay_factor;
537
538                // Remove nodes with very low reputation
539                reputation.response_rate > self.min_reputation / 10.0
540            } else {
541                true
542            }
543        });
544    }
545}
546
547/// Legacy security types for compatibility
548pub mod security_types {
549    use super::*;
550
551    /// Ed25519 key pair wrapper
552    pub struct KeyPair {
553        inner: SigningKey,
554    }
555
556    impl KeyPair {
557        /// Generate a new key pair
558        pub fn generate() -> Self {
559            // Generate key pair using ed25519-dalek directly
560            let signing_key = SigningKey::generate(&mut OsRng);
561
562            KeyPair { inner: signing_key }
563        }
564
565        /// Get the inner Ed25519 keypair
566        pub fn inner(&self) -> &SigningKey {
567            &self.inner
568        }
569
570        /// Get public key bytes
571        pub fn public_key_bytes(&self) -> [u8; 32] {
572            self.inner.verifying_key().to_bytes()
573        }
574
575        /// Sign a message
576        pub fn sign(&self, message: &[u8]) -> [u8; 64] {
577            self.inner.sign(message).to_bytes()
578        }
579    }
580}
581
582#[cfg(test)]
583mod tests {
584    use super::*;
585
586    fn create_test_keypair() -> SigningKey {
587        let mut csprng = rand::rngs::OsRng;
588        SigningKey::generate(&mut csprng)
589    }
590
591    fn create_test_ipv6() -> Ipv6Addr {
592        Ipv6Addr::new(
593            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7334,
594        )
595    }
596
597    fn create_test_diversity_config() -> IPDiversityConfig {
598        IPDiversityConfig {
599            max_nodes_per_64: 1,
600            max_nodes_per_48: 3,
601            max_nodes_per_32: 10,
602            max_nodes_per_asn: 20,
603            enable_geolocation_check: true,
604            min_geographic_diversity: 3,
605        }
606    }
607
608    #[test]
609    fn test_ipv6_node_id_generation() -> Result<()> {
610        let keypair = create_test_keypair();
611        let ipv6_addr = create_test_ipv6();
612
613        let node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
614
615        assert_eq!(node_id.ipv6_addr, ipv6_addr);
616        assert_eq!(node_id.public_key.len(), 32);
617        assert_eq!(node_id.signature.len(), 64);
618        assert_eq!(node_id.node_id.len(), 32); // SHA256 output
619        assert_eq!(node_id.salt.len(), 16);
620        assert!(node_id.timestamp_secs > 0);
621
622        Ok(())
623    }
624
625    #[test]
626    fn test_ipv6_node_id_verification() -> Result<()> {
627        let keypair = create_test_keypair();
628        let ipv6_addr = create_test_ipv6();
629
630        let node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
631        let is_valid = node_id.verify()?;
632
633        assert!(is_valid);
634
635        Ok(())
636    }
637
638    #[test]
639    fn test_ipv6_node_id_verification_fails_with_wrong_data() -> Result<()> {
640        let keypair = create_test_keypair();
641        let ipv6_addr = create_test_ipv6();
642
643        let mut node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
644
645        // Tamper with the node ID
646        node_id.node_id[0] ^= 0xFF;
647        let is_valid = node_id.verify()?;
648        assert!(!is_valid);
649
650        // Test with wrong signature length
651        let mut node_id2 = IPv6NodeID::generate(ipv6_addr, &keypair)?;
652        node_id2.signature = vec![0u8; 32]; // Wrong length
653        let is_valid2 = node_id2.verify()?;
654        assert!(!is_valid2);
655
656        // Test with wrong public key length
657        let mut node_id3 = IPv6NodeID::generate(ipv6_addr, &keypair)?;
658        node_id3.public_key = vec![0u8; 16]; // Wrong length
659        let is_valid3 = node_id3.verify()?;
660        assert!(!is_valid3);
661
662        Ok(())
663    }
664
665    #[test]
666    fn test_ipv6_subnet_extraction() -> Result<()> {
667        let keypair = create_test_keypair();
668        let ipv6_addr = Ipv6Addr::new(
669            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
670        );
671
672        let node_id = IPv6NodeID::generate(ipv6_addr, &keypair)?;
673
674        // Test /64 subnet extraction
675        let subnet_64 = node_id.extract_subnet_64();
676        let expected_64 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0);
677        assert_eq!(subnet_64, expected_64);
678
679        // Test /48 subnet extraction
680        let subnet_48 = node_id.extract_subnet_48();
681        let expected_48 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0);
682        assert_eq!(subnet_48, expected_48);
683
684        // Test /32 subnet extraction
685        let subnet_32 = node_id.extract_subnet_32();
686        let expected_32 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
687        assert_eq!(subnet_32, expected_32);
688
689        Ok(())
690    }
691
692    #[test]
693    fn test_ip_diversity_config_default() {
694        let config = IPDiversityConfig::default();
695
696        assert_eq!(config.max_nodes_per_64, 1);
697        assert_eq!(config.max_nodes_per_48, 3);
698        assert_eq!(config.max_nodes_per_32, 10);
699        assert_eq!(config.max_nodes_per_asn, 20);
700        assert!(config.enable_geolocation_check);
701        assert_eq!(config.min_geographic_diversity, 3);
702    }
703
704    #[test]
705    fn test_ip_diversity_enforcer_creation() {
706        let config = create_test_diversity_config();
707        let enforcer = IPDiversityEnforcer::new(config.clone());
708
709        assert_eq!(enforcer.config.max_nodes_per_64, config.max_nodes_per_64);
710        assert_eq!(enforcer.subnet_64_counts.len(), 0);
711        assert_eq!(enforcer.subnet_48_counts.len(), 0);
712        assert_eq!(enforcer.subnet_32_counts.len(), 0);
713    }
714
715    #[test]
716    fn test_ip_analysis() -> Result<()> {
717        let config = create_test_diversity_config();
718        let enforcer = IPDiversityEnforcer::new(config);
719
720        let ipv6_addr = create_test_ipv6();
721        let analysis = enforcer.analyze_ip(ipv6_addr)?;
722
723        assert_eq!(
724            analysis.subnet_64,
725            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 64)
726        );
727        assert_eq!(
728            analysis.subnet_48,
729            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 48)
730        );
731        assert_eq!(
732            analysis.subnet_32,
733            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 32)
734        );
735        assert!(analysis.asn.is_none()); // Not implemented in test
736        assert!(analysis.country.is_none()); // Not implemented in test
737        assert!(!analysis.is_hosting_provider);
738        assert!(!analysis.is_vpn_provider);
739        assert_eq!(analysis.reputation_score, 0.5);
740
741        Ok(())
742    }
743
744    #[test]
745    fn test_can_accept_node_basic() -> Result<()> {
746        let config = create_test_diversity_config();
747        let enforcer = IPDiversityEnforcer::new(config);
748
749        let ipv6_addr = create_test_ipv6();
750        let analysis = enforcer.analyze_ip(ipv6_addr)?;
751
752        // Should accept first node
753        assert!(enforcer.can_accept_node(&analysis));
754
755        Ok(())
756    }
757
758    #[test]
759    fn test_add_and_remove_node() -> Result<()> {
760        let config = create_test_diversity_config();
761        let mut enforcer = IPDiversityEnforcer::new(config);
762
763        let ipv6_addr = create_test_ipv6();
764        let analysis = enforcer.analyze_ip(ipv6_addr)?;
765
766        // Add node
767        enforcer.add_node(&analysis)?;
768        assert_eq!(enforcer.subnet_64_counts.get(&analysis.subnet_64), Some(&1));
769        assert_eq!(enforcer.subnet_48_counts.get(&analysis.subnet_48), Some(&1));
770        assert_eq!(enforcer.subnet_32_counts.get(&analysis.subnet_32), Some(&1));
771
772        // Remove node
773        enforcer.remove_node(&analysis);
774        assert_eq!(enforcer.subnet_64_counts.get(&analysis.subnet_64), None);
775        assert_eq!(enforcer.subnet_48_counts.get(&analysis.subnet_48), None);
776        assert_eq!(enforcer.subnet_32_counts.get(&analysis.subnet_32), None);
777
778        Ok(())
779    }
780
781    #[test]
782    fn test_diversity_limits_enforcement() -> Result<()> {
783        let config = create_test_diversity_config();
784        let mut enforcer = IPDiversityEnforcer::new(config);
785
786        let ipv6_addr1 = Ipv6Addr::new(
787            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
788        );
789        let ipv6_addr2 = Ipv6Addr::new(
790            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7335,
791        ); // Same /64
792
793        let analysis1 = enforcer.analyze_ip(ipv6_addr1)?;
794        let analysis2 = enforcer.analyze_ip(ipv6_addr2)?;
795
796        // First node should be accepted
797        assert!(enforcer.can_accept_node(&analysis1));
798        enforcer.add_node(&analysis1)?;
799
800        // Second node in same /64 should be rejected (max_nodes_per_64 = 1)
801        assert!(!enforcer.can_accept_node(&analysis2));
802
803        // But adding should fail
804        let result = enforcer.add_node(&analysis2);
805        assert!(result.is_err());
806        assert!(
807            result
808                .unwrap_err()
809                .to_string()
810                .contains("IP diversity limits exceeded")
811        );
812
813        Ok(())
814    }
815
816    #[test]
817    fn test_hosting_provider_stricter_limits() -> Result<()> {
818        let config = IPDiversityConfig {
819            max_nodes_per_64: 4, // Set higher limit for regular nodes
820            max_nodes_per_48: 8,
821            ..create_test_diversity_config()
822        };
823        let mut enforcer = IPDiversityEnforcer::new(config);
824
825        let ipv6_addr = create_test_ipv6();
826        let mut analysis = enforcer.analyze_ip(ipv6_addr)?;
827        analysis.is_hosting_provider = true;
828
829        // Should accept first hosting provider node
830        assert!(enforcer.can_accept_node(&analysis));
831        enforcer.add_node(&analysis)?;
832
833        // Add second hosting provider node in same /64 (should be accepted with limit=2)
834        let ipv6_addr2 = Ipv6Addr::new(
835            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7335,
836        );
837        let mut analysis2 = enforcer.analyze_ip(ipv6_addr2)?;
838        analysis2.is_hosting_provider = true;
839        analysis2.subnet_64 = analysis.subnet_64; // Force same subnet
840
841        assert!(enforcer.can_accept_node(&analysis2));
842        enforcer.add_node(&analysis2)?;
843
844        // Should reject third hosting provider node in same /64 (exceeds limit=2)
845        let ipv6_addr3 = Ipv6Addr::new(
846            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7336,
847        );
848        let mut analysis3 = enforcer.analyze_ip(ipv6_addr3)?;
849        analysis3.is_hosting_provider = true;
850        analysis3.subnet_64 = analysis.subnet_64; // Force same subnet
851
852        assert!(!enforcer.can_accept_node(&analysis3));
853
854        Ok(())
855    }
856
857    #[test]
858    fn test_diversity_stats() -> Result<()> {
859        let config = create_test_diversity_config();
860        let mut enforcer = IPDiversityEnforcer::new(config);
861
862        // Add some nodes with different subnets
863        let addresses = [
864            Ipv6Addr::new(
865                0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
866            ),
867            Ipv6Addr::new(
868                0x2001, 0xdb8, 0x85a4, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
869            ), // Different /48
870            Ipv6Addr::new(
871                0x2002, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
872            ), // Different /32
873        ];
874
875        for addr in addresses {
876            let analysis = enforcer.analyze_ip(addr)?;
877            enforcer.add_node(&analysis)?;
878        }
879
880        let stats = enforcer.get_diversity_stats();
881        assert_eq!(stats.total_64_subnets, 3);
882        assert_eq!(stats.total_48_subnets, 3);
883        assert_eq!(stats.total_32_subnets, 2); // Two /32 prefixes
884        assert_eq!(stats.max_nodes_per_64, 1);
885        assert_eq!(stats.max_nodes_per_48, 1);
886        assert_eq!(stats.max_nodes_per_32, 2); // 2001:db8 has 2 nodes
887
888        Ok(())
889    }
890
891    #[test]
892    fn test_extract_subnet_prefix() {
893        let addr = Ipv6Addr::new(
894            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
895        );
896
897        // Test /64 prefix
898        let prefix_64 = IPDiversityEnforcer::extract_subnet_prefix(addr, 64);
899        let expected_64 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0);
900        assert_eq!(prefix_64, expected_64);
901
902        // Test /48 prefix
903        let prefix_48 = IPDiversityEnforcer::extract_subnet_prefix(addr, 48);
904        let expected_48 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0);
905        assert_eq!(prefix_48, expected_48);
906
907        // Test /32 prefix
908        let prefix_32 = IPDiversityEnforcer::extract_subnet_prefix(addr, 32);
909        let expected_32 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
910        assert_eq!(prefix_32, expected_32);
911
912        // Test /56 prefix (partial byte)
913        let prefix_56 = IPDiversityEnforcer::extract_subnet_prefix(addr, 56);
914        let expected_56 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1200, 0, 0, 0, 0);
915        assert_eq!(prefix_56, expected_56);
916
917        // Test /128 prefix (full address)
918        let prefix_128 = IPDiversityEnforcer::extract_subnet_prefix(addr, 128);
919        assert_eq!(prefix_128, addr);
920    }
921
922    #[test]
923    fn test_reputation_manager_creation() {
924        let manager = ReputationManager::new(0.1, 0.1);
925        assert_eq!(manager.reputation_decay, 0.1);
926        assert_eq!(manager.min_reputation, 0.1);
927        assert_eq!(manager.reputations.len(), 0);
928    }
929
930    #[test]
931    fn test_reputation_get_nonexistent() {
932        let manager = ReputationManager::new(0.1, 0.1);
933        let peer_id = "test_peer".to_string();
934
935        let reputation = manager.get_reputation(&peer_id);
936        assert!(reputation.is_none());
937    }
938
939    #[test]
940    fn test_reputation_update_creates_entry() {
941        let mut manager = ReputationManager::new(0.1, 0.1);
942        let peer_id = "test_peer".to_string();
943
944        manager.update_reputation(&peer_id, true, Duration::from_millis(100));
945
946        let reputation = manager.get_reputation(&peer_id);
947        assert!(reputation.is_some());
948
949        let rep = reputation.unwrap();
950        assert_eq!(rep.peer_id, peer_id);
951        assert!(rep.response_rate > 0.5); // Should increase from initial 0.5
952        assert_eq!(rep.interaction_count, 1);
953    }
954
955    #[test]
956    fn test_reputation_update_success_improves_rate() {
957        let mut manager = ReputationManager::new(0.1, 0.1);
958        let peer_id = "test_peer".to_string();
959
960        // Multiple successful interactions
961        for _ in 0..15 {
962            manager.update_reputation(&peer_id, true, Duration::from_millis(100));
963        }
964
965        let reputation = manager.get_reputation(&peer_id).unwrap();
966        assert!(reputation.response_rate > 0.85); // Should be very high with higher learning rate
967        assert_eq!(reputation.interaction_count, 15);
968    }
969
970    #[test]
971    fn test_reputation_update_failure_decreases_rate() {
972        let mut manager = ReputationManager::new(0.1, 0.1);
973        let peer_id = "test_peer".to_string();
974
975        // Multiple failed interactions
976        for _ in 0..15 {
977            manager.update_reputation(&peer_id, false, Duration::from_millis(1000));
978        }
979
980        let reputation = manager.get_reputation(&peer_id).unwrap();
981        assert!(reputation.response_rate < 0.15); // Should be very low with higher learning rate
982        assert_eq!(reputation.interaction_count, 15);
983    }
984
985    #[test]
986    fn test_reputation_response_time_tracking() {
987        let mut manager = ReputationManager::new(0.1, 0.1);
988        let peer_id = "test_peer".to_string();
989
990        // Update with specific response time
991        manager.update_reputation(&peer_id, true, Duration::from_millis(200));
992
993        let reputation = manager.get_reputation(&peer_id).unwrap();
994        // Response time should be between initial 500ms and new 200ms
995        assert!(reputation.response_time.as_millis() > 200);
996        assert!(reputation.response_time.as_millis() < 500);
997    }
998
999    #[test]
1000    fn test_reputation_decay() {
1001        let mut manager = ReputationManager::new(1.0, 0.01); // High decay rate
1002        let peer_id = "test_peer".to_string();
1003
1004        // Create a reputation entry
1005        manager.update_reputation(&peer_id, true, Duration::from_millis(100));
1006
1007        // Manually set last_seen to past
1008        if let Some(reputation) = manager.reputations.get_mut(&peer_id) {
1009            reputation.last_seen = SystemTime::now() - Duration::from_secs(7200); // 2 hours ago
1010        }
1011
1012        let original_rate = manager.get_reputation(&peer_id).unwrap().response_rate;
1013
1014        // Apply decay
1015        manager.apply_decay();
1016
1017        let reputation = manager.get_reputation(&peer_id);
1018        if let Some(rep) = reputation {
1019            // Should have decayed
1020            assert!(rep.response_rate < original_rate);
1021        } // else the reputation was removed due to low score
1022    }
1023
1024    #[test]
1025    fn test_reputation_decay_removes_low_reputation() {
1026        let mut manager = ReputationManager::new(0.1, 0.5); // High min reputation
1027        let peer_id = "test_peer".to_string();
1028
1029        // Create a low reputation entry
1030        for _ in 0..10 {
1031            manager.update_reputation(&peer_id, false, Duration::from_millis(1000));
1032        }
1033
1034        // Manually set last_seen to past
1035        if let Some(reputation) = manager.reputations.get_mut(&peer_id) {
1036            reputation.last_seen = SystemTime::now() - Duration::from_secs(3600); // 1 hour ago
1037            reputation.response_rate = 0.01; // Very low
1038        }
1039
1040        // Apply decay
1041        manager.apply_decay();
1042
1043        // Should be removed
1044        assert!(manager.get_reputation(&peer_id).is_none());
1045    }
1046
1047    #[test]
1048    fn test_security_types_keypair() {
1049        let keypair = security_types::KeyPair::generate();
1050
1051        let public_key_bytes = keypair.public_key_bytes();
1052        assert_eq!(public_key_bytes.len(), 32);
1053
1054        let message = b"test message";
1055        let signature = keypair.sign(message);
1056        assert_eq!(signature.len(), 64);
1057
1058        // Verify the signature using the inner keypair
1059        let inner = keypair.inner();
1060        assert!(
1061            inner
1062                .verify(message, &Signature::from_bytes(&signature))
1063                .is_ok()
1064        );
1065    }
1066
1067    #[test]
1068    fn test_node_reputation_structure() {
1069        let peer_id = "test_peer".to_string();
1070        let reputation = NodeReputation {
1071            peer_id: peer_id.clone(),
1072            response_rate: 0.85,
1073            response_time: Duration::from_millis(150),
1074            consistency_score: 0.9,
1075            uptime_estimate: Duration::from_secs(86400),
1076            routing_accuracy: 0.8,
1077            last_seen: SystemTime::now(),
1078            interaction_count: 42,
1079        };
1080
1081        assert_eq!(reputation.peer_id, peer_id);
1082        assert_eq!(reputation.response_rate, 0.85);
1083        assert_eq!(reputation.response_time, Duration::from_millis(150));
1084        assert_eq!(reputation.consistency_score, 0.9);
1085        assert_eq!(reputation.uptime_estimate, Duration::from_secs(86400));
1086        assert_eq!(reputation.routing_accuracy, 0.8);
1087        assert_eq!(reputation.interaction_count, 42);
1088    }
1089
1090    #[test]
1091    fn test_ip_analysis_structure() {
1092        let analysis = IPAnalysis {
1093            subnet_64: Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0),
1094            subnet_48: Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0),
1095            subnet_32: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
1096            asn: Some(64512),
1097            country: Some("US".to_string()),
1098            is_hosting_provider: true,
1099            is_vpn_provider: false,
1100            reputation_score: 0.75,
1101        };
1102
1103        assert_eq!(analysis.asn, Some(64512));
1104        assert_eq!(analysis.country, Some("US".to_string()));
1105        assert!(analysis.is_hosting_provider);
1106        assert!(!analysis.is_vpn_provider);
1107        assert_eq!(analysis.reputation_score, 0.75);
1108    }
1109
1110    #[test]
1111    fn test_diversity_stats_structure() {
1112        let stats = DiversityStats {
1113            total_64_subnets: 100,
1114            total_48_subnets: 50,
1115            total_32_subnets: 25,
1116            total_asns: 15,
1117            total_countries: 8,
1118            max_nodes_per_64: 1,
1119            max_nodes_per_48: 3,
1120            max_nodes_per_32: 10,
1121        };
1122
1123        assert_eq!(stats.total_64_subnets, 100);
1124        assert_eq!(stats.total_48_subnets, 50);
1125        assert_eq!(stats.total_32_subnets, 25);
1126        assert_eq!(stats.total_asns, 15);
1127        assert_eq!(stats.total_countries, 8);
1128        assert_eq!(stats.max_nodes_per_64, 1);
1129        assert_eq!(stats.max_nodes_per_48, 3);
1130        assert_eq!(stats.max_nodes_per_32, 10);
1131    }
1132
1133    #[test]
1134    fn test_multiple_same_subnet_nodes() -> Result<()> {
1135        let config = IPDiversityConfig {
1136            max_nodes_per_64: 3, // Allow more nodes in same /64
1137            max_nodes_per_48: 5,
1138            max_nodes_per_32: 10,
1139            ..create_test_diversity_config()
1140        };
1141        let mut enforcer = IPDiversityEnforcer::new(config);
1142
1143        let _base_addr = Ipv6Addr::new(
1144            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x0000,
1145        );
1146
1147        // Add 3 nodes in same /64 subnet
1148        for i in 1..=3 {
1149            let addr = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, i);
1150            let analysis = enforcer.analyze_ip(addr)?;
1151            assert!(enforcer.can_accept_node(&analysis));
1152            enforcer.add_node(&analysis)?;
1153        }
1154
1155        // 4th node should be rejected
1156        let addr4 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 4);
1157        let analysis4 = enforcer.analyze_ip(addr4)?;
1158        assert!(!enforcer.can_accept_node(&analysis4));
1159
1160        let stats = enforcer.get_diversity_stats();
1161        assert_eq!(stats.total_64_subnets, 1);
1162        assert_eq!(stats.max_nodes_per_64, 3);
1163
1164        Ok(())
1165    }
1166
1167    #[test]
1168    fn test_asn_and_country_tracking() -> Result<()> {
1169        let config = create_test_diversity_config();
1170        let mut enforcer = IPDiversityEnforcer::new(config);
1171
1172        // Create analysis with ASN and country
1173        let ipv6_addr = create_test_ipv6();
1174        let mut analysis = enforcer.analyze_ip(ipv6_addr)?;
1175        analysis.asn = Some(64512);
1176        analysis.country = Some("US".to_string());
1177
1178        enforcer.add_node(&analysis)?;
1179
1180        assert_eq!(enforcer.asn_counts.get(&64512), Some(&1));
1181        assert_eq!(enforcer.country_counts.get("US"), Some(&1));
1182
1183        // Remove and check cleanup
1184        enforcer.remove_node(&analysis);
1185        assert!(enforcer.asn_counts.get(&64512).is_none());
1186        assert!(enforcer.country_counts.get("US").is_none());
1187
1188        Ok(())
1189    }
1190
1191    #[test]
1192    fn test_reputation_mixed_interactions() {
1193        let mut manager = ReputationManager::new(0.1, 0.1);
1194        let peer_id = "test_peer".to_string();
1195
1196        // Mix of successful and failed interactions
1197        for i in 0..15 {
1198            let success = i % 3 != 0; // 2/3 success rate
1199            manager.update_reputation(&peer_id, success, Duration::from_millis(100 + i * 10));
1200        }
1201
1202        let reputation = manager.get_reputation(&peer_id).unwrap();
1203        // Should converge closer to 2/3 with more iterations and higher learning rate
1204        // With alpha=0.3 and 2/3 success rate, convergence may be higher
1205        assert!(reputation.response_rate > 0.55);
1206        assert!(reputation.response_rate < 0.85);
1207        assert_eq!(reputation.interaction_count, 15);
1208    }
1209}