diff --git a/backend/src/util/location.rs b/backend/src/util/location.rs index 5d04f88..db97bd7 100644 --- a/backend/src/util/location.rs +++ b/backend/src/util/location.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use actix::prelude::*; use rustc_hash::FxHashMap; use parking_lot::RwLock; +use serde::Deserialize; use crate::chain::{Chain, LocateNode}; use crate::types::{NodeId, NodeLocation}; @@ -52,6 +53,34 @@ pub struct LocateRequest { pub chain: Addr, } +#[derive(Deserialize)] +pub struct IPApiLocate { + city: Box, + loc: Box, +} + +impl IPApiLocate { + fn into_node_location(self) -> Option { + let IPApiLocate { city, loc } = self; + + let mut loc = loc.split(",").map(|n| n.parse()); + + let latitude = loc.next()?.ok()?; + let longitude = loc.next()?.ok()?; + + // Guarantee that the iterator has been exhausted + if loc.next().is_some() { + return None; + } + + Some(NodeLocation { + latitude, + longitude, + city, + }) + } +} + impl Handler for Locator { type Result = (); @@ -66,18 +95,9 @@ impl Handler for Locator { return } - let ip_req = format!("https://ipapi.co/{}/json", ip); - let mut response = match self.client.post(&ip_req).send() { - Ok(response) => response, - Err(err) => return debug!("POST error for ip location: {:?}", err), - }; - - let location = match response.json::() { - Ok(location) => Some(Arc::new(location)), - Err(err) => { - debug!("JSON error for ip location: {:?}", err); - None - } + let location = match self.iplocate(ip) { + Ok(location) => location, + Err(err) => return debug!("GET error for ip location: {:?}", err), }; self.cache.write().insert(ip, location.clone()); @@ -87,3 +107,72 @@ impl Handler for Locator { } } } + +impl Locator { + fn iplocate(&self, ip: Ipv4Addr) -> Result>, reqwest::Error> { + let location = self.iplocate_ipapi_co(ip)?; + + match location { + Some(location) => Ok(Some(location)), + None => self.iplocate_ipinfo_io(ip), + } + } + + fn iplocate_ipapi_co(&self, ip: Ipv4Addr) -> Result>, reqwest::Error> { + let location = self.query(&format!("https://ipapi.co/{}/json", ip))?.map(Arc::new); + + Ok(location) + } + + fn iplocate_ipinfo_io(&self, ip: Ipv4Addr) -> Result>, reqwest::Error> { + let location = self.query(&format!("https://ipinfo.io/{}/json", ip))?.and_then(|loc: IPApiLocate| { + loc.into_node_location().map(Arc::new) + }); + + Ok(location) + } + + fn query(&self, url: &str) -> Result, reqwest::Error> + where + for<'de> T: Deserialize<'de>, + { + match self.client.get(url).send()?.json::() { + Ok(result) => Ok(Some(result)), + Err(err) => { + debug!("JSON error for ip location: {:?}", err); + Ok(None) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ipapi_locate_to_node_location() { + let ipapi = IPApiLocate { + loc: "12.5,56.25".into(), + city: "Foobar".into(), + }; + + let location = ipapi.into_node_location().unwrap(); + + assert_eq!(location.latitude, 12.5); + assert_eq!(location.longitude, 56.25); + assert_eq!(&*location.city, "Foobar"); + } + + #[test] + fn ipapi_locate_to_node_location_too_many() { + let ipapi = IPApiLocate { + loc: "12.5,56.25,1.0".into(), + city: "Foobar".into(), + }; + + let location = ipapi.into_node_location(); + + assert!(location.is_none()); + } +} \ No newline at end of file diff --git a/backend/src/util/num_stats.rs b/backend/src/util/num_stats.rs index 42cb548..ea23a19 100644 --- a/backend/src/util/num_stats.rs +++ b/backend/src/util/num_stats.rs @@ -1,4 +1,4 @@ -use num_traits::{Zero, NumOps, Bounded, ops::saturating::Saturating}; +use num_traits::{Zero, NumOps, Bounded}; use std::iter::Sum; use std::convert::TryFrom; @@ -10,7 +10,7 @@ pub struct NumStats { sum: T, } -impl> NumStats { +impl> NumStats { pub fn new(size: usize) -> Self { NumStats { stack: vec![T::zero(); size].into_boxed_slice(),