use dashmap::DashMap;
use libp2p::PeerId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct GeoLocation {
pub latitude: f64,
pub longitude: f64,
}
impl GeoLocation {
pub fn new(latitude: f64, longitude: f64) -> Self {
assert!(
(-90.0..=90.0).contains(&latitude),
"Latitude must be between -90 and 90"
);
assert!(
(-180.0..=180.0).contains(&longitude),
"Longitude must be between -180 and 180"
);
Self {
latitude,
longitude,
}
}
pub fn distance_to(&self, other: &GeoLocation) -> f64 {
const EARTH_RADIUS_KM: f64 = 6371.0;
let lat1 = self.latitude.to_radians();
let lat2 = other.latitude.to_radians();
let delta_lat = (other.latitude - self.latitude).to_radians();
let delta_lon = (other.longitude - self.longitude).to_radians();
let a = (delta_lat / 2.0).sin().powi(2)
+ lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
EARTH_RADIUS_KM * c
}
pub fn estimate_latency_ms(&self, other: &GeoLocation) -> f64 {
const BASE_LATENCY_MS: f64 = 10.0; const MS_PER_1000_KM: f64 = 5.0;
let distance = self.distance_to(other);
BASE_LATENCY_MS + (distance / 1000.0) * MS_PER_1000_KM
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum GeoRegion {
NorthAmerica,
SouthAmerica,
Europe,
Asia,
Africa,
Oceania,
Unknown,
}
impl GeoRegion {
pub fn from_location(location: &GeoLocation) -> Self {
let lat = location.latitude;
let lon = location.longitude;
if (-170.0..=-30.0).contains(&lon) {
if lat >= 15.0 {
GeoRegion::NorthAmerica
} else {
GeoRegion::SouthAmerica
}
} else if (-30.0..=60.0).contains(&lon) {
if lat >= 35.0 {
GeoRegion::Europe
} else {
GeoRegion::Africa
}
} else if (60.0..=150.0).contains(&lon) {
if lat >= -10.0 {
GeoRegion::Asia
} else {
GeoRegion::Oceania
}
} else {
GeoRegion::Unknown
}
}
#[allow(dead_code)]
pub fn representative_location(&self) -> GeoLocation {
match self {
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),
}
}
}
#[derive(Debug, Clone)]
pub struct GeoPeer {
pub peer_id: PeerId,
pub location: GeoLocation,
pub region: GeoRegion,
pub distance_km: Option<f64>,
}
impl GeoPeer {
pub fn new(peer_id: PeerId, location: GeoLocation) -> Self {
let region = GeoRegion::from_location(&location);
Self {
peer_id,
location,
region,
distance_km: None,
}
}
pub fn with_distance(mut self, distance_km: f64) -> Self {
self.distance_km = Some(distance_km);
self
}
}
#[derive(Debug, Clone)]
pub struct GeoRouterConfig {
pub nearby_threshold_km: f64,
pub same_region_bonus_km: f64,
pub enable_region_clustering: bool,
pub max_peers_per_region: usize,
}
impl Default for GeoRouterConfig {
fn default() -> Self {
Self {
nearby_threshold_km: 500.0, same_region_bonus_km: 1000.0, enable_region_clustering: true,
max_peers_per_region: 100,
}
}
}
impl GeoRouterConfig {
pub fn low_latency() -> Self {
Self {
nearby_threshold_km: 1000.0,
same_region_bonus_km: 2000.0,
enable_region_clustering: true,
max_peers_per_region: 50,
}
}
pub fn global() -> Self {
Self {
nearby_threshold_km: 5000.0,
same_region_bonus_km: 500.0,
enable_region_clustering: true,
max_peers_per_region: 200,
}
}
pub fn regional() -> Self {
Self {
nearby_threshold_km: 200.0,
same_region_bonus_km: 3000.0,
enable_region_clustering: true,
max_peers_per_region: 150,
}
}
}
pub struct GeoRouter {
config: GeoRouterConfig,
peer_locations: Arc<DashMap<PeerId, GeoLocation>>,
region_peers: Arc<DashMap<GeoRegion, Vec<PeerId>>>,
stats: Arc<parking_lot::RwLock<GeoRouterStats>>,
}
#[derive(Debug, Clone, Default)]
pub struct GeoRouterStats {
pub total_peers: usize,
pub peers_per_region: HashMap<GeoRegion, usize>,
pub proximity_queries: u64,
pub region_lookups: u64,
}
impl GeoRouter {
pub fn new(config: GeoRouterConfig) -> Self {
Self {
config,
peer_locations: Arc::new(DashMap::new()),
region_peers: Arc::new(DashMap::new()),
stats: Arc::new(parking_lot::RwLock::new(GeoRouterStats::default())),
}
}
pub fn update_peer_location(&self, peer_id: PeerId, location: GeoLocation) {
if let Some(old_location) = self.peer_locations.get(&peer_id) {
let old_region = GeoRegion::from_location(&old_location);
if let Some(mut peers) = self.region_peers.get_mut(&old_region) {
peers.retain(|p| p != &peer_id);
}
}
self.peer_locations.insert(peer_id, location);
if self.config.enable_region_clustering {
let region = GeoRegion::from_location(&location);
self.region_peers.entry(region).or_default().push(peer_id);
if let Some(mut peers) = self.region_peers.get_mut(®ion) {
if peers.len() > self.config.max_peers_per_region {
peers.truncate(self.config.max_peers_per_region);
}
}
}
let mut stats = self.stats.write();
stats.total_peers = self.peer_locations.len();
stats.peers_per_region.clear();
for entry in self.region_peers.iter() {
stats
.peers_per_region
.insert(*entry.key(), entry.value().len());
}
}
pub fn remove_peer(&self, peer_id: &PeerId) {
if let Some((_, location)) = self.peer_locations.remove(peer_id) {
let region = GeoRegion::from_location(&location);
if let Some(mut peers) = self.region_peers.get_mut(®ion) {
peers.retain(|p| p != peer_id);
}
let mut stats = self.stats.write();
stats.total_peers = self.peer_locations.len();
stats.peers_per_region.insert(
region,
self.region_peers.get(®ion).map(|p| p.len()).unwrap_or(0),
);
}
}
pub fn get_peer_location(&self, peer_id: &PeerId) -> Option<GeoLocation> {
self.peer_locations.get(peer_id).map(|loc| *loc)
}
pub fn get_peers_in_region(&self, region: GeoRegion) -> Vec<PeerId> {
self.stats.write().region_lookups += 1;
self.region_peers
.get(®ion)
.map(|peers| peers.clone())
.unwrap_or_default()
}
pub fn rank_peers_by_proximity(&self, reference: &GeoLocation) -> Vec<GeoPeer> {
self.stats.write().proximity_queries += 1;
let mut peers: Vec<GeoPeer> = self
.peer_locations
.iter()
.map(|entry| {
let peer_id = *entry.key();
let location = *entry.value();
let distance = reference.distance_to(&location);
GeoPeer::new(peer_id, location).with_distance(distance)
})
.collect();
if self.config.enable_region_clustering {
let ref_region = GeoRegion::from_location(reference);
for peer in &mut peers {
if peer.region == ref_region {
if let Some(dist) = peer.distance_km.as_mut() {
*dist = (*dist - self.config.same_region_bonus_km).max(0.0);
}
}
}
}
peers.sort_by(|a, b| {
a.distance_km
.unwrap_or(f64::INFINITY)
.partial_cmp(&b.distance_km.unwrap_or(f64::INFINITY))
.unwrap_or(std::cmp::Ordering::Equal)
});
peers
}
pub fn get_nearby_peers(&self, reference: &GeoLocation) -> Vec<GeoPeer> {
let all_peers = self.rank_peers_by_proximity(reference);
all_peers
.into_iter()
.filter(|peer| {
peer.distance_km
.map(|d| d <= self.config.nearby_threshold_km)
.unwrap_or(false)
})
.collect()
}
#[allow(dead_code)]
pub fn estimate_ip_location(&self, _ip: IpAddr) -> Option<GeoLocation> {
None
}
pub fn stats(&self) -> GeoRouterStats {
self.stats.read().clone()
}
#[allow(dead_code)]
pub fn clear(&self) {
self.peer_locations.clear();
self.region_peers.clear();
let mut stats = self.stats.write();
stats.total_peers = 0;
stats.peers_per_region.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_geo_location_new() {
let loc = GeoLocation::new(40.7128, -74.0060);
assert_eq!(loc.latitude, 40.7128);
assert_eq!(loc.longitude, -74.0060);
}
#[test]
#[should_panic(expected = "Latitude must be between -90 and 90")]
fn test_geo_location_invalid_latitude() {
GeoLocation::new(100.0, 0.0);
}
#[test]
#[should_panic(expected = "Longitude must be between -180 and 180")]
fn test_geo_location_invalid_longitude() {
GeoLocation::new(0.0, 200.0);
}
#[test]
fn test_distance_calculation() {
let ny = GeoLocation::new(40.7128, -74.0060);
let london = GeoLocation::new(51.5074, -0.1278);
let distance = ny.distance_to(&london);
assert!((distance - 5570.0).abs() < 100.0, "Distance: {}", distance);
}
#[test]
fn test_distance_same_location() {
let loc = GeoLocation::new(40.7128, -74.0060);
let distance = loc.distance_to(&loc);
assert!(distance < 0.1, "Same location distance should be ~0");
}
#[test]
fn test_latency_estimation() {
let sf = GeoLocation::new(37.7749, -122.4194);
let tokyo = GeoLocation::new(35.6762, 139.6503);
let latency = sf.estimate_latency_ms(&tokyo);
assert!(latency > 40.0 && latency < 70.0, "Latency: {}", latency);
}
#[test]
fn test_region_from_location() {
let ny = GeoLocation::new(40.7128, -74.0060);
assert_eq!(GeoRegion::from_location(&ny), GeoRegion::NorthAmerica);
let london = GeoLocation::new(51.5074, -0.1278);
assert_eq!(GeoRegion::from_location(&london), GeoRegion::Europe);
let tokyo = GeoLocation::new(35.6762, 139.6503);
assert_eq!(GeoRegion::from_location(&tokyo), GeoRegion::Asia);
let sao_paulo = GeoLocation::new(-23.5505, -46.6333);
assert_eq!(
GeoRegion::from_location(&sao_paulo),
GeoRegion::SouthAmerica
);
}
#[test]
fn test_geo_router_basic() {
let config = GeoRouterConfig::default();
let router = GeoRouter::new(config);
let peer1 = PeerId::random();
let peer2 = PeerId::random();
router.update_peer_location(peer1, GeoLocation::new(40.7128, -74.0060));
router.update_peer_location(peer2, GeoLocation::new(51.5074, -0.1278));
assert_eq!(router.stats().total_peers, 2);
}
#[test]
fn test_proximity_ranking() {
let config = GeoRouterConfig::default();
let router = GeoRouter::new(config);
let peer_ny = PeerId::random();
let peer_la = PeerId::random();
let peer_london = PeerId::random();
router.update_peer_location(peer_ny, GeoLocation::new(40.7128, -74.0060));
router.update_peer_location(peer_la, GeoLocation::new(34.0522, -118.2437));
router.update_peer_location(peer_london, GeoLocation::new(51.5074, -0.1278));
let sf = GeoLocation::new(37.7749, -122.4194);
let ranked = router.rank_peers_by_proximity(&sf);
assert_eq!(ranked.len(), 3);
assert_eq!(ranked[0].peer_id, peer_la);
assert_eq!(ranked[1].peer_id, peer_ny);
assert_eq!(ranked[2].peer_id, peer_london);
}
#[test]
fn test_nearby_peers() {
let config = GeoRouterConfig {
nearby_threshold_km: 500.0,
..Default::default()
};
let router = GeoRouter::new(config);
let peer_nearby = PeerId::random();
let peer_far = PeerId::random();
router.update_peer_location(peer_nearby, GeoLocation::new(39.9526, -75.1652)); router.update_peer_location(peer_far, GeoLocation::new(51.5074, -0.1278));
let ny = GeoLocation::new(40.7128, -74.0060);
let nearby = router.get_nearby_peers(&ny);
assert_eq!(nearby.len(), 1);
assert_eq!(nearby[0].peer_id, peer_nearby);
}
#[test]
fn test_region_clustering() {
let config = GeoRouterConfig::default();
let router = GeoRouter::new(config);
let peer1 = PeerId::random();
let peer2 = PeerId::random();
let peer3 = PeerId::random();
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);
let eu_peers = router.get_peers_in_region(GeoRegion::Europe);
assert_eq!(na_peers.len(), 2);
assert_eq!(eu_peers.len(), 1);
assert!(na_peers.contains(&peer1));
assert!(na_peers.contains(&peer2));
assert!(eu_peers.contains(&peer3));
}
#[test]
fn test_remove_peer() {
let config = GeoRouterConfig::default();
let router = GeoRouter::new(config);
let peer = PeerId::random();
router.update_peer_location(peer, GeoLocation::new(40.7128, -74.0060));
assert_eq!(router.stats().total_peers, 1);
router.remove_peer(&peer);
assert_eq!(router.stats().total_peers, 0);
assert!(router.get_peer_location(&peer).is_none());
}
#[test]
fn test_same_region_bonus() {
let config = GeoRouterConfig {
same_region_bonus_km: 1000.0,
enable_region_clustering: true,
..Default::default()
};
let router = GeoRouter::new(config);
let peer_same_region = PeerId::random();
let peer_diff_region = PeerId::random();
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);
let ranked = router.rank_peers_by_proximity(&ny);
assert_eq!(ranked[0].peer_id, peer_same_region);
}
#[test]
fn test_max_peers_per_region() {
let config = GeoRouterConfig {
max_peers_per_region: 2,
enable_region_clustering: true,
..Default::default()
};
let router = GeoRouter::new(config);
for _ in 0..3 {
let peer = PeerId::random();
router.update_peer_location(peer, GeoLocation::new(40.0, -100.0));
}
let na_peers = router.get_peers_in_region(GeoRegion::NorthAmerica);
assert!(na_peers.len() <= 2, "Should enforce max peers per region");
}
#[test]
fn test_statistics_tracking() {
let config = GeoRouterConfig::default();
let router = GeoRouter::new(config);
let peer1 = PeerId::random();
let peer2 = PeerId::random();
router.update_peer_location(peer1, GeoLocation::new(40.7128, -74.0060));
router.update_peer_location(peer2, GeoLocation::new(51.5074, -0.1278));
let stats = router.stats();
assert_eq!(stats.total_peers, 2);
assert_eq!(stats.proximity_queries, 0);
router.rank_peers_by_proximity(&GeoLocation::new(0.0, 0.0));
let stats = router.stats();
assert_eq!(stats.proximity_queries, 1);
}
#[test]
fn test_config_presets() {
let low_latency = GeoRouterConfig::low_latency();
assert_eq!(low_latency.nearby_threshold_km, 1000.0);
let global = GeoRouterConfig::global();
assert_eq!(global.nearby_threshold_km, 5000.0);
let regional = GeoRouterConfig::regional();
assert_eq!(regional.nearby_threshold_km, 200.0);
}
}