generated from norskeld/rustafarian
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
548 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
use std::time::Duration; | ||
|
||
use clap::builder::PossibleValue; | ||
use clap::{Parser, ValueEnum}; | ||
use indicatif::{ProgressBar, ProgressStyle}; | ||
|
||
use crate::relays::Protocol; | ||
|
||
#[derive(Parser, Debug)] | ||
#[command(version, about, long_about = None)] | ||
pub struct Cli { | ||
/// Filter servers by used protocol. | ||
#[arg(short, long, value_parser = clap::value_parser!(Protocol))] | ||
pub protocol: Option<Protocol>, | ||
|
||
/// Filter servers by maximum physical distance (in km). | ||
#[arg(short, long, default_value_t = 500)] | ||
pub distance: usize, | ||
|
||
/// Filter servers by maximum rtt (in ms). | ||
#[arg(short, long)] | ||
pub rtt: Option<usize>, | ||
|
||
/// How many pings to send for each relay. | ||
#[arg(short, long, default_value_t = 4)] | ||
pub count: usize, | ||
|
||
/// Specify ping timeout (in ms). | ||
#[arg(long, default_value_t = 750)] | ||
pub timeout: u64, | ||
|
||
/// Specify the latitude. | ||
#[arg(long, requires = "longitude")] | ||
pub latitude: Option<f64>, | ||
|
||
/// Specify the longitude. | ||
#[arg(long, requires = "latitude")] | ||
pub longitude: Option<f64>, | ||
} | ||
|
||
impl ValueEnum for Protocol { | ||
fn value_variants<'a>() -> &'a [Self] { | ||
&[Self::OpenVPN, Self::WireGuard] | ||
} | ||
|
||
fn to_possible_value(&self) -> Option<PossibleValue> { | ||
Some(match self { | ||
| Protocol::OpenVPN => PossibleValue::new("openvpn"), | ||
| Protocol::WireGuard => PossibleValue::new("wireguard"), | ||
}) | ||
} | ||
} | ||
|
||
/// Small wrapper around the `indicatif` spinner. | ||
pub struct Spinner { | ||
spinner: ProgressBar, | ||
} | ||
|
||
impl Spinner { | ||
pub fn new() -> Self { | ||
let style = ProgressStyle::default_spinner() | ||
.tick_strings(&[" ", "· ", "·· ", "···", " ··", " ·", " "]); | ||
|
||
let spinner = ProgressBar::new_spinner(); | ||
|
||
spinner.set_style(style); | ||
spinner.enable_steady_tick(Duration::from_millis(150)); | ||
|
||
Self { spinner } | ||
} | ||
|
||
/// Sets the message of the spinner. | ||
pub fn set_message(&self, message: &'static str) { | ||
self.spinner.set_message(message); | ||
} | ||
|
||
/// Stops the spinner and clears the message. | ||
pub fn stop(&self) { | ||
self.spinner.finish_and_clear(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use serde::{Deserialize, Serialize}; | ||
use serde_json::Value; | ||
use thiserror::Error; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum CoordError { | ||
#[error("Failed to fetch coordinates")] | ||
FetchFailed(reqwest::Error), | ||
#[error("Failed to parse response")] | ||
ParseResponseFailed(reqwest::Error), | ||
#[error("Failed to get latitude and longitude from the response")] | ||
GetCoordsFailed, | ||
} | ||
|
||
/// Represents a point on Earth. | ||
#[derive(Clone, Debug, Serialize, Deserialize)] | ||
pub struct Coord { | ||
latitude: f64, | ||
longitude: f64, | ||
} | ||
|
||
impl Coord { | ||
/// Constructs a new `Coord`. | ||
pub fn new(latitude: f64, longitude: f64) -> Self { | ||
Self { | ||
latitude, | ||
longitude, | ||
} | ||
} | ||
|
||
/// Fetches the current coordinates using the Mullvad API. | ||
pub async fn fetch() -> Result<Self, CoordError> { | ||
let response = reqwest::get("https://am.i.mullvad.net/json") | ||
.await | ||
.map_err(CoordError::FetchFailed)?; | ||
|
||
let data = response | ||
.json::<Value>() | ||
.await | ||
.map_err(CoordError::ParseResponseFailed)?; | ||
|
||
let lat = data["latitude"].as_f64(); | ||
let lon = data["longitude"].as_f64(); | ||
|
||
lat | ||
.zip(lon) | ||
.map(|(latitude, longitude)| Self::new(latitude, longitude)) | ||
.ok_or_else(|| CoordError::GetCoordsFailed) | ||
} | ||
|
||
/// Finds the distance (in meters) between two coordinates using the haversine formula. | ||
pub fn distance_to(&self, other: &Self) -> f64 { | ||
// Earth radius in meters. This is *average*, since Earth is not a sphere, but a spheroid. | ||
const R: f64 = 6_371_000f64; | ||
|
||
// Turn latitudes and longitudes into radians. | ||
let phi1 = self.latitude.to_radians(); | ||
let phi2 = other.latitude.to_radians(); | ||
let lam1 = self.longitude.to_radians(); | ||
let lam2 = other.longitude.to_radians(); | ||
|
||
// The haversine function. Computes half a versine of the given angle `theta`. | ||
let haversine = |theta: f64| (1.0 - theta.cos()) / 2.0; | ||
|
||
let hav_delta_phi = haversine(phi2 - phi1); | ||
let hav_delta_lam = phi1.cos() * phi2.cos() * haversine(lam2 - lam1); | ||
let hav_delta = hav_delta_phi + hav_delta_lam; | ||
|
||
let distance = (2.0 * R * hav_delta.sqrt().asin() * 1_000.0).round() / 1_000.0; | ||
|
||
distance | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use std::fmt::Debug; | ||
|
||
use crate::coord::Coord; | ||
use crate::relays::{Protocol, Relay}; | ||
|
||
#[derive(PartialEq)] | ||
pub enum FilterStage { | ||
/// Such filters apply when loading them from the relays file. | ||
Load, | ||
/// Such filters apply after pinging relays. | ||
Ping, | ||
} | ||
|
||
/// Filter trait to dynamically dispatch filters. | ||
pub trait Filter: Debug { | ||
/// Returns the stage of the filter. | ||
fn stage(&self) -> FilterStage; | ||
|
||
/// Filter predicate. | ||
fn matches(&self, relay: &Relay) -> bool; | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct FilterByDistance { | ||
user: Coord, | ||
distance: f64, | ||
} | ||
|
||
impl FilterByDistance { | ||
pub fn new(user: Coord, distance: f64) -> Self { | ||
Self { user, distance } | ||
} | ||
} | ||
|
||
impl Filter for FilterByDistance { | ||
fn stage(&self) -> FilterStage { | ||
FilterStage::Load | ||
} | ||
|
||
fn matches(&self, relay: &Relay) -> bool { | ||
(relay.coord.distance_to(&self.user) / 1_000.0) < self.distance | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct FilterByProtocol(Option<Protocol>); | ||
|
||
impl FilterByProtocol { | ||
pub fn new(protocol: Option<Protocol>) -> Self { | ||
Self(protocol) | ||
} | ||
} | ||
|
||
impl Filter for FilterByProtocol { | ||
fn stage(&self) -> FilterStage { | ||
FilterStage::Load | ||
} | ||
|
||
fn matches(&self, relay: &Relay) -> bool { | ||
self | ||
.0 | ||
.as_ref() | ||
.map_or(true, |use_protocol| relay.protocol == *use_protocol) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pub mod cli; | ||
pub mod coord; | ||
pub mod filters; | ||
pub mod pinger; | ||
pub mod relays; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,70 @@ | ||
fn main() { | ||
println!("Hello, world!"); | ||
use std::thread; | ||
use std::time::Duration; | ||
|
||
use clap::Parser; | ||
use pingmole::cli::{Cli, Spinner}; | ||
use pingmole::coord::Coord; | ||
use pingmole::filters::{FilterByDistance, FilterByProtocol}; | ||
use pingmole::pinger::RelayPinger; | ||
use pingmole::relays::RelaysLoader; | ||
|
||
#[tokio::main] | ||
async fn main() -> anyhow::Result<()> { | ||
let cli = Cli::parse(); | ||
let spinner = Spinner::new(); | ||
|
||
// ----------------------------------------------------------------------------------------------- | ||
// 1. Get current location, either via arguments or via Mullvad API. | ||
spinner.set_message("Getting current location"); | ||
|
||
let user = match cli.latitude.zip(cli.longitude) { | ||
| Some((latitude, longitude)) => Coord::new(latitude, longitude), | ||
| None => Coord::fetch().await?, | ||
}; | ||
|
||
thread::sleep(std::time::Duration::from_secs(1)); | ||
|
||
// ----------------------------------------------------------------------------------------------- | ||
// 2. Load relays from file and filter them. | ||
spinner.set_message("Loading relays"); | ||
|
||
let loader = RelaysLoader::new( | ||
RelaysLoader::resolve_path()?, | ||
vec![ | ||
Box::new(FilterByDistance::new(user, cli.distance as f64)), | ||
Box::new(FilterByProtocol::new(cli.protocol)), | ||
], | ||
); | ||
|
||
let relays = loader.load()?; | ||
|
||
thread::sleep(std::time::Duration::from_secs(1)); | ||
|
||
// ----------------------------------------------------------------------------------------------- | ||
// 3. Pinging relays. | ||
spinner.set_message("Pinging relays"); | ||
|
||
let mut tasks = Vec::new(); | ||
let mut timings = Vec::new(); | ||
|
||
for relay in relays { | ||
let mut pinger = RelayPinger::new(relay); | ||
|
||
pinger.set_count(cli.count); | ||
pinger.set_timeout(Duration::from_millis(cli.timeout)); | ||
|
||
tasks.push(tokio::spawn(pinger.execute())); | ||
} | ||
|
||
for task in tasks { | ||
timings.push(task.await?); | ||
} | ||
|
||
// ----------------------------------------------------------------------------------------------- | ||
// 4. Print results. | ||
spinner.stop(); | ||
|
||
dbg!(timings); | ||
|
||
Ok(()) | ||
} |
Oops, something went wrong.