1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Ban list merging utilities
//!
//! Provides functions to merge ban lists from multiple peers,
//! with conflict resolution and validation.
use crate::network::protocol::{BanEntry, BanListMessage, NetworkAddress};
use std::collections::HashMap;
/// Merge multiple ban lists into a single list
///
/// Conflict resolution:
/// - If same address appears in multiple lists, use longest ban duration
/// - If permanent ban (u64::MAX) exists, keep it
/// - Combine reasons from all sources
pub fn merge_ban_lists(ban_lists: Vec<&BanListMessage>) -> Vec<BanEntry> {
let mut merged: HashMap<NetworkAddress, BanEntry> = HashMap::new();
for ban_list in ban_lists {
if !ban_list.is_full {
continue; // Skip hash-only responses
}
for entry in &ban_list.ban_entries {
let addr = entry.addr.clone();
match merged.get_mut(&addr) {
Some(existing) => {
// Conflict resolution: use longest ban
if entry.unban_timestamp == u64::MAX {
// Permanent ban always wins
existing.unban_timestamp = u64::MAX;
} else if existing.unban_timestamp != u64::MAX {
// Both temporary - use longer one
if entry.unban_timestamp > existing.unban_timestamp {
existing.unban_timestamp = entry.unban_timestamp;
}
}
// Merge reasons
if let Some(ref reason) = entry.reason {
existing.reason = existing
.reason
.as_ref()
.map(|r| format!("{r}; {reason}"))
.or_else(|| Some(reason.clone()));
}
}
None => {
merged.insert(addr, entry.clone());
}
}
}
}
// Convert to sorted vector
let mut result: Vec<BanEntry> = merged.into_values().collect();
result.sort_by(|a, b| {
// Sort by address for deterministic output
a.addr
.ip
.cmp(&b.addr.ip)
.then_with(|| a.addr.port.cmp(&b.addr.port))
});
result
}
/// Validate a ban list entry
///
/// Returns true if entry is valid and should be applied
pub fn validate_ban_entry(entry: &BanEntry) -> bool {
// Check if ban is expired
if entry.unban_timestamp != u64::MAX {
let now = crate::utils::current_timestamp();
if now >= entry.unban_timestamp {
return false; // Ban already expired
}
}
// Additional validation could include:
// - IP address format validation
// - Reason length limits
// - etc.
true
}
/// Calculate hash of ban list (for verification)
pub fn calculate_ban_list_hash(entries: &[BanEntry]) -> [u8; 32] {
use sha2::{Digest, Sha256};
// Sort entries for deterministic hashing
let mut sorted = entries.to_vec();
sorted.sort_by(|a, b| {
a.addr
.ip
.cmp(&b.addr.ip)
.then_with(|| a.addr.port.cmp(&b.addr.port))
});
// Serialize and hash
let serialized = bincode::serialize(&sorted).unwrap_or_default();
let hash = Sha256::digest(&serialized);
let mut result = [0u8; 32];
result.copy_from_slice(&hash);
result
}
/// Verify ban list hash matches entries
pub fn verify_ban_list_hash(entries: &[BanEntry], expected_hash: &[u8; 32]) -> bool {
let calculated = calculate_ban_list_hash(entries);
calculated == *expected_hash
}