1use dashmap::DashMap;
41use libp2p::PeerId;
42use serde::{Deserialize, Serialize};
43use std::collections::HashMap;
44use std::net::IpAddr;
45use std::sync::Arc;
46
47#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
49pub struct GeoLocation {
50 pub latitude: f64,
52 pub longitude: f64,
54}
55
56impl GeoLocation {
57 pub fn new(latitude: f64, longitude: f64) -> Self {
68 assert!(
69 (-90.0..=90.0).contains(&latitude),
70 "Latitude must be between -90 and 90"
71 );
72 assert!(
73 (-180.0..=180.0).contains(&longitude),
74 "Longitude must be between -180 and 180"
75 );
76 Self {
77 latitude,
78 longitude,
79 }
80 }
81
82 pub fn distance_to(&self, other: &GeoLocation) -> f64 {
85 const EARTH_RADIUS_KM: f64 = 6371.0;
86
87 let lat1 = self.latitude.to_radians();
88 let lat2 = other.latitude.to_radians();
89 let delta_lat = (other.latitude - self.latitude).to_radians();
90 let delta_lon = (other.longitude - self.longitude).to_radians();
91
92 let a = (delta_lat / 2.0).sin().powi(2)
93 + lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
94 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
95
96 EARTH_RADIUS_KM * c
97 }
98
99 pub fn estimate_latency_ms(&self, other: &GeoLocation) -> f64 {
104 const BASE_LATENCY_MS: f64 = 10.0; const MS_PER_1000_KM: f64 = 5.0; let distance = self.distance_to(other);
108 BASE_LATENCY_MS + (distance / 1000.0) * MS_PER_1000_KM
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
114pub enum GeoRegion {
115 NorthAmerica,
117 SouthAmerica,
119 Europe,
121 Asia,
123 Africa,
125 Oceania,
127 Unknown,
129}
130
131impl GeoRegion {
132 pub fn from_location(location: &GeoLocation) -> Self {
134 let lat = location.latitude;
135 let lon = location.longitude;
136
137 if (-170.0..=-30.0).contains(&lon) {
139 if lat >= 15.0 {
141 GeoRegion::NorthAmerica
142 } else {
143 GeoRegion::SouthAmerica
144 }
145 } else if (-30.0..=60.0).contains(&lon) {
146 if lat >= 35.0 {
148 GeoRegion::Europe
149 } else {
150 GeoRegion::Africa
151 }
152 } else if (60.0..=150.0).contains(&lon) {
153 if lat >= -10.0 {
155 GeoRegion::Asia
156 } else {
157 GeoRegion::Oceania
158 }
159 } else {
160 GeoRegion::Unknown
161 }
162 }
163
164 #[allow(dead_code)]
166 pub fn representative_location(&self) -> GeoLocation {
167 match self {
168 GeoRegion::NorthAmerica => GeoLocation::new(39.8283, -98.5795), GeoRegion::SouthAmerica => GeoLocation::new(-14.2350, -51.9253), GeoRegion::Europe => GeoLocation::new(50.0, 10.0), GeoRegion::Asia => GeoLocation::new(34.0, 100.0), GeoRegion::Africa => GeoLocation::new(1.0, 18.0), GeoRegion::Oceania => GeoLocation::new(-25.2744, 133.7751), GeoRegion::Unknown => GeoLocation::new(0.0, 0.0),
175 }
176 }
177}
178
179#[derive(Debug, Clone)]
181pub struct GeoPeer {
182 pub peer_id: PeerId,
184 pub location: GeoLocation,
186 pub region: GeoRegion,
188 pub distance_km: Option<f64>,
190}
191
192impl GeoPeer {
193 pub fn new(peer_id: PeerId, location: GeoLocation) -> Self {
195 let region = GeoRegion::from_location(&location);
196 Self {
197 peer_id,
198 location,
199 region,
200 distance_km: None,
201 }
202 }
203
204 pub fn with_distance(mut self, distance_km: f64) -> Self {
206 self.distance_km = Some(distance_km);
207 self
208 }
209}
210
211#[derive(Debug, Clone)]
213pub struct GeoRouterConfig {
214 pub nearby_threshold_km: f64,
216 pub same_region_bonus_km: f64,
218 pub enable_region_clustering: bool,
220 pub max_peers_per_region: usize,
222}
223
224impl Default for GeoRouterConfig {
225 fn default() -> Self {
226 Self {
227 nearby_threshold_km: 500.0, same_region_bonus_km: 1000.0, enable_region_clustering: true,
230 max_peers_per_region: 100,
231 }
232 }
233}
234
235impl GeoRouterConfig {
236 pub fn low_latency() -> Self {
238 Self {
239 nearby_threshold_km: 1000.0,
240 same_region_bonus_km: 2000.0,
241 enable_region_clustering: true,
242 max_peers_per_region: 50,
243 }
244 }
245
246 pub fn global() -> Self {
248 Self {
249 nearby_threshold_km: 5000.0,
250 same_region_bonus_km: 500.0,
251 enable_region_clustering: true,
252 max_peers_per_region: 200,
253 }
254 }
255
256 pub fn regional() -> Self {
258 Self {
259 nearby_threshold_km: 200.0,
260 same_region_bonus_km: 3000.0,
261 enable_region_clustering: true,
262 max_peers_per_region: 150,
263 }
264 }
265}
266
267pub struct GeoRouter {
269 config: GeoRouterConfig,
271 peer_locations: Arc<DashMap<PeerId, GeoLocation>>,
273 region_peers: Arc<DashMap<GeoRegion, Vec<PeerId>>>,
275 stats: Arc<parking_lot::RwLock<GeoRouterStats>>,
277}
278
279#[derive(Debug, Clone, Default)]
281pub struct GeoRouterStats {
282 pub total_peers: usize,
284 pub peers_per_region: HashMap<GeoRegion, usize>,
286 pub proximity_queries: u64,
288 pub region_lookups: u64,
290}
291
292impl GeoRouter {
293 pub fn new(config: GeoRouterConfig) -> Self {
295 Self {
296 config,
297 peer_locations: Arc::new(DashMap::new()),
298 region_peers: Arc::new(DashMap::new()),
299 stats: Arc::new(parking_lot::RwLock::new(GeoRouterStats::default())),
300 }
301 }
302
303 pub fn update_peer_location(&self, peer_id: PeerId, location: GeoLocation) {
305 if let Some(old_location) = self.peer_locations.get(&peer_id) {
307 let old_region = GeoRegion::from_location(&old_location);
308 if let Some(mut peers) = self.region_peers.get_mut(&old_region) {
309 peers.retain(|p| p != &peer_id);
310 }
311 }
312
313 self.peer_locations.insert(peer_id, location);
315
316 if self.config.enable_region_clustering {
318 let region = GeoRegion::from_location(&location);
319 self.region_peers.entry(region).or_default().push(peer_id);
320
321 if let Some(mut peers) = self.region_peers.get_mut(®ion) {
323 if peers.len() > self.config.max_peers_per_region {
324 peers.truncate(self.config.max_peers_per_region);
325 }
326 }
327 }
328
329 let mut stats = self.stats.write();
331 stats.total_peers = self.peer_locations.len();
332 stats.peers_per_region.clear();
333 for entry in self.region_peers.iter() {
334 stats
335 .peers_per_region
336 .insert(*entry.key(), entry.value().len());
337 }
338 }
339
340 pub fn remove_peer(&self, peer_id: &PeerId) {
342 if let Some((_, location)) = self.peer_locations.remove(peer_id) {
343 let region = GeoRegion::from_location(&location);
344 if let Some(mut peers) = self.region_peers.get_mut(®ion) {
345 peers.retain(|p| p != peer_id);
346 }
347
348 let mut stats = self.stats.write();
350 stats.total_peers = self.peer_locations.len();
351 stats.peers_per_region.insert(
352 region,
353 self.region_peers.get(®ion).map(|p| p.len()).unwrap_or(0),
354 );
355 }
356 }
357
358 pub fn get_peer_location(&self, peer_id: &PeerId) -> Option<GeoLocation> {
360 self.peer_locations.get(peer_id).map(|loc| *loc)
361 }
362
363 pub fn get_peers_in_region(&self, region: GeoRegion) -> Vec<PeerId> {
365 self.stats.write().region_lookups += 1;
366 self.region_peers
367 .get(®ion)
368 .map(|peers| peers.clone())
369 .unwrap_or_default()
370 }
371
372 pub fn rank_peers_by_proximity(&self, reference: &GeoLocation) -> Vec<GeoPeer> {
374 self.stats.write().proximity_queries += 1;
375
376 let mut peers: Vec<GeoPeer> = self
377 .peer_locations
378 .iter()
379 .map(|entry| {
380 let peer_id = *entry.key();
381 let location = *entry.value();
382 let distance = reference.distance_to(&location);
383 GeoPeer::new(peer_id, location).with_distance(distance)
384 })
385 .collect();
386
387 if self.config.enable_region_clustering {
389 let ref_region = GeoRegion::from_location(reference);
390 for peer in &mut peers {
391 if peer.region == ref_region {
392 if let Some(dist) = peer.distance_km.as_mut() {
393 *dist = (*dist - self.config.same_region_bonus_km).max(0.0);
394 }
395 }
396 }
397 }
398
399 peers.sort_by(|a, b| {
401 a.distance_km
402 .unwrap_or(f64::INFINITY)
403 .partial_cmp(&b.distance_km.unwrap_or(f64::INFINITY))
404 .unwrap_or(std::cmp::Ordering::Equal)
405 });
406
407 peers
408 }
409
410 pub fn get_nearby_peers(&self, reference: &GeoLocation) -> Vec<GeoPeer> {
412 let all_peers = self.rank_peers_by_proximity(reference);
413 all_peers
414 .into_iter()
415 .filter(|peer| {
416 peer.distance_km
417 .map(|d| d <= self.config.nearby_threshold_km)
418 .unwrap_or(false)
419 })
420 .collect()
421 }
422
423 #[allow(dead_code)]
427 pub fn estimate_ip_location(&self, _ip: IpAddr) -> Option<GeoLocation> {
428 None
431 }
432
433 pub fn stats(&self) -> GeoRouterStats {
435 self.stats.read().clone()
436 }
437
438 #[allow(dead_code)]
440 pub fn clear(&self) {
441 self.peer_locations.clear();
442 self.region_peers.clear();
443 let mut stats = self.stats.write();
444 stats.total_peers = 0;
445 stats.peers_per_region.clear();
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn test_geo_location_new() {
455 let loc = GeoLocation::new(40.7128, -74.0060);
456 assert_eq!(loc.latitude, 40.7128);
457 assert_eq!(loc.longitude, -74.0060);
458 }
459
460 #[test]
461 #[should_panic(expected = "Latitude must be between -90 and 90")]
462 fn test_geo_location_invalid_latitude() {
463 GeoLocation::new(100.0, 0.0);
464 }
465
466 #[test]
467 #[should_panic(expected = "Longitude must be between -180 and 180")]
468 fn test_geo_location_invalid_longitude() {
469 GeoLocation::new(0.0, 200.0);
470 }
471
472 #[test]
473 fn test_distance_calculation() {
474 let ny = GeoLocation::new(40.7128, -74.0060);
475 let london = GeoLocation::new(51.5074, -0.1278);
476
477 let distance = ny.distance_to(&london);
478 assert!((distance - 5570.0).abs() < 100.0, "Distance: {}", distance);
480 }
481
482 #[test]
483 fn test_distance_same_location() {
484 let loc = GeoLocation::new(40.7128, -74.0060);
485 let distance = loc.distance_to(&loc);
486 assert!(distance < 0.1, "Same location distance should be ~0");
487 }
488
489 #[test]
490 fn test_latency_estimation() {
491 let sf = GeoLocation::new(37.7749, -122.4194);
492 let tokyo = GeoLocation::new(35.6762, 139.6503);
493
494 let latency = sf.estimate_latency_ms(&tokyo);
495 assert!(latency > 40.0 && latency < 70.0, "Latency: {}", latency);
497 }
498
499 #[test]
500 fn test_region_from_location() {
501 let ny = GeoLocation::new(40.7128, -74.0060);
503 assert_eq!(GeoRegion::from_location(&ny), GeoRegion::NorthAmerica);
504
505 let london = GeoLocation::new(51.5074, -0.1278);
507 assert_eq!(GeoRegion::from_location(&london), GeoRegion::Europe);
508
509 let tokyo = GeoLocation::new(35.6762, 139.6503);
511 assert_eq!(GeoRegion::from_location(&tokyo), GeoRegion::Asia);
512
513 let sao_paulo = GeoLocation::new(-23.5505, -46.6333);
515 assert_eq!(
516 GeoRegion::from_location(&sao_paulo),
517 GeoRegion::SouthAmerica
518 );
519 }
520
521 #[test]
522 fn test_geo_router_basic() {
523 let config = GeoRouterConfig::default();
524 let router = GeoRouter::new(config);
525
526 let peer1 = PeerId::random();
527 let peer2 = PeerId::random();
528
529 router.update_peer_location(peer1, GeoLocation::new(40.7128, -74.0060));
530 router.update_peer_location(peer2, GeoLocation::new(51.5074, -0.1278));
531
532 assert_eq!(router.stats().total_peers, 2);
533 }
534
535 #[test]
536 fn test_proximity_ranking() {
537 let config = GeoRouterConfig::default();
538 let router = GeoRouter::new(config);
539
540 let peer_ny = PeerId::random();
541 let peer_la = PeerId::random();
542 let peer_london = PeerId::random();
543
544 router.update_peer_location(peer_ny, GeoLocation::new(40.7128, -74.0060));
545 router.update_peer_location(peer_la, GeoLocation::new(34.0522, -118.2437));
546 router.update_peer_location(peer_london, GeoLocation::new(51.5074, -0.1278));
547
548 let sf = GeoLocation::new(37.7749, -122.4194);
550 let ranked = router.rank_peers_by_proximity(&sf);
551
552 assert_eq!(ranked.len(), 3);
553 assert_eq!(ranked[0].peer_id, peer_la);
554 assert_eq!(ranked[1].peer_id, peer_ny);
555 assert_eq!(ranked[2].peer_id, peer_london);
556 }
557
558 #[test]
559 fn test_nearby_peers() {
560 let config = GeoRouterConfig {
561 nearby_threshold_km: 500.0,
562 ..Default::default()
563 };
564 let router = GeoRouter::new(config);
565
566 let peer_nearby = PeerId::random();
567 let peer_far = PeerId::random();
568
569 router.update_peer_location(peer_nearby, GeoLocation::new(39.9526, -75.1652)); router.update_peer_location(peer_far, GeoLocation::new(51.5074, -0.1278));
573
574 let ny = GeoLocation::new(40.7128, -74.0060);
575 let nearby = router.get_nearby_peers(&ny);
576
577 assert_eq!(nearby.len(), 1);
578 assert_eq!(nearby[0].peer_id, peer_nearby);
579 }
580
581 #[test]
582 fn test_region_clustering() {
583 let config = GeoRouterConfig::default();
584 let router = GeoRouter::new(config);
585
586 let peer1 = PeerId::random();
587 let peer2 = PeerId::random();
588 let peer3 = PeerId::random();
589
590 router.update_peer_location(peer1, GeoLocation::new(40.7128, -74.0060)); router.update_peer_location(peer2, GeoLocation::new(34.0522, -118.2437)); router.update_peer_location(peer3, GeoLocation::new(51.5074, -0.1278)); let na_peers = router.get_peers_in_region(GeoRegion::NorthAmerica);
595 let eu_peers = router.get_peers_in_region(GeoRegion::Europe);
596
597 assert_eq!(na_peers.len(), 2);
598 assert_eq!(eu_peers.len(), 1);
599 assert!(na_peers.contains(&peer1));
600 assert!(na_peers.contains(&peer2));
601 assert!(eu_peers.contains(&peer3));
602 }
603
604 #[test]
605 fn test_remove_peer() {
606 let config = GeoRouterConfig::default();
607 let router = GeoRouter::new(config);
608
609 let peer = PeerId::random();
610 router.update_peer_location(peer, GeoLocation::new(40.7128, -74.0060));
611 assert_eq!(router.stats().total_peers, 1);
612
613 router.remove_peer(&peer);
614 assert_eq!(router.stats().total_peers, 0);
615 assert!(router.get_peer_location(&peer).is_none());
616 }
617
618 #[test]
619 fn test_same_region_bonus() {
620 let config = GeoRouterConfig {
621 same_region_bonus_km: 1000.0,
622 enable_region_clustering: true,
623 ..Default::default()
624 };
625 let router = GeoRouter::new(config);
626
627 let peer_same_region = PeerId::random();
628 let peer_diff_region = PeerId::random();
629
630 router.update_peer_location(peer_same_region, GeoLocation::new(34.0522, -118.2437)); router.update_peer_location(peer_diff_region, GeoLocation::new(51.5074, -0.1278)); let ny = GeoLocation::new(40.7128, -74.0060);
635 let ranked = router.rank_peers_by_proximity(&ny);
636
637 assert_eq!(ranked[0].peer_id, peer_same_region);
639 }
640
641 #[test]
642 fn test_max_peers_per_region() {
643 let config = GeoRouterConfig {
644 max_peers_per_region: 2,
645 enable_region_clustering: true,
646 ..Default::default()
647 };
648 let router = GeoRouter::new(config);
649
650 for _ in 0..3 {
652 let peer = PeerId::random();
653 router.update_peer_location(peer, GeoLocation::new(40.0, -100.0));
654 }
655
656 let na_peers = router.get_peers_in_region(GeoRegion::NorthAmerica);
657 assert!(na_peers.len() <= 2, "Should enforce max peers per region");
658 }
659
660 #[test]
661 fn test_statistics_tracking() {
662 let config = GeoRouterConfig::default();
663 let router = GeoRouter::new(config);
664
665 let peer1 = PeerId::random();
666 let peer2 = PeerId::random();
667
668 router.update_peer_location(peer1, GeoLocation::new(40.7128, -74.0060));
669 router.update_peer_location(peer2, GeoLocation::new(51.5074, -0.1278));
670
671 let stats = router.stats();
672 assert_eq!(stats.total_peers, 2);
673 assert_eq!(stats.proximity_queries, 0);
674
675 router.rank_peers_by_proximity(&GeoLocation::new(0.0, 0.0));
676 let stats = router.stats();
677 assert_eq!(stats.proximity_queries, 1);
678 }
679
680 #[test]
681 fn test_config_presets() {
682 let low_latency = GeoRouterConfig::low_latency();
683 assert_eq!(low_latency.nearby_threshold_km, 1000.0);
684
685 let global = GeoRouterConfig::global();
686 assert_eq!(global.nearby_threshold_km, 5000.0);
687
688 let regional = GeoRouterConfig::regional();
689 assert_eq!(regional.nearby_threshold_km, 200.0);
690 }
691}