use crate::dht::{Key, DHTNode};
use crate::security::ReputationManager;
use crate::{PeerId, Result, P2PError};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet, VecDeque};
use std::time::{Duration, Instant, SystemTime};
use tracing::{debug, info, warn};
#[derive(Debug, Clone)]
pub struct SKademliaConfig {
pub disjoint_path_count: usize,
pub max_shared_nodes: usize,
pub sibling_list_size: usize,
pub security_bucket_size: usize,
pub enable_distance_verification: bool,
pub enable_routing_validation: bool,
pub min_routing_reputation: f64,
pub lookup_timeout: Duration,
}
impl Default for SKademliaConfig {
fn default() -> Self {
Self {
disjoint_path_count: 3,
max_shared_nodes: 1,
sibling_list_size: 16,
security_bucket_size: 8,
enable_distance_verification: true,
enable_routing_validation: true,
min_routing_reputation: 0.3,
lookup_timeout: Duration::from_secs(30),
}
}
}
#[derive(Debug, Clone)]
pub struct DisjointPathLookup {
pub target: Key,
pub paths: Vec<Vec<DHTNode>>,
pub path_count: usize,
pub max_shared_nodes: usize,
pub started_at: Instant,
pub path_states: Vec<PathState>,
}
#[derive(Debug, Clone)]
pub struct PathState {
pub path_id: usize,
pub nodes: Vec<DHTNode>,
pub queried: HashSet<PeerId>,
pub to_query: VecDeque<DHTNode>,
pub completed: bool,
pub results: Vec<DHTNode>,
}
#[derive(Debug, Clone)]
pub struct SiblingList {
pub local_id: Key,
pub siblings: Vec<DHTNode>,
pub max_size: usize,
pub last_updated: Instant,
}
#[derive(Debug, Clone)]
pub struct SecurityBucket {
pub trusted_nodes: Vec<DHTNode>,
pub backup_routes: Vec<Vec<DHTNode>>,
pub max_size: usize,
pub last_validated: Instant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DistanceChallenge {
pub challenger: PeerId,
pub target_key: Key,
pub expected_distance: Key,
pub nonce: [u8; 32],
pub timestamp: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DistanceProof {
pub challenge: DistanceChallenge,
pub proof_nodes: Vec<PeerId>,
pub signatures: Vec<Vec<u8>>,
pub response_time: Duration,
}
#[derive(Debug, Clone)]
pub struct EnhancedDistanceChallenge {
pub challenger: PeerId,
pub target_key: Key,
pub expected_distance: Key,
pub nonce: [u8; 32],
pub timestamp: SystemTime,
pub witness_nodes: Vec<PeerId>,
pub challenge_round: u32,
pub max_rounds: u32,
}
#[derive(Debug, Clone)]
pub struct DistanceConsensus {
pub target_key: Key,
pub target_node: PeerId,
pub consensus_distance: Key,
pub measurements: Vec<DistanceMeasurement>,
pub confidence: f64,
pub verified_at: SystemTime,
}
#[derive(Debug, Clone)]
pub struct DistanceMeasurement {
pub witness: PeerId,
pub distance: Key,
pub confidence: f64,
pub response_time: Duration,
}
#[derive(Debug, Clone)]
pub struct ConsistencyReport {
pub nodes_checked: usize,
pub inconsistencies: usize,
pub suspicious_nodes: Vec<PeerId>,
pub validated_at: Instant,
}
#[derive(Debug)]
pub struct SKademlia {
pub config: SKademliaConfig,
pub sibling_lists: HashMap<Key, SiblingList>,
pub security_buckets: HashMap<Key, SecurityBucket>,
pub reputation_manager: ReputationManager,
pub active_lookups: HashMap<Key, DisjointPathLookup>,
pub pending_challenges: HashMap<PeerId, DistanceChallenge>,
}
impl DisjointPathLookup {
pub fn new(target: Key, path_count: usize, max_shared_nodes: usize) -> Self {
let path_states = (0..path_count)
.map(|i| PathState {
path_id: i,
nodes: Vec::new(),
queried: HashSet::new(),
to_query: VecDeque::new(),
completed: false,
results: Vec::new(),
})
.collect();
Self {
target,
paths: vec![Vec::new(); path_count],
path_count,
max_shared_nodes,
started_at: Instant::now(),
path_states,
}
}
pub fn initialize_paths(&mut self, initial_nodes: Vec<DHTNode>) -> Result<()> {
if initial_nodes.len() < self.path_count {
return Err(P2PError::DHT("Not enough initial nodes for disjoint paths".to_string()).into());
}
for (i, node) in initial_nodes.into_iter().enumerate() {
let path_id = i % self.path_count;
self.path_states[path_id].to_query.push_back(node.clone());
self.paths[path_id].push(node);
}
Ok(())
}
pub fn verify_disjointness(&self) -> bool {
for i in 0..self.path_count {
for j in (i + 1)..self.path_count {
let shared_count = self.count_shared_nodes(i, j);
if shared_count > self.max_shared_nodes {
debug!("Paths {} and {} share {} nodes (max: {})",
i, j, shared_count, self.max_shared_nodes);
return false;
}
}
}
true
}
fn count_shared_nodes(&self, path1: usize, path2: usize) -> usize {
let nodes1: HashSet<_> = self.path_states[path1].queried.iter().collect();
let nodes2: HashSet<_> = self.path_states[path2].queried.iter().collect();
nodes1.intersection(&nodes2).count()
}
pub fn get_next_node(&mut self, path_id: usize) -> Option<DHTNode> {
if path_id >= self.path_count {
return None;
}
if self.path_states[path_id].completed {
return None;
}
let mut next_node = None;
while let Some(node) = self.path_states[path_id].to_query.pop_front() {
if !self.path_states[path_id].queried.contains(&node.peer_id) {
if self.would_violate_disjointness(path_id, &node.peer_id) {
continue;
}
next_node = Some(node);
break;
}
}
if let Some(node) = next_node {
self.path_states[path_id].queried.insert(node.peer_id.clone());
return Some(node);
}
self.path_states[path_id].completed = true;
None
}
fn would_violate_disjointness(&self, path_id: usize, peer_id: &PeerId) -> bool {
for (i, path_state) in self.path_states.iter().enumerate() {
if i == path_id {
continue;
}
if path_state.queried.contains(peer_id) {
let shared_count = self.count_shared_nodes(path_id, i);
if shared_count >= self.max_shared_nodes {
return true;
}
}
}
false
}
pub fn add_query_results(&mut self, path_id: usize, nodes: Vec<DHTNode>) {
if path_id >= self.path_count {
return;
}
let target = self.target.clone();
for node in nodes {
let distance = node.distance.distance(&target);
if self.is_close_to_target(&distance) {
self.path_states[path_id].results.push(node.clone());
}
if !self.path_states[path_id].queried.contains(&node.peer_id) {
self.path_states[path_id].to_query.push_back(node);
}
}
let mut to_query: Vec<_> = self.path_states[path_id].to_query.drain(..).collect();
to_query.sort_by_key(|node| node.distance.distance(&target).leading_zeros());
self.path_states[path_id].to_query = to_query.into();
}
fn is_close_to_target(&self, distance: &Key) -> bool {
distance.leading_zeros() > 128 }
pub fn is_complete(&self) -> bool {
self.path_states.iter().all(|path| path.completed) ||
self.started_at.elapsed() > Duration::from_secs(60)
}
pub fn get_results(&self) -> Vec<DHTNode> {
let mut all_results = Vec::new();
for path_state in &self.path_states {
all_results.extend(path_state.results.clone());
}
all_results.sort_by_key(|node| node.distance.distance(&self.target).leading_zeros());
all_results.dedup_by_key(|node| node.peer_id.clone());
all_results
}
pub fn validate_results(&self) -> Result<bool> {
let mut path_results: Vec<Vec<&DHTNode>> = Vec::new();
for path_state in &self.path_states {
let mut sorted_results: Vec<_> = path_state.results.iter().collect();
sorted_results.sort_by_key(|node| node.distance.distance(&self.target).leading_zeros());
path_results.push(sorted_results);
}
let min_results = path_results.iter().map(|r| r.len()).min().unwrap_or(0);
if min_results == 0 {
return Ok(true); }
let consensus_threshold = (self.path_count * 2) / 3; let check_count = std::cmp::min(min_results, 5);
for i in 0..check_count {
let mut node_counts: HashMap<PeerId, usize> = HashMap::new();
for path_result in &path_results {
if i < path_result.len() {
*node_counts.entry(path_result[i].peer_id.clone()).or_insert(0) += 1;
}
}
let has_consensus = node_counts.values().any(|&count| count >= consensus_threshold);
if !has_consensus {
warn!("No consensus for result position {}: {:?}", i, node_counts);
return Ok(false);
}
}
Ok(true)
}
}
impl SiblingList {
pub fn new(local_id: Key, max_size: usize) -> Self {
Self {
local_id,
siblings: Vec::new(),
max_size,
last_updated: Instant::now(),
}
}
pub fn add_node(&mut self, node: DHTNode) {
self.siblings.retain(|n| n.peer_id != node.peer_id);
self.siblings.push(node);
self.siblings.sort_by_key(|n| n.distance.distance(&self.local_id).leading_zeros());
if self.siblings.len() > self.max_size {
self.siblings.truncate(self.max_size);
}
self.last_updated = Instant::now();
}
pub fn get_closest_siblings(&self, count: usize) -> Vec<&DHTNode> {
self.siblings.iter().take(count).collect()
}
pub fn verify_routing_decision(&self, target: &Key, proposed_nodes: &[DHTNode]) -> bool {
let expected_distance = target.distance(&self.local_id);
for proposed in proposed_nodes {
let proposed_distance = target.distance(&proposed.distance);
if proposed_distance.leading_zeros() <= expected_distance.leading_zeros() {
debug!("Proposed node is not closer to target than local node");
return false;
}
let should_know = self.siblings.iter().any(|sibling| {
let sibling_to_target = target.distance(&sibling.distance);
let sibling_to_proposed = proposed.distance.distance(&sibling.distance);
sibling_to_proposed.leading_zeros() > sibling_to_target.leading_zeros()
});
if !should_know {
debug!("No sibling knows about proposed node: {}", proposed.peer_id);
}
}
true
}
}
impl SecurityBucket {
pub fn new(max_size: usize) -> Self {
Self {
trusted_nodes: Vec::new(),
backup_routes: Vec::new(),
max_size,
last_validated: Instant::now(),
}
}
pub fn add_trusted_node(&mut self, node: DHTNode) {
self.trusted_nodes.retain(|n| n.peer_id != node.peer_id);
self.trusted_nodes.push(node);
if self.trusted_nodes.len() > self.max_size {
self.trusted_nodes.sort_by_key(|n| n.last_seen);
self.trusted_nodes.truncate(self.max_size);
}
}
pub fn get_trusted_nodes(&self) -> &[DHTNode] {
&self.trusted_nodes
}
pub fn add_backup_route(&mut self, route: Vec<DHTNode>) {
self.backup_routes.push(route);
if self.backup_routes.len() > 5 {
self.backup_routes.remove(0);
}
}
pub fn get_backup_routes(&self) -> &[Vec<DHTNode>] {
&self.backup_routes
}
}
impl SKademlia {
pub fn new(config: SKademliaConfig) -> Self {
let reputation_manager = ReputationManager::new(0.1, config.min_routing_reputation);
Self {
config,
sibling_lists: HashMap::new(),
security_buckets: HashMap::new(),
reputation_manager,
active_lookups: HashMap::new(),
pending_challenges: HashMap::new(),
}
}
pub async fn secure_lookup(&mut self, target: Key, initial_nodes: Vec<DHTNode>) -> Result<Vec<DHTNode>> {
info!("Starting secure lookup for target: {}", target.to_hex());
let mut lookup = DisjointPathLookup::new(
target.clone(),
self.config.disjoint_path_count,
self.config.max_shared_nodes
);
lookup.initialize_paths(initial_nodes)?;
if !lookup.verify_disjointness() {
warn!("Unable to create sufficiently disjoint paths");
}
self.active_lookups.insert(target.clone(), lookup);
if let Some(lookup) = self.active_lookups.get(&target) {
Ok(lookup.get_results())
} else {
Err(P2PError::DHT("Lookup disappeared".to_string()).into())
}
}
pub fn update_sibling_list(&mut self, key: Key, nodes: Vec<DHTNode>) {
let sibling_list = self.sibling_lists.entry(key.clone()).or_insert_with(|| {
SiblingList::new(key, self.config.sibling_list_size)
});
for node in nodes {
sibling_list.add_node(node);
}
}
pub fn get_security_bucket(&mut self, key: &Key) -> &mut SecurityBucket {
self.security_buckets.entry(key.clone()).or_insert_with(|| {
SecurityBucket::new(self.config.security_bucket_size)
})
}
pub fn create_distance_challenge(&mut self, target: &PeerId, key: &Key) -> DistanceChallenge {
let mut nonce = [0u8; 32];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce);
let challenge = DistanceChallenge {
challenger: target.clone(), target_key: key.clone(),
expected_distance: key.distance(&Key::new(target.as_bytes())),
nonce,
timestamp: SystemTime::now(),
};
self.pending_challenges.insert(target.clone(), challenge.clone());
challenge
}
pub fn create_enhanced_distance_challenge(&mut self, target: &PeerId, key: &Key, witness_nodes: Vec<PeerId>) -> EnhancedDistanceChallenge {
let mut nonce = [0u8; 32];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce);
let challenge = EnhancedDistanceChallenge {
challenger: target.clone(),
target_key: key.clone(),
expected_distance: key.distance(&Key::new(target.as_bytes())),
nonce,
timestamp: SystemTime::now(),
witness_nodes,
challenge_round: 1,
max_rounds: 3,
};
self.pending_challenges.insert(target.clone(), DistanceChallenge {
challenger: challenge.challenger.clone(),
target_key: challenge.target_key.clone(),
expected_distance: challenge.expected_distance.clone(),
nonce: challenge.nonce,
timestamp: challenge.timestamp,
});
challenge
}
pub fn verify_distance_proof(&self, proof: &DistanceProof) -> Result<bool> {
let elapsed = proof.challenge.timestamp.elapsed()
.map_err(|e| P2PError::DHT(format!("Invalid timestamp: {}", e)))?;
if elapsed > Duration::from_secs(300) {
return Ok(false); }
let actual_distance = proof.challenge.target_key.distance(
&Key::new(proof.challenge.challenger.as_bytes())
);
if actual_distance.as_bytes() != proof.challenge.expected_distance.as_bytes() {
return Ok(false);
}
if proof.proof_nodes.len() != proof.signatures.len() {
return Ok(false);
}
let min_proofs = (self.config.disjoint_path_count + 1) / 2;
Ok(proof.proof_nodes.len() >= min_proofs)
}
pub async fn verify_distance_consensus(&mut self, target_node: &PeerId, target_key: &Key, witness_nodes: Vec<PeerId>) -> Result<DistanceConsensus> {
let mut measurements = Vec::new();
for witness in &witness_nodes {
let start_time = Instant::now();
let simulated_distance = target_key.distance(&Key::new(target_node.as_bytes()));
let response_time = start_time.elapsed();
let confidence = self.reputation_manager.get_reputation(witness)
.map(|rep| rep.response_rate * rep.consistency_score)
.unwrap_or(0.5);
let measurement = DistanceMeasurement {
witness: witness.clone(),
distance: simulated_distance.clone(),
confidence,
response_time,
};
measurements.push(measurement);
}
let total_confidence: f64 = measurements.iter().map(|m| m.confidence).sum();
let confidence = if measurements.is_empty() { 0.0 } else { total_confidence / measurements.len() as f64 };
let consensus_distance = if measurements.is_empty() {
Key::from_hash([0u8; 32])
} else {
self.calculate_consensus_distance(&measurements)?
};
Ok(DistanceConsensus {
target_key: target_key.clone(),
target_node: target_node.clone(),
consensus_distance,
measurements,
confidence,
verified_at: SystemTime::now(),
})
}
fn calculate_consensus_distance(&self, measurements: &[DistanceMeasurement]) -> Result<Key> {
if measurements.is_empty() {
return Err(P2PError::DHT("No measurements provided".to_string()).into());
}
let best_measurement = measurements.iter()
.max_by(|a, b| a.confidence.partial_cmp(&b.confidence).unwrap_or(std::cmp::Ordering::Equal))
.unwrap();
Ok(best_measurement.distance.clone())
}
pub async fn verify_distance_multi_round(&mut self, challenge: &EnhancedDistanceChallenge) -> Result<bool> {
let mut successful_rounds = 0;
let required_rounds = (challenge.max_rounds + 1) / 2;
for _round in 1..=challenge.max_rounds {
let round_witnesses: Vec<_> = challenge.witness_nodes.iter()
.take(3) .cloned()
.collect();
if round_witnesses.is_empty() {
break;
}
let consensus = self.verify_distance_consensus(
&challenge.challenger,
&challenge.target_key,
round_witnesses
).await?;
let distance_diff = consensus.consensus_distance.distance(&challenge.expected_distance);
let tolerance = Key::new(&[0; 31].iter().chain(&[1u8]).copied().collect::<Vec<_>>());
if distance_diff.distance(&Key::new(&[0; 32])).as_bytes() <= tolerance.as_bytes() {
successful_rounds += 1;
}
if successful_rounds >= required_rounds {
return Ok(true);
}
}
Ok(successful_rounds >= required_rounds)
}
pub fn create_adaptive_distance_challenge(&mut self, target: &PeerId, key: &Key, suspected_attack: bool) -> EnhancedDistanceChallenge {
let witness_count = if suspected_attack { 7 } else { 3 }; let max_rounds = if suspected_attack { 5 } else { 3 };
let mut witness_nodes = Vec::new();
for i in 0..witness_count {
witness_nodes.push(format!("witness_{}", i));
}
let mut nonce = [0u8; 32];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce);
EnhancedDistanceChallenge {
challenger: target.clone(),
target_key: key.clone(),
expected_distance: key.distance(&Key::new(target.as_bytes())),
nonce,
timestamp: SystemTime::now(),
witness_nodes,
challenge_round: 1,
max_rounds,
}
}
pub async fn validate_routing_consistency(&self, nodes: &[DHTNode]) -> Result<ConsistencyReport> {
let mut inconsistencies = 0;
let mut suspicious_nodes = Vec::new();
for node in nodes {
if let Some(reputation) = self.reputation_manager.get_reputation(&node.peer_id) {
if reputation.response_rate < self.config.min_routing_reputation {
inconsistencies += 1;
suspicious_nodes.push(node.peer_id.clone());
}
}
}
Ok(ConsistencyReport {
nodes_checked: nodes.len(),
inconsistencies,
suspicious_nodes,
validated_at: Instant::now(),
})
}
pub fn select_secure_nodes(&self, candidates: &[DHTNode], target: &Key, count: usize) -> Vec<DHTNode> {
let mut scored_nodes: Vec<_> = candidates.iter().map(|node| {
let distance_score = node.distance.distance(target).leading_zeros() as f64;
let reputation_score = self.reputation_manager.get_reputation(&node.peer_id)
.map(|rep| rep.response_rate * rep.consistency_score)
.unwrap_or(0.0);
let combined_score = distance_score + (reputation_score * 100.0);
(node.clone(), combined_score)
}).collect();
scored_nodes.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
scored_nodes.into_iter()
.take(count)
.map(|(node, _)| node)
.collect()
}
pub fn cleanup_expired(&mut self) {
let _now = Instant::now();
self.active_lookups.retain(|_, lookup| {
!lookup.is_complete() && lookup.started_at.elapsed() < self.config.lookup_timeout
});
self.pending_challenges.retain(|_, challenge| {
challenge.timestamp.elapsed().unwrap_or(Duration::MAX) < Duration::from_secs(300)
});
self.reputation_manager.apply_decay();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dht::DHTNode;
use std::time::SystemTime;
fn create_test_dht_node(peer_id: &str, distance_bytes: [u8; 32]) -> DHTNode {
DHTNode {
peer_id: peer_id.to_string(),
addresses: vec![],
last_seen: std::time::Instant::now(),
distance: Key::from_hash(distance_bytes),
is_connected: false,
}
}
fn create_test_key(bytes: [u8; 32]) -> Key {
Key::from_hash(bytes)
}
#[test]
fn test_skademlia_config_default() {
let config = SKademliaConfig::default();
assert_eq!(config.disjoint_path_count, 3);
assert_eq!(config.max_shared_nodes, 1);
assert_eq!(config.sibling_list_size, 16);
assert_eq!(config.security_bucket_size, 8);
assert!(config.enable_distance_verification);
assert!(config.enable_routing_validation);
assert_eq!(config.min_routing_reputation, 0.3);
assert_eq!(config.lookup_timeout, Duration::from_secs(30));
}
#[test]
fn test_disjoint_path_lookup_creation() {
let target = create_test_key([1u8; 32]);
let lookup = DisjointPathLookup::new(target.clone(), 3, 1);
assert_eq!(lookup.target, target);
assert_eq!(lookup.path_count, 3);
assert_eq!(lookup.max_shared_nodes, 1);
assert_eq!(lookup.paths.len(), 3);
assert_eq!(lookup.path_states.len(), 3);
for (i, path_state) in lookup.path_states.iter().enumerate() {
assert_eq!(path_state.path_id, i);
assert!(path_state.nodes.is_empty());
assert!(path_state.queried.is_empty());
assert!(path_state.to_query.is_empty());
assert!(!path_state.completed);
assert!(path_state.results.is_empty());
}
}
#[test]
fn test_disjoint_path_initialization() -> Result<()> {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 3, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
create_test_dht_node("peer3", [4u8; 32]),
create_test_dht_node("peer4", [5u8; 32]),
];
lookup.initialize_paths(initial_nodes)?;
assert!(!lookup.path_states[0].to_query.is_empty());
assert!(!lookup.path_states[1].to_query.is_empty());
assert!(!lookup.path_states[2].to_query.is_empty());
for path_state in &lookup.path_states {
assert!(!path_state.to_query.is_empty());
}
Ok(())
}
#[test]
fn test_disjoint_path_initialization_insufficient_nodes() {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 5, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
let result = lookup.initialize_paths(initial_nodes);
assert!(result.is_err());
}
#[test]
fn test_disjoint_path_get_next_node() -> Result<()> {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 2, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
lookup.initialize_paths(initial_nodes)?;
let next_node = lookup.get_next_node(0);
assert!(next_node.is_some());
if let Some(node) = next_node {
assert!(lookup.path_states[0].queried.contains(&node.peer_id));
}
Ok(())
}
#[test]
fn test_disjoint_path_invalid_path_id() -> Result<()> {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 2, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
lookup.initialize_paths(initial_nodes)?;
let next_node = lookup.get_next_node(10);
assert!(next_node.is_none());
Ok(())
}
#[test]
fn test_disjoint_path_add_query_results() -> Result<()> {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 2, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
lookup.initialize_paths(initial_nodes)?;
let query_results = vec![
create_test_dht_node("peer3", [4u8; 32]),
create_test_dht_node("peer4", [5u8; 32]),
];
let initial_queue_size = lookup.path_states[0].to_query.len();
lookup.add_query_results(0, query_results);
assert!(lookup.path_states[0].to_query.len() >= initial_queue_size);
Ok(())
}
#[test]
fn test_disjoint_path_verify_disjointness() -> Result<()> {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 2, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
lookup.initialize_paths(initial_nodes)?;
assert!(lookup.verify_disjointness());
Ok(())
}
#[test]
fn test_disjoint_path_count_shared_nodes() -> Result<()> {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 2, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
lookup.initialize_paths(initial_nodes)?;
let shared_count = lookup.count_shared_nodes(0, 1);
assert_eq!(shared_count, 0);
Ok(())
}
#[test]
fn test_disjoint_path_completion() {
let target = create_test_key([1u8; 32]);
let lookup = DisjointPathLookup::new(target, 2, 1);
assert!(!lookup.is_complete());
}
#[test]
fn test_disjoint_path_get_results() -> Result<()> {
let target = create_test_key([1u8; 32]);
let mut lookup = DisjointPathLookup::new(target, 2, 1);
let initial_nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
lookup.initialize_paths(initial_nodes)?;
lookup.path_states[0].results.push(create_test_dht_node("result1", [10u8; 32]));
lookup.path_states[1].results.push(create_test_dht_node("result2", [11u8; 32]));
let results = lookup.get_results();
assert_eq!(results.len(), 2);
Ok(())
}
#[test]
fn test_sibling_list_creation() {
let local_id = create_test_key([1u8; 32]);
let sibling_list = SiblingList::new(local_id.clone(), 16);
assert_eq!(sibling_list.local_id, local_id);
assert_eq!(sibling_list.max_size, 16);
assert!(sibling_list.siblings.is_empty());
}
#[test]
fn test_sibling_list_add_node() {
let local_id = create_test_key([1u8; 32]);
let mut sibling_list = SiblingList::new(local_id, 16);
let node = create_test_dht_node("peer1", [2u8; 32]);
sibling_list.add_node(node.clone());
assert_eq!(sibling_list.siblings.len(), 1);
assert_eq!(sibling_list.siblings[0].peer_id, node.peer_id);
}
#[test]
fn test_sibling_list_size_limit() {
let local_id = create_test_key([1u8; 32]);
let mut sibling_list = SiblingList::new(local_id, 2);
sibling_list.add_node(create_test_dht_node("peer1", [2u8; 32]));
sibling_list.add_node(create_test_dht_node("peer2", [3u8; 32]));
sibling_list.add_node(create_test_dht_node("peer3", [4u8; 32]));
assert_eq!(sibling_list.siblings.len(), 2);
}
#[test]
fn test_sibling_list_get_closest_siblings() {
let local_id = create_test_key([1u8; 32]);
let mut sibling_list = SiblingList::new(local_id, 16);
sibling_list.add_node(create_test_dht_node("peer1", [2u8; 32]));
sibling_list.add_node(create_test_dht_node("peer2", [3u8; 32]));
sibling_list.add_node(create_test_dht_node("peer3", [4u8; 32]));
let closest = sibling_list.get_closest_siblings(2);
assert_eq!(closest.len(), 2);
}
#[test]
fn test_sibling_list_verify_routing_decision() {
let local_id = create_test_key([1u8; 32]);
let sibling_list = SiblingList::new(local_id, 16);
let target = create_test_key([10u8; 32]);
let proposed_nodes = vec![create_test_dht_node("peer1", [11u8; 32])];
let is_valid = sibling_list.verify_routing_decision(&target, &proposed_nodes);
assert!(is_valid);
}
#[test]
fn test_security_bucket_creation() {
let security_bucket = SecurityBucket::new(8);
assert_eq!(security_bucket.max_size, 8);
assert!(security_bucket.trusted_nodes.is_empty());
assert!(security_bucket.backup_routes.is_empty());
}
#[test]
fn test_security_bucket_add_trusted_node() {
let mut security_bucket = SecurityBucket::new(8);
let node = create_test_dht_node("peer1", [2u8; 32]);
security_bucket.add_trusted_node(node.clone());
assert_eq!(security_bucket.trusted_nodes.len(), 1);
assert_eq!(security_bucket.trusted_nodes[0].peer_id, node.peer_id);
}
#[test]
fn test_security_bucket_size_limit() {
let mut security_bucket = SecurityBucket::new(2);
security_bucket.add_trusted_node(create_test_dht_node("peer1", [2u8; 32]));
security_bucket.add_trusted_node(create_test_dht_node("peer2", [3u8; 32]));
security_bucket.add_trusted_node(create_test_dht_node("peer3", [4u8; 32]));
assert_eq!(security_bucket.trusted_nodes.len(), 2);
}
#[test]
fn test_security_bucket_add_backup_route() {
let mut security_bucket = SecurityBucket::new(8);
let route = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
security_bucket.add_backup_route(route.clone());
assert_eq!(security_bucket.backup_routes.len(), 1);
assert_eq!(security_bucket.backup_routes[0].len(), 2);
}
#[test]
fn test_security_bucket_backup_route_limit() {
let mut security_bucket = SecurityBucket::new(8);
for i in 0..7 {
let route = vec![create_test_dht_node(&format!("peer{}", i), [i as u8; 32])];
security_bucket.add_backup_route(route);
}
assert_eq!(security_bucket.backup_routes.len(), 5);
}
#[test]
fn test_skademlia_creation() {
let config = SKademliaConfig::default();
let skademlia = SKademlia::new(config);
assert!(skademlia.sibling_lists.is_empty());
assert!(skademlia.security_buckets.is_empty());
assert!(skademlia.active_lookups.is_empty());
assert!(skademlia.pending_challenges.is_empty());
}
#[test]
fn test_skademlia_update_sibling_list() {
let config = SKademliaConfig::default();
let mut skademlia = SKademlia::new(config);
let key = create_test_key([1u8; 32]);
let nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
skademlia.update_sibling_list(key.clone(), nodes);
assert!(skademlia.sibling_lists.contains_key(&key));
assert_eq!(skademlia.sibling_lists[&key].siblings.len(), 2);
}
#[test]
fn test_skademlia_get_security_bucket() {
let config = SKademliaConfig::default();
let mut skademlia = SKademlia::new(config);
let key = create_test_key([1u8; 32]);
let bucket = skademlia.get_security_bucket(&key);
assert_eq!(bucket.max_size, 8);
let bucket2 = skademlia.get_security_bucket(&key);
assert_eq!(bucket2.max_size, 8);
}
#[test]
fn test_skademlia_create_distance_challenge() {
let config = SKademliaConfig::default();
let mut skademlia = SKademlia::new(config);
let target = "test_peer".to_string();
let key = create_test_key([1u8; 32]);
let challenge = skademlia.create_distance_challenge(&target, &key);
assert_eq!(challenge.challenger, target);
assert_eq!(challenge.target_key, key);
assert!(skademlia.pending_challenges.contains_key(&target));
}
#[test]
fn test_skademlia_create_enhanced_distance_challenge() {
let config = SKademliaConfig::default();
let mut skademlia = SKademlia::new(config);
let target = "test_peer".to_string();
let key = create_test_key([1u8; 32]);
let witness_nodes = vec!["witness1".to_string(), "witness2".to_string()];
let challenge = skademlia.create_enhanced_distance_challenge(&target, &key, witness_nodes.clone());
assert_eq!(challenge.challenger, target);
assert_eq!(challenge.target_key, key);
assert_eq!(challenge.witness_nodes, witness_nodes);
assert_eq!(challenge.challenge_round, 1);
assert_eq!(challenge.max_rounds, 3);
}
#[test]
fn test_skademlia_create_adaptive_distance_challenge() {
let config = SKademliaConfig::default();
let mut skademlia = SKademlia::new(config);
let target = "test_peer".to_string();
let key = create_test_key([1u8; 32]);
let normal_challenge = skademlia.create_adaptive_distance_challenge(&target, &key, false);
assert_eq!(normal_challenge.witness_nodes.len(), 3);
assert_eq!(normal_challenge.max_rounds, 3);
let attack_challenge = skademlia.create_adaptive_distance_challenge(&target, &key, true);
assert_eq!(attack_challenge.witness_nodes.len(), 7);
assert_eq!(attack_challenge.max_rounds, 5);
}
#[test]
fn test_skademlia_select_secure_nodes() {
let config = SKademliaConfig::default();
let skademlia = SKademlia::new(config);
let candidates = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
create_test_dht_node("peer3", [4u8; 32]),
];
let target = create_test_key([1u8; 32]);
let selected = skademlia.select_secure_nodes(&candidates, &target, 2);
assert_eq!(selected.len(), 2);
}
#[test]
fn test_skademlia_cleanup_expired() {
let config = SKademliaConfig::default();
let mut skademlia = SKademlia::new(config);
let key = create_test_key([1u8; 32]);
let target = "test_peer".to_string();
skademlia.create_distance_challenge(&target, &key);
assert!(!skademlia.pending_challenges.is_empty());
skademlia.cleanup_expired();
assert!(!skademlia.pending_challenges.is_empty());
}
#[test]
fn test_distance_challenge_creation() {
let challenger = "test_peer".to_string();
let target_key = create_test_key([1u8; 32]);
let expected_distance = target_key.distance(&Key::new(challenger.as_bytes()));
let challenge = DistanceChallenge {
challenger: challenger.clone(),
target_key: target_key.clone(),
expected_distance: expected_distance.clone(),
nonce: [1u8; 32],
timestamp: SystemTime::now(),
};
assert_eq!(challenge.challenger, challenger);
assert_eq!(challenge.target_key, target_key);
assert_eq!(challenge.expected_distance, expected_distance);
assert_eq!(challenge.nonce, [1u8; 32]);
}
#[test]
fn test_distance_measurement() {
let witness = "witness_peer".to_string();
let distance = create_test_key([5u8; 32]);
let confidence = 0.8;
let response_time = Duration::from_millis(100);
let measurement = DistanceMeasurement {
witness: witness.clone(),
distance: distance.clone(),
confidence,
response_time,
};
assert_eq!(measurement.witness, witness);
assert_eq!(measurement.distance, distance);
assert_eq!(measurement.confidence, confidence);
assert_eq!(measurement.response_time, response_time);
}
#[test]
fn test_consistency_report() {
let nodes_checked = 10;
let inconsistencies = 2;
let suspicious_nodes = vec!["peer1".to_string(), "peer2".to_string()];
let validated_at = Instant::now();
let report = ConsistencyReport {
nodes_checked,
inconsistencies,
suspicious_nodes: suspicious_nodes.clone(),
validated_at,
};
assert_eq!(report.nodes_checked, nodes_checked);
assert_eq!(report.inconsistencies, inconsistencies);
assert_eq!(report.suspicious_nodes, suspicious_nodes);
}
#[tokio::test]
async fn test_skademlia_validate_routing_consistency() -> Result<()> {
let config = SKademliaConfig::default();
let skademlia = SKademlia::new(config);
let nodes = vec![
create_test_dht_node("peer1", [2u8; 32]),
create_test_dht_node("peer2", [3u8; 32]),
];
let report = skademlia.validate_routing_consistency(&nodes).await?;
assert_eq!(report.nodes_checked, 2);
assert!(report.inconsistencies <= 2);
assert!(report.suspicious_nodes.len() <= 2);
Ok(())
}
#[test]
fn test_distance_proof_validation_components() {
let challenger = "test_peer".to_string();
let target_key = create_test_key([1u8; 32]);
let expected_distance = target_key.distance(&Key::new(challenger.as_bytes()));
let challenge = DistanceChallenge {
challenger: challenger.clone(),
target_key: target_key.clone(),
expected_distance: expected_distance.clone(),
nonce: [1u8; 32],
timestamp: SystemTime::now(),
};
let proof = DistanceProof {
challenge,
proof_nodes: vec!["proof1".to_string(), "proof2".to_string()],
signatures: vec![vec![1u8; 64], vec![2u8; 64]],
response_time: Duration::from_millis(50),
};
assert_eq!(proof.proof_nodes.len(), 2);
assert_eq!(proof.signatures.len(), 2);
assert_eq!(proof.response_time, Duration::from_millis(50));
}
}