1use libp2p::PeerId;
10use parking_lot::RwLock;
11use serde::Serialize;
12use std::collections::{HashMap, HashSet};
13use std::time::{Duration, Instant};
14use tracing::{debug, info, warn};
15
16#[derive(Debug, Clone)]
18pub struct ConnectionLimitsConfig {
19 pub max_connections: usize,
21 pub max_inbound: usize,
23 pub max_outbound: usize,
25 pub reserved_slots: usize,
27 pub idle_timeout: Duration,
29 pub min_score_threshold: u8,
31}
32
33impl Default for ConnectionLimitsConfig {
34 fn default() -> Self {
35 Self {
36 max_connections: 256,
37 max_inbound: 128,
38 max_outbound: 128,
39 reserved_slots: 8,
40 idle_timeout: Duration::from_secs(300),
41 min_score_threshold: 30,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum ConnectionDirection {
49 Inbound,
50 Outbound,
51}
52
53#[derive(Debug, Clone)]
55struct ConnectionInfo {
56 peer_id: PeerId,
58 direction: ConnectionDirection,
60 established_at: Instant,
62 last_activity: Instant,
64 score: u8,
66 reserved: bool,
68 messages_sent: u64,
70 messages_received: u64,
72 avg_latency_ms: Option<u64>,
74}
75
76impl ConnectionInfo {
77 fn new(peer_id: PeerId, direction: ConnectionDirection) -> Self {
78 let now = Instant::now();
79 Self {
80 peer_id,
81 direction,
82 established_at: now,
83 last_activity: now,
84 score: 50, reserved: false,
86 messages_sent: 0,
87 messages_received: 0,
88 avg_latency_ms: None,
89 }
90 }
91
92 fn is_idle(&self, timeout: Duration) -> bool {
93 self.last_activity.elapsed() > timeout
94 }
95
96 fn touch(&mut self) {
97 self.last_activity = Instant::now();
98 }
99
100 fn calculate_value(&self) -> u64 {
102 let age_secs = self.established_at.elapsed().as_secs();
103 let activity = self.messages_sent + self.messages_received;
104
105 let base_value = self.score as u64 * 10;
107 let activity_rate = (activity * 60)
108 .checked_div(age_secs)
109 .unwrap_or(activity * 60); let latency_bonus = match self.avg_latency_ms {
111 Some(lat) if lat < 50 => 20,
112 Some(lat) if lat < 100 => 10,
113 Some(lat) if lat < 200 => 5,
114 _ => 0,
115 };
116
117 base_value + activity_rate + latency_bonus
118 }
119}
120
121pub struct ConnectionManager {
123 config: ConnectionLimitsConfig,
125 connections: RwLock<HashMap<PeerId, ConnectionInfo>>,
127 reserved_peers: RwLock<HashSet<PeerId>>,
129 banned_peers: RwLock<HashSet<PeerId>>,
131}
132
133impl ConnectionManager {
134 pub fn new(config: ConnectionLimitsConfig) -> Self {
136 Self {
137 config,
138 connections: RwLock::new(HashMap::new()),
139 reserved_peers: RwLock::new(HashSet::new()),
140 banned_peers: RwLock::new(HashSet::new()),
141 }
142 }
143
144 pub fn should_accept(&self, peer_id: &PeerId, direction: ConnectionDirection) -> bool {
146 if self.banned_peers.read().contains(peer_id) {
148 debug!("Rejecting banned peer: {}", peer_id);
149 return false;
150 }
151
152 if self.reserved_peers.read().contains(peer_id) {
154 let reserved_count = self
155 .connections
156 .read()
157 .values()
158 .filter(|c| c.reserved)
159 .count();
160 if reserved_count < self.config.reserved_slots {
161 return true;
162 }
163 }
164
165 let connections = self.connections.read();
166
167 if connections.len() >= self.config.max_connections {
169 debug!(
170 "At max connections ({}), rejecting {}",
171 self.config.max_connections, peer_id
172 );
173 return false;
174 }
175
176 let (inbound, outbound) =
178 connections
179 .values()
180 .fold((0, 0), |(i, o), c| match c.direction {
181 ConnectionDirection::Inbound => (i + 1, o),
182 ConnectionDirection::Outbound => (i, o + 1),
183 });
184
185 match direction {
186 ConnectionDirection::Inbound => {
187 if inbound >= self.config.max_inbound {
188 debug!(
189 "At max inbound ({}), rejecting {}",
190 self.config.max_inbound, peer_id
191 );
192 return false;
193 }
194 }
195 ConnectionDirection::Outbound => {
196 if outbound >= self.config.max_outbound {
197 debug!(
198 "At max outbound ({}), rejecting {}",
199 self.config.max_outbound, peer_id
200 );
201 return false;
202 }
203 }
204 }
205
206 true
207 }
208
209 pub fn connection_established(&self, peer_id: PeerId, direction: ConnectionDirection) {
211 let is_reserved = self.reserved_peers.read().contains(&peer_id);
212
213 let mut connections = self.connections.write();
214 let mut info = ConnectionInfo::new(peer_id, direction);
215 info.reserved = is_reserved;
216
217 connections.insert(peer_id, info);
218 info!("Connection established: {} ({:?})", peer_id, direction);
219 }
220
221 pub fn connection_closed(&self, peer_id: &PeerId) {
223 let mut connections = self.connections.write();
224 if connections.remove(peer_id).is_some() {
225 debug!("Connection closed: {}", peer_id);
226 }
227 }
228
229 pub fn record_activity(&self, peer_id: &PeerId, sent: bool) {
231 let mut connections = self.connections.write();
232 if let Some(info) = connections.get_mut(peer_id) {
233 info.touch();
234 if sent {
235 info.messages_sent += 1;
236 } else {
237 info.messages_received += 1;
238 }
239 }
240 }
241
242 pub fn update_score(&self, peer_id: &PeerId, delta: i16) {
244 let mut connections = self.connections.write();
245 if let Some(info) = connections.get_mut(peer_id) {
246 let new_score = (info.score as i16 + delta).clamp(0, 100) as u8;
247 info.score = new_score;
248 }
249 }
250
251 pub fn update_latency(&self, peer_id: &PeerId, latency_ms: u64) {
253 let mut connections = self.connections.write();
254 if let Some(info) = connections.get_mut(peer_id) {
255 info.avg_latency_ms = Some(latency_ms);
256 info.touch();
257 }
258 }
259
260 pub fn add_reserved(&self, peer_id: PeerId) {
262 self.reserved_peers.write().insert(peer_id);
263
264 if let Some(info) = self.connections.write().get_mut(&peer_id) {
266 info.reserved = true;
267 }
268
269 info!("Added reserved peer: {}", peer_id);
270 }
271
272 pub fn remove_reserved(&self, peer_id: &PeerId) {
274 self.reserved_peers.write().remove(peer_id);
275
276 if let Some(info) = self.connections.write().get_mut(peer_id) {
278 info.reserved = false;
279 }
280
281 debug!("Removed reserved peer: {}", peer_id);
282 }
283
284 pub fn ban_peer(&self, peer_id: PeerId) {
286 self.banned_peers.write().insert(peer_id);
287 self.reserved_peers.write().remove(&peer_id);
288 warn!("Banned peer: {}", peer_id);
289 }
290
291 pub fn unban_peer(&self, peer_id: &PeerId) {
293 self.banned_peers.write().remove(peer_id);
294 info!("Unbanned peer: {}", peer_id);
295 }
296
297 pub fn is_banned(&self, peer_id: &PeerId) -> bool {
299 self.banned_peers.read().contains(peer_id)
300 }
301
302 pub fn get_prune_candidates(&self, count: usize) -> Vec<PeerId> {
304 let connections = self.connections.read();
305
306 let mut candidates: Vec<_> = connections
308 .values()
309 .filter(|c| !c.reserved && c.score < self.config.min_score_threshold)
310 .map(|c| (c.peer_id, c.calculate_value()))
311 .collect();
312
313 candidates.sort_by_key(|(_, value)| *value);
315
316 candidates
317 .into_iter()
318 .take(count)
319 .map(|(peer_id, _)| peer_id)
320 .collect()
321 }
322
323 pub fn get_idle_connections(&self) -> Vec<PeerId> {
325 let connections = self.connections.read();
326 let timeout = self.config.idle_timeout;
327
328 connections
329 .values()
330 .filter(|c| !c.reserved && c.is_idle(timeout))
331 .map(|c| c.peer_id)
332 .collect()
333 }
334
335 pub fn prune_to_limit(&self) -> Vec<PeerId> {
339 let connections = self.connections.read();
340 let current = connections.len();
341
342 if current <= self.config.max_connections {
343 return vec![];
344 }
345
346 let to_prune = current - self.config.max_connections;
347 drop(connections);
348
349 let candidates = self.get_prune_candidates(to_prune);
350 info!(
351 "Pruning {} connections to stay within limit",
352 candidates.len()
353 );
354 candidates
355 }
356
357 pub fn connected_peers(&self) -> Vec<PeerId> {
359 self.connections.read().keys().cloned().collect()
360 }
361
362 pub fn connection_count(&self) -> usize {
364 self.connections.read().len()
365 }
366
367 pub fn is_connected(&self, peer_id: &PeerId) -> bool {
369 self.connections.read().contains_key(peer_id)
370 }
371
372 pub fn stats(&self) -> ConnectionManagerStats {
374 let connections = self.connections.read();
375
376 let (inbound, outbound) =
377 connections
378 .values()
379 .fold((0, 0), |(i, o), c| match c.direction {
380 ConnectionDirection::Inbound => (i + 1, o),
381 ConnectionDirection::Outbound => (i, o + 1),
382 });
383
384 let reserved = connections.values().filter(|c| c.reserved).count();
385
386 let avg_score = if connections.is_empty() {
387 0
388 } else {
389 connections.values().map(|c| c.score as u64).sum::<u64>() / connections.len() as u64
390 };
391
392 ConnectionManagerStats {
393 total_connections: connections.len(),
394 max_connections: self.config.max_connections,
395 inbound_connections: inbound,
396 outbound_connections: outbound,
397 reserved_connections: reserved,
398 banned_peers: self.banned_peers.read().len(),
399 average_score: avg_score as u8,
400 }
401 }
402}
403
404impl Default for ConnectionManager {
405 fn default() -> Self {
406 Self::new(ConnectionLimitsConfig::default())
407 }
408}
409
410#[derive(Debug, Clone, Serialize)]
412pub struct ConnectionManagerStats {
413 pub total_connections: usize,
415 pub max_connections: usize,
417 pub inbound_connections: usize,
419 pub outbound_connections: usize,
421 pub reserved_connections: usize,
423 pub banned_peers: usize,
425 pub average_score: u8,
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 fn random_peer() -> PeerId {
434 PeerId::random()
435 }
436
437 #[test]
438 fn test_connection_manager_basic() {
439 let manager = ConnectionManager::default();
440 let peer1 = random_peer();
441 let peer2 = random_peer();
442
443 assert!(manager.should_accept(&peer1, ConnectionDirection::Inbound));
444
445 manager.connection_established(peer1, ConnectionDirection::Inbound);
446 assert!(manager.is_connected(&peer1));
447 assert_eq!(manager.connection_count(), 1);
448
449 manager.connection_established(peer2, ConnectionDirection::Outbound);
450 assert_eq!(manager.connection_count(), 2);
451
452 manager.connection_closed(&peer1);
453 assert!(!manager.is_connected(&peer1));
454 assert_eq!(manager.connection_count(), 1);
455 }
456
457 #[test]
458 fn test_connection_limits() {
459 let config = ConnectionLimitsConfig {
460 max_connections: 3,
461 max_inbound: 2,
462 max_outbound: 2,
463 ..Default::default()
464 };
465 let manager = ConnectionManager::new(config);
466
467 let peer1 = random_peer();
469 let peer2 = random_peer();
470 manager.connection_established(peer1, ConnectionDirection::Inbound);
471 manager.connection_established(peer2, ConnectionDirection::Inbound);
472
473 let peer3 = random_peer();
475 assert!(!manager.should_accept(&peer3, ConnectionDirection::Inbound));
476
477 assert!(manager.should_accept(&peer3, ConnectionDirection::Outbound));
479 manager.connection_established(peer3, ConnectionDirection::Outbound);
480
481 let peer4 = random_peer();
483 assert!(!manager.should_accept(&peer4, ConnectionDirection::Inbound));
484 assert!(!manager.should_accept(&peer4, ConnectionDirection::Outbound));
485 }
486
487 #[test]
488 fn test_reserved_peers() {
489 let config = ConnectionLimitsConfig {
490 max_connections: 2,
491 reserved_slots: 1,
492 ..Default::default()
493 };
494 let manager = ConnectionManager::new(config);
495
496 let reserved_peer = random_peer();
497 manager.add_reserved(reserved_peer);
498
499 let peer1 = random_peer();
500 let peer2 = random_peer();
501 manager.connection_established(peer1, ConnectionDirection::Inbound);
502 manager.connection_established(peer2, ConnectionDirection::Outbound);
503
504 assert!(manager.should_accept(&reserved_peer, ConnectionDirection::Inbound));
506 }
507
508 #[test]
509 fn test_banned_peers() {
510 let manager = ConnectionManager::default();
511 let peer = random_peer();
512
513 assert!(manager.should_accept(&peer, ConnectionDirection::Inbound));
514
515 manager.ban_peer(peer);
516 assert!(manager.is_banned(&peer));
517 assert!(!manager.should_accept(&peer, ConnectionDirection::Inbound));
518
519 manager.unban_peer(&peer);
520 assert!(!manager.is_banned(&peer));
521 assert!(manager.should_accept(&peer, ConnectionDirection::Inbound));
522 }
523
524 #[test]
525 fn test_activity_tracking() {
526 let manager = ConnectionManager::default();
527 let peer = random_peer();
528
529 manager.connection_established(peer, ConnectionDirection::Outbound);
530
531 manager.record_activity(&peer, true); manager.record_activity(&peer, false); manager.record_activity(&peer, true); let stats = manager.stats();
537 assert_eq!(stats.total_connections, 1);
538 }
539
540 #[test]
541 fn test_score_update() {
542 let manager = ConnectionManager::default();
543 let peer = random_peer();
544
545 manager.connection_established(peer, ConnectionDirection::Inbound);
546 manager.update_score(&peer, 20); manager.update_score(&peer, -40); manager.update_score(&peer, -100); }
552
553 #[test]
554 fn test_prune_candidates() {
555 let config = ConnectionLimitsConfig {
556 min_score_threshold: 50,
557 ..Default::default()
558 };
559 let manager = ConnectionManager::new(config);
560
561 let high_score = random_peer();
563 let low_score1 = random_peer();
564 let low_score2 = random_peer();
565 let reserved = random_peer();
566
567 manager.connection_established(high_score, ConnectionDirection::Inbound);
568 manager.connection_established(low_score1, ConnectionDirection::Inbound);
569 manager.connection_established(low_score2, ConnectionDirection::Outbound);
570 manager.add_reserved(reserved);
571 manager.connection_established(reserved, ConnectionDirection::Inbound);
572
573 manager.update_score(&high_score, 30); manager.update_score(&low_score1, -30); manager.update_score(&low_score2, -25); let candidates = manager.get_prune_candidates(2);
580
581 assert!(!candidates.contains(&reserved));
583 assert!(!candidates.contains(&high_score));
584 assert!(candidates.len() <= 2);
585 }
586
587 #[test]
588 fn test_idle_connections() {
589 let config = ConnectionLimitsConfig {
590 idle_timeout: Duration::from_millis(50),
591 ..Default::default()
592 };
593 let manager = ConnectionManager::new(config);
594
595 let peer = random_peer();
596 manager.connection_established(peer, ConnectionDirection::Inbound);
597
598 assert!(manager.get_idle_connections().is_empty());
600
601 std::thread::sleep(Duration::from_millis(100));
603
604 let idle = manager.get_idle_connections();
606 assert_eq!(idle.len(), 1);
607 assert_eq!(idle[0], peer);
608 }
609
610 #[test]
611 fn test_stats() {
612 let manager = ConnectionManager::default();
613
614 let peer1 = random_peer();
615 let peer2 = random_peer();
616 let reserved = random_peer();
617
618 manager.connection_established(peer1, ConnectionDirection::Inbound);
619 manager.connection_established(peer2, ConnectionDirection::Outbound);
620 manager.add_reserved(reserved);
621 manager.connection_established(reserved, ConnectionDirection::Inbound);
622
623 let banned = random_peer();
624 manager.ban_peer(banned);
625
626 let stats = manager.stats();
627 assert_eq!(stats.total_connections, 3);
628 assert_eq!(stats.inbound_connections, 2);
629 assert_eq!(stats.outbound_connections, 1);
630 assert_eq!(stats.reserved_connections, 1);
631 assert_eq!(stats.banned_peers, 1);
632 }
633}