From ff2e24afbc819c05cbb5ddd34fe36e9e62dd07ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 12 May 2020 12:52:31 +0100 Subject: [PATCH] Updated directory_client reqwest to 0.10 (#226) * Updated directory_client reqwest to 0.10 * Using consistent syntax --- Cargo.lock | 187 +++++++++++++++++- clients/desktop/src/client/mod.rs | 2 +- .../desktop/src/client/topology_control.rs | 3 +- clients/desktop/src/commands/init.rs | 2 +- .../client-libs/directory-client/Cargo.toml | 4 +- .../client-libs/directory-client/src/lib.rs | 144 ++++++++++---- .../directory-client/src/presence/mod.rs | 116 +---------- .../directory-client/src/presence/topology.rs | 133 +++++++++++++ .../src/requests/health_check_get.rs | 65 +++--- .../src/requests/metrics_mixes_get.rs | 67 +++---- .../src/requests/metrics_mixes_post.rs | 60 +++--- .../directory-client/src/requests/mod.rs | 23 +++ .../src/requests/presence_coconodes_post.rs | 65 +++--- .../src/requests/presence_gateways_post.rs | 65 +++--- .../src/requests/presence_mixnodes_post.rs | 65 +++--- .../src/requests/presence_providers_post.rs | 65 +++--- .../src/requests/presence_topology_get.rs | 60 +++--- common/topology/Cargo.toml | 1 + common/topology/src/lib.rs | 6 +- gateway/src/commands/run.rs | 35 ---- gateway/src/node/mod.rs | 32 +++ gateway/src/node/presence/mod.rs | 7 +- mixnode/src/node/metrics.rs | 15 +- mixnode/src/node/mod.rs | 6 +- mixnode/src/node/presence.rs | 11 +- 25 files changed, 759 insertions(+), 480 deletions(-) create mode 100644 common/client-libs/directory-client/src/presence/topology.rs diff --git a/Cargo.lock b/Cargo.lock index aa8c3eb28e..de1b350111 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,11 +582,13 @@ dependencies = [ name = "directory-client" version = "0.1.0" dependencies = [ + "futures 0.3.4", "log 0.4.8", "mockito", "pretty_env_logger", - "reqwest", + "reqwest 0.10.4", "serde", + "tokio 0.2.16", "topology", ] @@ -970,6 +972,25 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "h2" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" +dependencies = [ + "bytes 0.5.4", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.1", + "indexmap", + "log 0.4.8", + "slab", + "tokio 0.2.16", + "tokio-util", +] + [[package]] name = "handlebars" version = "3.0.1" @@ -1067,6 +1088,16 @@ dependencies = [ "tokio-buf", ] +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.4", + "http 0.2.1", +] + [[package]] name = "httparse" version = "1.3.4" @@ -1110,9 +1141,9 @@ dependencies = [ "bytes 0.4.12", "futures 0.1.29", "futures-cpupool", - "h2", + "h2 0.1.26", "http 0.1.21", - "http-body", + "http-body 0.1.0", "httparse", "iovec", "itoa", @@ -1128,7 +1159,31 @@ dependencies = [ "tokio-tcp", "tokio-threadpool", "tokio-timer", - "want", + "want 0.2.0", +] + +[[package]] +name = "hyper" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" +dependencies = [ + "bytes 0.5.4", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.2.5", + "http 0.2.1", + "http-body 0.3.1", + "httparse", + "itoa", + "log 0.4.8", + "net2", + "pin-project", + "time", + "tokio 0.2.16", + "tower-service", + "want 0.3.0", ] [[package]] @@ -1144,6 +1199,19 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "hyper-tls" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" +dependencies = [ + "bytes 0.5.4", + "hyper 0.13.5", + "native-tls", + "tokio 0.2.16", + "tokio-tls", +] + [[package]] name = "idna" version = "0.1.5" @@ -1617,7 +1685,7 @@ dependencies = [ "pem", "pemstore", "pretty_env_logger", - "reqwest", + "reqwest 0.9.24", "serde", "serde_json", "tempfile", @@ -2357,14 +2425,14 @@ dependencies = [ "futures 0.1.29", "http 0.1.21", "hyper 0.12.35", - "hyper-tls", + "hyper-tls 0.3.2", "log 0.4.8", "mime 0.3.16", "mime_guess 2.0.3", "native-tls", "serde", "serde_json", - "serde_urlencoded", + "serde_urlencoded 0.5.5", "time", "tokio 0.1.22", "tokio-executor", @@ -2376,6 +2444,42 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" +dependencies = [ + "base64 0.11.0", + "bytes 0.5.4", + "encoding_rs", + "futures-core", + "futures-util", + "http 0.2.1", + "http-body 0.3.1", + "hyper 0.13.5", + "hyper-tls 0.4.1", + "js-sys", + "lazy_static", + "log 0.4.8", + "mime 0.3.16", + "mime_guess 2.0.3", + "native-tls", + "percent-encoding 2.1.0", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded 0.6.1", + "time", + "tokio 0.2.16", + "tokio-tls", + "url 2.1.1", + "wasm-bindgen", + "wasm-bindgen-futures 0.4.10", + "web-sys", + "winreg", +] + [[package]] name = "route-recognizer" version = "0.1.13" @@ -2535,6 +2639,18 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url 2.1.1", +] + [[package]] name = "sha-1" version = "0.8.2" @@ -2954,6 +3070,16 @@ dependencies = [ "tokio-executor", ] +[[package]] +name = "tokio-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +dependencies = [ + "native-tls", + "tokio 0.2.16", +] + [[package]] name = "tokio-tungstenite" version = "0.10.1" @@ -2967,6 +3093,20 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.4", + "futures-core", + "futures-sink", + "log 0.4.8", + "pin-project-lite", + "tokio 0.2.16", +] + [[package]] name = "toml" version = "0.5.6" @@ -2981,6 +3121,7 @@ name = "topology" version = "0.1.0" dependencies = [ "bs58", + "futures 0.3.4", "itertools", "log 0.4.8", "nymsphinx", @@ -2990,6 +3131,12 @@ dependencies = [ "version-checker", ] +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" + [[package]] name = "traitobject" version = "0.1.0" @@ -3207,6 +3354,16 @@ dependencies = [ "try-lock", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.8", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3220,6 +3377,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" dependencies = [ "cfg-if", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -3251,6 +3410,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7add542ea1ac7fdaa9dc25e031a6af33b7d63376292bd24140c637d00d1c312a" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.60" @@ -3291,7 +3462,7 @@ dependencies = [ "js-sys", "scoped-tls", "wasm-bindgen", - "wasm-bindgen-futures", + "wasm-bindgen-futures 0.3.27", "wasm-bindgen-test-macro", ] diff --git a/clients/desktop/src/client/mod.rs b/clients/desktop/src/client/mod.rs index 9b54067bef..6a097c2e7b 100644 --- a/clients/desktop/src/client/mod.rs +++ b/clients/desktop/src/client/mod.rs @@ -29,7 +29,7 @@ use gateway_client::{GatewayClient, SphinxPacketReceiver, SphinxPacketSender}; use gateway_requests::auth_token::AuthToken; use log::*; use nymsphinx::chunking::split_and_prepare_payloads; -use nymsphinx::{Destination, DestinationAddressBytes}; +use nymsphinx::DestinationAddressBytes; use received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver}; use tokio::runtime::Runtime; diff --git a/clients/desktop/src/client/topology_control.rs b/clients/desktop/src/client/topology_control.rs index cd13e8fc79..1f91f39ed9 100644 --- a/clients/desktop/src/client/topology_control.rs +++ b/clients/desktop/src/client/topology_control.rs @@ -157,7 +157,8 @@ impl TopologyRefresher { } async fn get_current_compatible_topology(&self) -> T { - let full_topology = T::new(self.directory_server.clone()); + // note: this call makes it neccessary that `T::new()`does *not* have 'static lifetime + let full_topology = T::new(self.directory_server.clone()).await; // just filter by version and assume the validators will remove all bad behaving // nodes with the staking full_topology.filter_system_version(built_info::PKG_VERSION) diff --git a/clients/desktop/src/commands/init.rs b/clients/desktop/src/commands/init.rs index d3323669fa..527c203269 100644 --- a/clients/desktop/src/commands/init.rs +++ b/clients/desktop/src/commands/init.rs @@ -89,7 +89,7 @@ async fn choose_gateway( our_address: DestinationAddressBytes, ) -> (String, AuthToken) { // TODO: once we change to graph topology this here will need to be updated! - let topology = Topology::new(directory_server.clone()); + let topology = Topology::new(directory_server.clone()).await; let version_filtered_topology = topology.filter_system_version(built_info::PKG_VERSION); // don't care about health of the networks as mixes can go up and down any time, // but DO care about gateways diff --git a/common/client-libs/directory-client/Cargo.toml b/common/client-libs/directory-client/Cargo.toml index a38459dcd2..73fbbb9a16 100644 --- a/common/client-libs/directory-client/Cargo.toml +++ b/common/client-libs/directory-client/Cargo.toml @@ -8,8 +8,9 @@ edition = "2018" [dependencies] log = "0.4" +futures = "0.3" pretty_env_logger = "0.3" -reqwest = "0.9.22" +reqwest = { version = "0.10", features = ["json"] } serde = { version = "1.0.104", features = ["derive"] } ## internal @@ -17,3 +18,4 @@ topology = {path = "../../topology"} [dev-dependencies] mockito = "0.23.0" +tokio = { version = "0.2", features = ["macros"] } \ No newline at end of file diff --git a/common/client-libs/directory-client/src/lib.rs b/common/client-libs/directory-client/src/lib.rs index 8a4160ab48..80277174be 100644 --- a/common/client-libs/directory-client/src/lib.rs +++ b/common/client-libs/directory-client/src/lib.rs @@ -12,24 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::requests::health_check_get::{HealthCheckRequester, Request as HealthCheckRequest}; -use crate::requests::metrics_mixes_get::{MetricsMixRequester, Request as MetricsMixRequest}; -use crate::requests::metrics_mixes_post::{MetricsMixPoster, Request as MetricsMixPost}; -use crate::requests::presence_coconodes_post::{ - PresenceCocoNodesPoster, Request as PresenceCocoNodesPost, -}; -use crate::requests::presence_gateways_post::{ - PresenceGatewayPoster, Request as PresenceGatewayPost, -}; -use crate::requests::presence_mixnodes_post::{ - PresenceMixNodesPoster, Request as PresenceMixNodesPost, -}; -use crate::requests::presence_providers_post::{ - PresenceMixProviderPoster, Request as PresenceProvidersPost, -}; -use crate::requests::presence_topology_get::{ - PresenceTopologyGetRequester, Request as PresenceTopologyRequest, +use crate::requests::health_check_get::Request as HealthCheckRequest; +use crate::requests::metrics_mixes_get::Request as MetricsMixRequest; +use crate::requests::metrics_mixes_post::Request as MetricsMixPost; +use crate::requests::presence_coconodes_post::Request as PresenceCocoNodesPost; +use crate::requests::presence_gateways_post::Request as PresenceGatewayPost; +use crate::requests::presence_mixnodes_post::Request as PresenceMixNodesPost; +use crate::requests::presence_providers_post::Request as PresenceProvidersPost; +use crate::requests::presence_topology_get::Request as PresenceTopologyRequest; + +use metrics::{MixMetric, PersistedMixMetric}; +use presence::{ + coconodes::CocoPresence, gateways::GatewayPresence, mixnodes::MixNodePresence, + providers::MixProviderPresence, Topology, }; +use requests::{health_check_get::HealthCheckResponse, DirectoryGetRequest, DirectoryPostRequest}; pub mod metrics; pub mod presence; @@ -50,35 +47,100 @@ pub trait DirectoryClient { } pub struct Client { - pub health_check: HealthCheckRequest, - pub metrics_mixes: MetricsMixRequest, - pub metrics_post: MetricsMixPost, - pub presence_coconodes_post: PresenceCocoNodesPost, - pub presence_gateway_post: PresenceGatewayPost, - pub presence_mix_nodes_post: PresenceMixNodesPost, - pub presence_providers_post: PresenceProvidersPost, - pub presence_topology: PresenceTopologyRequest, + base_url: String, + reqwest_client: reqwest::Client, } impl DirectoryClient for Client { fn new(config: Config) -> Client { - let health_check = HealthCheckRequest::new(config.base_url.clone()); - let metrics_mixes = MetricsMixRequest::new(config.base_url.clone()); - let metrics_post = MetricsMixPost::new(config.base_url.clone()); - let presence_topology = PresenceTopologyRequest::new(config.base_url.clone()); - let presence_coconodes_post = PresenceCocoNodesPost::new(config.base_url.clone()); - let presence_gateway_post = PresenceGatewayPost::new(config.base_url.clone()); - let presence_mix_nodes_post = PresenceMixNodesPost::new(config.base_url.clone()); - let presence_providers_post = PresenceProvidersPost::new(config.base_url); + let reqwest_client = reqwest::Client::new(); Client { - health_check, - metrics_mixes, - metrics_post, - presence_coconodes_post, - presence_gateway_post, - presence_mix_nodes_post, - presence_providers_post, - presence_topology, + base_url: config.base_url, + reqwest_client, } } } + +impl Client { + async fn post( + &self, + request: R, + ) -> reqwest::Result { + self.reqwest_client + .post(&request.url()) + .json(request.json_payload()) + .send() + .await + } + + async fn get(&self, request: R) -> reqwest::Result { + self.reqwest_client + .get(&request.url()) + .send() + .await? + .json() + .await + } + + pub async fn get_healthcheck(&self) -> reqwest::Result { + let req = HealthCheckRequest::new(&self.base_url); + self.get(req).await + } + + pub async fn post_mix_metrics(&self, metrics: MixMetric) -> reqwest::Result { + let req = MetricsMixPost::new(&self.base_url, metrics); + self.post(req).await + } + + pub async fn get_mix_metrics(&self) -> reqwest::Result> { + let req = MetricsMixRequest::new(&self.base_url); + self.get(req).await + } + + pub async fn post_coconode_presence( + &self, + presence: CocoPresence, + ) -> reqwest::Result { + let req = PresenceCocoNodesPost::new(&self.base_url, presence); + self.post(req).await + } + + pub async fn post_gateway_presence( + &self, + presence: GatewayPresence, + ) -> reqwest::Result { + let req = PresenceGatewayPost::new(&self.base_url, presence); + self.post(req).await + } + + pub async fn post_mixnode_presence( + &self, + presence: MixNodePresence, + ) -> reqwest::Result { + let req = PresenceMixNodesPost::new(&self.base_url, presence); + self.post(req).await + } + + // this should be soft-deprecated as the whole concept of provider will + // be removed in the next topology rework + pub async fn post_provider_presence( + &self, + presence: MixProviderPresence, + ) -> reqwest::Result { + let req = PresenceProvidersPost::new(&self.base_url, presence); + self.post(req).await + } + + pub async fn get_topology(&self) -> reqwest::Result { + let req = PresenceTopologyRequest::new(&self.base_url); + self.get(req).await + } +} + +#[cfg(test)] +pub(crate) fn client_test_fixture(base_url: &str) -> Client { + Client { + base_url: base_url.to_string(), + reqwest_client: reqwest::Client::new(), + } +} diff --git a/common/client-libs/directory-client/src/presence/mod.rs b/common/client-libs/directory-client/src/presence/mod.rs index cea08e9efc..10bc4a3927 100644 --- a/common/client-libs/directory-client/src/presence/mod.rs +++ b/common/client-libs/directory-client/src/presence/mod.rs @@ -12,122 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::requests::presence_topology_get::PresenceTopologyGetRequester; -use crate::{Client, Config, DirectoryClient}; -use log::*; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use topology::{coco, gateway, mix, provider, NymTopology}; - pub mod coconodes; pub mod gateways; pub mod mixnodes; pub mod providers; +pub mod topology; -// Topology shows us the current state of the overall Nym network -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Topology { - pub coco_nodes: Vec, - pub mix_nodes: Vec, - pub mix_provider_nodes: Vec, - pub gateway_nodes: Vec, -} - -impl NymTopology for Topology { - // TODO: this will need some changes to not imply having to make an HTTP request in constructor - fn new(directory_server: String) -> Self { - debug!("Using directory server: {:?}", directory_server); - let directory_config = Config { - base_url: directory_server, - }; - let directory = Client::new(directory_config); - - directory - .presence_topology - .get() - .expect("Failed to retrieve network topology.") - } - - fn new_from_nodes( - mix_nodes: Vec, - mix_provider_nodes: Vec, - coco_nodes: Vec, - gateway_nodes: Vec, - ) -> Self { - Topology { - coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(), - mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(), - mix_provider_nodes: mix_provider_nodes - .into_iter() - .map(|node| node.into()) - .collect(), - gateway_nodes: gateway_nodes.into_iter().map(|node| node.into()).collect(), - } - } - - fn mix_nodes(&self) -> Vec { - self.mix_nodes - .iter() - .filter_map(|x| x.clone().try_into().ok()) - .collect() - } - - fn providers(&self) -> Vec { - self.mix_provider_nodes - .iter() - .map(|x| x.clone().into()) - .collect() - } - - fn gateways(&self) -> Vec { - self.gateway_nodes - .iter() - .map(|x| x.clone().into()) - .collect() - } - - fn coco_nodes(&self) -> Vec { - self.coco_nodes.iter().map(|x| x.clone().into()).collect() - } -} - -#[cfg(test)] -mod converting_mixnode_presence_into_topology_mixnode { - use super::*; - - #[test] - fn it_returns_error_on_unresolvable_hostname() { - let unresolvable_hostname = "foomp.foomp.foomp:1234"; - - let mix_presence = mixnodes::MixNodePresence { - location: "".to_string(), - host: unresolvable_hostname.to_string(), - pub_key: "".to_string(), - layer: 0, - last_seen: 0, - version: "".to_string(), - }; - - let _result: Result = mix_presence.try_into(); - // assert!(result.is_err()) // This fails only for me. Why? - // ¯\_(ツ)_/¯ - works on my machine (and travis) - } - - #[test] - fn it_returns_resolved_ip_on_resolvable_hostname() { - let resolvable_hostname = "nymtech.net:1234"; - - let mix_presence = mixnodes::MixNodePresence { - location: "".to_string(), - host: resolvable_hostname.to_string(), - pub_key: "".to_string(), - layer: 0, - last_seen: 0, - version: "".to_string(), - }; - - let result: Result = mix_presence.try_into(); - assert!(result.is_ok()) - } -} +pub use self::topology::Topology; diff --git a/common/client-libs/directory-client/src/presence/topology.rs b/common/client-libs/directory-client/src/presence/topology.rs new file mode 100644 index 0000000000..a2bc5ca862 --- /dev/null +++ b/common/client-libs/directory-client/src/presence/topology.rs @@ -0,0 +1,133 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{coconodes, gateways, mixnodes, providers}; +use crate::{Client, Config, DirectoryClient}; +use futures::future::BoxFuture; +use log::*; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use topology::{coco, gateway, mix, provider, NymTopology}; +// Topology shows us the current state of the overall Nym network +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Topology { + pub coco_nodes: Vec, + pub mix_nodes: Vec, + pub mix_provider_nodes: Vec, + pub gateway_nodes: Vec, +} + +impl NymTopology for Topology { + // TODO: this will need some changes to not imply having to make an HTTP request in constructor + // TODO2: and also be reworked/removed with the topology re-work + fn new<'a>(directory_server: String) -> BoxFuture<'a, Self> { + debug!("Using directory server: {:?}", directory_server); + let directory_config = Config { + base_url: directory_server, + }; + let directory = Client::new(directory_config); + Box::pin({ + async move { + directory + .get_topology() + .await + .expect("Failed to retrieve network topology.") + } + }) + } + + fn new_from_nodes( + mix_nodes: Vec, + mix_provider_nodes: Vec, + coco_nodes: Vec, + gateway_nodes: Vec, + ) -> Self { + Topology { + coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(), + mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(), + mix_provider_nodes: mix_provider_nodes + .into_iter() + .map(|node| node.into()) + .collect(), + gateway_nodes: gateway_nodes.into_iter().map(|node| node.into()).collect(), + } + } + + fn mix_nodes(&self) -> Vec { + self.mix_nodes + .iter() + .filter_map(|x| x.clone().try_into().ok()) + .collect() + } + + fn providers(&self) -> Vec { + self.mix_provider_nodes + .iter() + .map(|x| x.clone().into()) + .collect() + } + + fn gateways(&self) -> Vec { + self.gateway_nodes + .iter() + .map(|x| x.clone().into()) + .collect() + } + + fn coco_nodes(&self) -> Vec { + self.coco_nodes.iter().map(|x| x.clone().into()).collect() + } +} + +#[cfg(test)] +mod converting_mixnode_presence_into_topology_mixnode { + use super::*; + + #[test] + fn it_returns_error_on_unresolvable_hostname() { + let unresolvable_hostname = "foomp.foomp.foomp:1234"; + + let mix_presence = mixnodes::MixNodePresence { + location: "".to_string(), + host: unresolvable_hostname.to_string(), + pub_key: "".to_string(), + layer: 0, + last_seen: 0, + version: "".to_string(), + }; + + let result: Result = mix_presence.try_into(); + assert!(result.is_err()) // This fails only for me. Why? + // ¯\_(ツ)_/¯ - works on my machine (and travis) + // Is it still broken? + } + + #[test] + fn it_returns_resolved_ip_on_resolvable_hostname() { + let resolvable_hostname = "nymtech.net:1234"; + + let mix_presence = mixnodes::MixNodePresence { + location: "".to_string(), + host: resolvable_hostname.to_string(), + pub_key: "".to_string(), + layer: 0, + last_seen: 0, + version: "".to_string(), + }; + + let result: Result = mix_presence.try_into(); + assert!(result.is_ok()) + } +} diff --git a/common/client-libs/directory-client/src/requests/health_check_get.rs b/common/client-libs/directory-client/src/requests/health_check_get.rs index 7728e80c25..93fbe91336 100644 --- a/common/client-libs/directory-client/src/requests/health_check_get.rs +++ b/common/client-libs/directory-client/src/requests/health_check_get.rs @@ -12,49 +12,53 @@ // See the License for the specific language governing permissions and // limitations under the License. -use reqwest::Response; +use super::{DirectoryGetRequest, DirectoryRequest}; +use serde::{Deserialize, Serialize}; + +const PATH: &str = "/api/healthcheck"; + +#[derive(Deserialize, Serialize)] +pub struct HealthCheckResponse { + pub ok: bool, +} pub struct Request { base_url: String, path: String, } -pub trait HealthCheckRequester { - fn new(base_url: String) -> Self; - fn get(&self) -> Result; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl HealthCheckRequester for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/healthcheck".to_string(), - } - } +impl DirectoryGetRequest for Request { + type JSONResponse = HealthCheckResponse; - fn get(&self) -> Result { - let url = format!("{}{}", self.base_url, self.path); - reqwest::get(&url) + fn new(base_url: &str) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + } } } #[cfg(test)] mod healthcheck_requests { - use super::*; - - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - #[should_panic] - fn it_returns_an_error() { + #[tokio::test] + async fn it_returns_an_error() { let _m = mock("GET", "/api/healthcheck").with_status(400).create(); - let req = Request::new(mockito::server_url()); - assert!(req.get().is_err()); + let client = client_test_fixture(&mockito::server_url()); + let res = client.get_healthcheck().await; + assert!(res.is_err()); _m.assert(); } } @@ -63,11 +67,18 @@ mod healthcheck_requests { mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_200_status() { - let _m = mock("GET", "/api/healthcheck").with_status(200).create(); - let req = Request::new(mockito::server_url()); - assert!(req.get().is_ok()); + #[tokio::test] + async fn it_returns_a_response_with_200_status() { + let json = r#"{ + "ok": true + }"#; + let _m = mock("GET", "/api/healthcheck") + .with_status(200) + .with_body(json) + .create(); + let client = client_test_fixture(&mockito::server_url()); + let res = client.get_healthcheck().await; + assert!(res.unwrap().ok); _m.assert(); } } diff --git a/common/client-libs/directory-client/src/requests/metrics_mixes_get.rs b/common/client-libs/directory-client/src/requests/metrics_mixes_get.rs index 8c31b76f3a..09b8ade2ce 100644 --- a/common/client-libs/directory-client/src/requests/metrics_mixes_get.rs +++ b/common/client-libs/directory-client/src/requests/metrics_mixes_get.rs @@ -12,50 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{DirectoryGetRequest, DirectoryRequest}; use crate::metrics::PersistedMixMetric; +const PATH: &str = "/api/metrics/mixes"; + pub struct Request { base_url: String, path: String, } -pub trait MetricsMixRequester { - fn new(base_url: String) -> Self; - fn get(&self) -> Result, reqwest::Error>; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl MetricsMixRequester for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/metrics/mixes".to_string(), - } - } +impl DirectoryGetRequest for Request { + type JSONResponse = Vec; - fn get(&self) -> Result, reqwest::Error> { - let url = format!("{}{}", self.base_url, self.path); - let mix_metric_vec = reqwest::get(&url)?.json()?; - Ok(mix_metric_vec) + fn new(base_url: &str) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + } } } #[cfg(test)] mod metrics_get_request { use super::*; - - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - #[should_panic] - fn it_returns_an_error() { - let _m = mock("GET", "/api/metrics/mixes").with_status(400).create(); - let req = Request::new(mockito::server_url()); - req.get().unwrap(); + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("GET", PATH).with_status(400).create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client.get_mix_metrics().await; + assert!(result.is_err()); _m.assert(); } } @@ -63,20 +62,15 @@ mod metrics_get_request { #[cfg(test)] mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_200_status_and_a_correct_topology() { + #[tokio::test] + async fn it_returns_a_response_with_200_status_and_a_correct_metrics() { let json = fixtures::mix_metrics_response_json(); - let _m = mock("GET", "/api/metrics/mixes") - .with_status(200) - .with_body(json) - .create(); - let req = Request::new(mockito::server_url()); - let result = req.get(); - assert_eq!(true, result.is_ok()); - assert_eq!( - 1576061080635800000, - result.unwrap().first().unwrap().timestamp - ); + let _m = mock("GET", PATH).with_status(200).with_body(json).create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client.get_mix_metrics().await; + let unwrapped = result.unwrap(); + + assert_eq!(10, unwrapped.first().unwrap().received); _m.assert(); } } @@ -84,7 +78,7 @@ mod metrics_get_request { #[cfg(test)] mod fixtures { #[cfg(test)] - pub fn mix_metrics_response_json() -> String { + pub fn mix_metrics_response_json() -> &'static str { r#"[ { "pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=", @@ -247,7 +241,6 @@ mod metrics_get_request { "timestamp": 1576061080501732900 } ]"# - .to_string() } } } diff --git a/common/client-libs/directory-client/src/requests/metrics_mixes_post.rs b/common/client-libs/directory-client/src/requests/metrics_mixes_post.rs index 1f0614ae93..c6d4600a97 100644 --- a/common/client-libs/directory-client/src/requests/metrics_mixes_post.rs +++ b/common/client-libs/directory-client/src/requests/metrics_mixes_post.rs @@ -12,52 +12,53 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{DirectoryPostRequest, DirectoryRequest}; use crate::metrics::MixMetric; -use reqwest::Response; + +const PATH: &str = "/api/metrics/mixes"; pub struct Request { base_url: String, path: String, + payload: MixMetric, } -pub trait MetricsMixPoster { - fn new(base_url: String) -> Self; - fn post(&self, metric: &MixMetric) -> Result; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl MetricsMixPoster for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/metrics/mixes".to_string(), - } +impl DirectoryPostRequest for Request { + type Payload = MixMetric; + fn json_payload(&self) -> &MixMetric { + &self.payload } - fn post(&self, metric: &MixMetric) -> Result { - let url = format!("{}{}", self.base_url, self.path); - let client = reqwest::Client::new(); - let mix_metric_vec = client.post(&url).json(&metric).send()?; - Ok(mix_metric_vec) + fn new(base_url: &str, payload: Self::Payload) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + payload, + } } } #[cfg(test)] -mod metrics_get_request { +mod metrics_post_request { use super::*; - - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - fn it_returns_an_error() { - let _m = mock("POST", "/api/metrics/mixes").with_status(400).create(); - let req = Request::new(mockito::server_url()); - let metric = fixtures::new_metric(); - let result = req.post(&metric); + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("POST", PATH).with_status(400).create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client.post_mix_metrics(fixtures::new_metric()).await; assert_eq!(400, result.unwrap().status()); _m.assert(); } @@ -66,17 +67,16 @@ mod metrics_get_request { #[cfg(test)] mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_200() { + #[tokio::test] + async fn it_returns_a_response_with_200() { let json = fixtures::mix_metrics_response_json(); let _m = mock("POST", "/api/metrics/mixes") .with_status(201) .with_body(json) .create(); - let req = Request::new(mockito::server_url()); - let metric = fixtures::new_metric(); - let result = req.post(&metric); - assert_eq!(true, result.is_ok()); + let client = client_test_fixture(&mockito::server_url()); + let result = client.post_mix_metrics(fixtures::new_metric()).await; + assert!(result.is_ok()); _m.assert(); } } diff --git a/common/client-libs/directory-client/src/requests/mod.rs b/common/client-libs/directory-client/src/requests/mod.rs index 3ac0f648fa..39b712dff9 100644 --- a/common/client-libs/directory-client/src/requests/mod.rs +++ b/common/client-libs/directory-client/src/requests/mod.rs @@ -20,3 +20,26 @@ pub mod presence_gateways_post; pub mod presence_mixnodes_post; pub mod presence_providers_post; pub mod presence_topology_get; + +use serde::{de::DeserializeOwned, Serialize}; + +pub(crate) trait DirectoryRequest { + fn url(&self) -> String; +} + +pub(crate) trait DirectoryGetRequest: DirectoryRequest { + // perhaps the name of this is not the best because it's technically not a JSON, + // but something that can be deserialised from JSON. + // I'm open to all suggestions on how to rename it + type JSONResponse: DeserializeOwned; + + fn new(base_url: &str) -> Self; +} + +pub(crate) trait DirectoryPostRequest: DirectoryRequest { + // Similarly this, it's something that can be serialized into a JSON + type Payload: Serialize + ?Sized; + + fn new(base_url: &str, payload: Self::Payload) -> Self; + fn json_payload(&self) -> &Self::Payload; +} diff --git a/common/client-libs/directory-client/src/requests/presence_coconodes_post.rs b/common/client-libs/directory-client/src/requests/presence_coconodes_post.rs index 91ef7ff6c5..71832cbed5 100644 --- a/common/client-libs/directory-client/src/requests/presence_coconodes_post.rs +++ b/common/client-libs/directory-client/src/requests/presence_coconodes_post.rs @@ -12,54 +12,54 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{DirectoryPostRequest, DirectoryRequest}; use crate::presence::coconodes::CocoPresence; -use reqwest::Response; + +const PATH: &str = "/api/presence/coconodes"; pub struct Request { base_url: String, path: String, + payload: CocoPresence, } -pub trait PresenceCocoNodesPoster { - fn new(base_url: String) -> Self; - fn post(&self, presence: &CocoPresence) -> Result; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl PresenceCocoNodesPoster for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/presence/coconodes".to_string(), - } +impl DirectoryPostRequest for Request { + type Payload = CocoPresence; + fn json_payload(&self) -> &CocoPresence { + &self.payload } - fn post(&self, presence: &CocoPresence) -> Result { - let url = format!("{}{}", self.base_url, self.path); - let client = reqwest::Client::new(); - let p = client.post(&url).json(&presence).send()?; - Ok(p) + fn new(base_url: &str, payload: Self::Payload) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + payload, + } } } #[cfg(test)] -mod metrics_get_request { +mod presence_coconodes_post_request { use super::*; - - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - fn it_returns_an_error() { - let _m = mock("POST", "/api/presence/coconodes") - .with_status(400) - .create(); - let req = Request::new(mockito::server_url()); + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("POST", PATH).with_status(400).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); + let result = client.post_coconode_presence(presence).await; assert_eq!(400, result.unwrap().status()); _m.assert(); } @@ -68,19 +68,16 @@ mod metrics_get_request { #[cfg(test)] mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_201() { + #[tokio::test] + async fn it_returns_a_response_with_201() { let json = r#"{ "ok": true }"#; - let _m = mock("POST", "/api/presence/coconodes") - .with_status(201) - .with_body(json) - .create(); - let req = Request::new(mockito::server_url()); + let _m = mock("POST", PATH).with_status(201).with_body(json).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); - assert_eq!(true, result.is_ok()); + let result = client.post_coconode_presence(presence).await; + assert!(result.is_ok()); _m.assert(); } } diff --git a/common/client-libs/directory-client/src/requests/presence_gateways_post.rs b/common/client-libs/directory-client/src/requests/presence_gateways_post.rs index c15779f2a5..1ed7adc5e9 100644 --- a/common/client-libs/directory-client/src/requests/presence_gateways_post.rs +++ b/common/client-libs/directory-client/src/requests/presence_gateways_post.rs @@ -12,54 +12,54 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{DirectoryPostRequest, DirectoryRequest}; use crate::presence::gateways::GatewayPresence; -use reqwest::Response; + +const PATH: &str = "/api/presence/gateways"; pub struct Request { base_url: String, path: String, + payload: GatewayPresence, } -pub trait PresenceGatewayPoster { - fn new(base_url: String) -> Self; - fn post(&self, presence: &GatewayPresence) -> Result; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl PresenceGatewayPoster for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/presence/gateways".to_string(), - } +impl DirectoryPostRequest for Request { + type Payload = GatewayPresence; + fn json_payload(&self) -> &GatewayPresence { + &self.payload } - fn post(&self, presence: &GatewayPresence) -> Result { - let url = format!("{}{}", self.base_url, self.path); - let client = reqwest::Client::new(); - let p = client.post(&url).json(&presence).send()?; - Ok(p) + fn new(base_url: &str, payload: Self::Payload) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + payload, + } } } #[cfg(test)] -mod metrics_get_request { +mod presence_gateways_post_request { use super::*; - - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - fn it_returns_an_error() { - let _m = mock("POST", "/api/presence/gateways") - .with_status(400) - .create(); - let req = Request::new(mockito::server_url()); + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("POST", PATH).with_status(400).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); + let result = client.post_gateway_presence(presence).await; assert_eq!(400, result.unwrap().status()); _m.assert(); } @@ -68,19 +68,16 @@ mod metrics_get_request { #[cfg(test)] mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_201() { + #[tokio::test] + async fn it_returns_a_response_with_201() { let json = r#"{ "ok": true }"#; - let _m = mock("POST", "/api/presence/gateways") - .with_status(201) - .with_body(json) - .create(); - let req = Request::new(mockito::server_url()); + let _m = mock("POST", PATH).with_status(201).with_body(json).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); - assert_eq!(true, result.is_ok()); + let result = client.post_gateway_presence(presence).await; + assert!(result.is_ok()); _m.assert(); } } diff --git a/common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs b/common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs index f119c898c5..6a3af4eb00 100644 --- a/common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs +++ b/common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs @@ -12,54 +12,54 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{DirectoryPostRequest, DirectoryRequest}; use crate::presence::mixnodes::MixNodePresence; -use reqwest::Response; + +const PATH: &str = "/api/presence/mixnodes"; pub struct Request { base_url: String, path: String, + payload: MixNodePresence, } -pub trait PresenceMixNodesPoster { - fn new(base_url: String) -> Self; - fn post(&self, presence: &MixNodePresence) -> Result; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl PresenceMixNodesPoster for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/presence/mixnodes".to_string(), - } +impl DirectoryPostRequest for Request { + type Payload = MixNodePresence; + fn json_payload(&self) -> &MixNodePresence { + &self.payload } - fn post(&self, presence: &MixNodePresence) -> Result { - let url = format!("{}{}", self.base_url, self.path); - let client = reqwest::Client::new(); - let p = client.post(&url).json(&presence).send()?; - Ok(p) + fn new(base_url: &str, payload: Self::Payload) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + payload, + } } } #[cfg(test)] -mod metrics_get_request { +mod presence_mixnodes_post_request { use super::*; - - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - fn it_returns_an_error() { - let _m = mock("POST", "/api/presence/mixnodes") - .with_status(400) - .create(); - let req = Request::new(mockito::server_url()); + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("POST", PATH).with_status(400).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); + let result = client.post_mixnode_presence(presence).await; assert_eq!(400, result.unwrap().status()); _m.assert(); } @@ -68,19 +68,16 @@ mod metrics_get_request { #[cfg(test)] mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_201() { + #[tokio::test] + async fn it_returns_a_response_with_201() { let json = r#"{ "ok": true }"#; - let _m = mock("POST", "/api/presence/mixnodes") - .with_status(201) - .with_body(json) - .create(); - let req = Request::new(mockito::server_url()); + let _m = mock("POST", PATH).with_status(201).with_body(json).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); - assert_eq!(true, result.is_ok()); + let result = client.post_mixnode_presence(presence).await; + assert!(result.is_ok()); _m.assert(); } } diff --git a/common/client-libs/directory-client/src/requests/presence_providers_post.rs b/common/client-libs/directory-client/src/requests/presence_providers_post.rs index 3c198352c1..c8583596db 100644 --- a/common/client-libs/directory-client/src/requests/presence_providers_post.rs +++ b/common/client-libs/directory-client/src/requests/presence_providers_post.rs @@ -12,54 +12,54 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{DirectoryPostRequest, DirectoryRequest}; use crate::presence::providers::MixProviderPresence; -use reqwest::Response; + +const PATH: &str = "/api/presence/mixproviders"; pub struct Request { base_url: String, path: String, + payload: MixProviderPresence, } -pub trait PresenceMixProviderPoster { - fn new(base_url: String) -> Self; - fn post(&self, presence: &MixProviderPresence) -> Result; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl PresenceMixProviderPoster for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/presence/mixproviders".to_string(), - } +impl DirectoryPostRequest for Request { + type Payload = MixProviderPresence; + fn json_payload(&self) -> &MixProviderPresence { + &self.payload } - fn post(&self, presence: &MixProviderPresence) -> Result { - let url = format!("{}{}", self.base_url, self.path); - let client = reqwest::Client::new(); - let p = client.post(&url).json(&presence).send()?; - Ok(p) + fn new(base_url: &str, payload: Self::Payload) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + payload, + } } } #[cfg(test)] -mod metrics_get_request { +mod presence_providers_post_request { use super::*; - - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - fn it_returns_an_error() { - let _m = mock("POST", "/api/presence/mixproviders") - .with_status(400) - .create(); - let req = Request::new(mockito::server_url()); + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("POST", PATH).with_status(400).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); + let result = client.post_provider_presence(presence).await; assert_eq!(400, result.unwrap().status()); _m.assert(); } @@ -68,19 +68,16 @@ mod metrics_get_request { #[cfg(test)] mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_201() { + #[tokio::test] + async fn it_returns_a_response_with_201() { let json = r#"{ "ok": true }"#; - let _m = mock("POST", "/api/presence/mixproviders") - .with_status(201) - .with_body(json) - .create(); - let req = Request::new(mockito::server_url()); + let _m = mock("POST", PATH).with_status(201).with_body(json).create(); + let client = client_test_fixture(&mockito::server_url()); let presence = fixtures::new_presence(); - let result = req.post(&presence); - assert_eq!(true, result.is_ok()); + let result = client.post_provider_presence(presence).await; + assert!(result.is_ok()); _m.assert(); } } diff --git a/common/client-libs/directory-client/src/requests/presence_topology_get.rs b/common/client-libs/directory-client/src/requests/presence_topology_get.rs index 21b714ff4a..d13a0d1f8e 100644 --- a/common/client-libs/directory-client/src/requests/presence_topology_get.rs +++ b/common/client-libs/directory-client/src/requests/presence_topology_get.rs @@ -12,73 +12,71 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{DirectoryGetRequest, DirectoryRequest}; use crate::presence::Topology; +const PATH: &str = "/api/presence/topology"; + pub struct Request { base_url: String, path: String, } -pub trait PresenceTopologyGetRequester { - fn new(base_url: String) -> Self; - fn get(&self) -> Result; +impl DirectoryRequest for Request { + fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } -impl PresenceTopologyGetRequester for Request { - fn new(base_url: String) -> Self { - Request { - base_url, - path: "/api/presence/topology".to_string(), - } - } +impl DirectoryGetRequest for Request { + type JSONResponse = Topology; - fn get(&self) -> Result { - let url = format!("{}{}", self.base_url, self.path); - let topology: Topology = reqwest::get(&url)?.json()?; - Ok(topology) + fn new(base_url: &str) -> Self { + Request { + base_url: base_url.to_string(), + path: PATH.to_string(), + } } } #[cfg(test)] mod topology_requests { use super::*; - #[cfg(test)] + use crate::client_test_fixture; use mockito::mock; + #[cfg(test)] mod on_a_400_status { use super::*; - #[test] - #[should_panic] - fn it_panics() { - let _m = mock("GET", "/api/presence/topology") + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("GET", PATH) .with_status(400) .with_body("bad body") .create(); - let req = Request::new(mockito::server_url()); - req.get().unwrap(); + let client = client_test_fixture(&mockito::server_url()); + let result = client.get_topology().await; + assert!(result.is_err()); _m.assert(); } } #[cfg(test)] mod on_a_200 { use super::*; - #[test] - fn it_returns_a_response_with_200_status_and_a_correct_topology() { + #[tokio::test] + async fn it_returns_a_response_with_200_status_and_a_correct_topology() { let json = fixtures::topology_response_json(); - let _m = mock("GET", "/api/presence/topology") - .with_status(200) - .with_body(json) - .create(); - let req = Request::new(mockito::server_url()); - let result = req.get(); - assert!(result.is_ok()); + let _m = mock("GET", PATH).with_status(200).with_body(json).create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client.get_topology().await; assert_eq!( - 1575915097085539300, + 1_575_915_097_085_539_300, result.unwrap().coco_nodes.first().unwrap().last_seen ); _m.assert(); } } + #[cfg(test)] pub mod fixtures { #[cfg(test)] diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index f782505d78..46186e1714 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] bs58 = "0.3.0" itertools = "0.8.2" +futures = "0.3" log = "0.4" pretty_env_logger = "0.3" rand = "0.7.2" diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index 992f9a25ac..ea8c7d5fe8 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::filter::VersionFilterable; +use futures::future::BoxFuture; use itertools::Itertools; use nymsphinx::Node as SphinxNode; use rand::seq::IteratorRandom; @@ -28,7 +29,10 @@ pub mod provider; // TODO: Figure out why 'Clone' was required to have 'TopologyAccessor' working // even though it only contains an Arc pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone { - fn new(directory_server: String) -> Self; + // this is just a temporary work-around to make current code work without having to + // do major topology rewrites now. + // This will be removed once topology is re-worked + fn new<'a>(directory_server: String) -> BoxFuture<'a, Self>; fn new_from_nodes( mix_nodes: Vec, mix_provider_nodes: Vec, diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 529a8b3dc5..0a0d780c4f 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -19,8 +19,6 @@ use crate::node::Gateway; use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::encryption; -use directory_client::requests::presence_topology_get::PresenceTopologyGetRequester; -use directory_client::DirectoryClient; use pemstore::pemstore::PemStore; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { @@ -140,27 +138,6 @@ fn load_sphinx_keys(config_file: &Config) -> encryption::KeyPair { sphinx_keypair } -fn check_if_same_ip_gateway_exists( - directory_server: String, - announced_mix_host: String, - announced_clients_host: String, -) -> Option { - let directory_client_cfg = directory_client::Config::new(directory_server); - let topology = directory_client::Client::new(directory_client_cfg) - .presence_topology - .get() - .expect("Failed to retrieve network topology"); - - let existing_gateways = topology.gateway_nodes; - existing_gateways - .iter() - .find(|node| { - node.mixnet_listener == announced_mix_host - || node.client_listener == announced_clients_host - }) - .map(|node| node.pub_key.clone()) -} - pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); @@ -172,18 +149,6 @@ pub fn execute(matches: &ArgMatches) { config = override_config(config, matches); - if let Some(duplicate_gateway_key) = check_if_same_ip_gateway_exists( - config.get_presence_directory_server(), - config.get_mix_announce_address(), - config.get_clients_announce_address(), - ) { - println!( - "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", - duplicate_gateway_key - ); - return; - } - let sphinx_keypair = load_sphinx_keys(&config); let mix_listening_ip_string = config.get_mix_listening_address().ip().to_string(); diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 4eb912b241..87e289a90d 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -18,6 +18,7 @@ use crate::node::client_handling::websocket; use crate::node::mixnet_handling::sender::{OutboundMixMessageSender, PacketForwarder}; use crate::node::storage::{inboxes, ClientLedger}; use crypto::encryption; +use directory_client::DirectoryClient; use log::*; use std::sync::Arc; use tokio::runtime::Runtime; @@ -137,6 +138,26 @@ impl Gateway { ); } + async fn check_if_same_ip_gateway_exists(&self) -> Option { + let announced_mix_host = self.config.get_mix_announce_address(); + let announced_clients_host = self.config.get_clients_announce_address(); + let directory_client_cfg = + directory_client::Config::new(self.config.get_presence_directory_server()); + let topology = directory_client::Client::new(directory_client_cfg) + .get_topology() + .await + .expect("Failed to retrieve network topology"); + + let existing_gateways = topology.gateway_nodes; + existing_gateways + .iter() + .find(|node| { + node.mixnet_listener == announced_mix_host + || node.client_listener == announced_clients_host + }) + .map(|node| node.pub_key.clone()) + } + // Rather than starting all futures with explicit `&Handle` argument, let's see how it works // out if we make it implicit using `tokio::spawn` inside Runtime context. // Basically more or less equivalent of using #[tokio::main] attribute. @@ -145,6 +166,17 @@ impl Gateway { let mut runtime = Runtime::new().unwrap(); runtime.block_on(async { + + if let Some(duplicate_gateway_key) = self.check_if_same_ip_gateway_exists().await { + error!( + "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", + duplicate_gateway_key + ); + return; + } + + + let mix_forwarding_channel = self.start_packet_forwarder(); let clients_handler_sender = self.start_clients_handler(); diff --git a/gateway/src/node/presence/mod.rs b/gateway/src/node/presence/mod.rs index 3ab06c487d..ca0eeeb125 100644 --- a/gateway/src/node/presence/mod.rs +++ b/gateway/src/node/presence/mod.rs @@ -15,7 +15,6 @@ use crate::built_info; use crate::node::storage::ClientLedger; use directory_client::presence::gateways::{GatewayClient, GatewayPresence}; -use directory_client::requests::presence_gateways_post::PresenceGatewayPoster; use directory_client::DirectoryClient; use log::{error, trace}; use std::time::Duration; @@ -98,8 +97,8 @@ impl Notifier { } } - pub(crate) fn notify(&self, presence: GatewayPresence) { - match self.net_client.presence_gateway_post.post(&presence) { + async fn notify(&self, presence: GatewayPresence) { + match self.net_client.post_gateway_presence(presence).await { Err(err) => error!("failed to send presence - {:?}", err), Ok(_) => trace!("sent presence information"), } @@ -111,7 +110,7 @@ impl Notifier { // set the deadline in the future let sending_delay = tokio::time::delay_for(self.sending_delay); let presence = self.make_presence().await; - self.notify(presence); + self.notify(presence).await; // wait for however much is left sending_delay.await; } diff --git a/mixnode/src/node/metrics.rs b/mixnode/src/node/metrics.rs index 1d8f7ea3c2..1ce9315703 100644 --- a/mixnode/src/node/metrics.rs +++ b/mixnode/src/node/metrics.rs @@ -13,7 +13,6 @@ // limitations under the License. use directory_client::metrics::MixMetric; -use directory_client::requests::metrics_mixes_post::MetricsMixPoster; use directory_client::DirectoryClient; use futures::channel::mpsc; use futures::lock::Mutex; @@ -141,11 +140,15 @@ impl MetricsSender { self.metrics_informer.log_report_stats(received, &sent); self.metrics_informer.try_log_running_stats(); - match self.directory_client.metrics_post.post(&MixMetric { - pub_key: self.pub_key_str.clone(), - received, - sent, - }) { + match self + .directory_client + .post_mix_metrics(MixMetric { + pub_key: self.pub_key_str.clone(), + received, + sent, + }) + .await + { Err(err) => error!("failed to send metrics - {:?}", err), Ok(_) => debug!("sent metrics information"), } diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 7bbe8bebc4..7002ea2d49 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -100,9 +100,11 @@ impl MixNode { .start(self.runtime.handle()) } - fn check_if_same_ip_node_exists(&self) -> Option { + fn check_if_same_ip_node_exists(&mut self) -> Option { // TODO: once we change to graph topology this here will need to be updated! - let topology = Topology::new(self.config.get_presence_directory_server()); + let topology = self + .runtime + .block_on(Topology::new(self.config.get_presence_directory_server())); let existing_mixes_presence = topology.mix_nodes; existing_mixes_presence .iter() diff --git a/mixnode/src/node/presence.rs b/mixnode/src/node/presence.rs index 766e4e0079..e31abd06ab 100644 --- a/mixnode/src/node/presence.rs +++ b/mixnode/src/node/presence.rs @@ -14,7 +14,6 @@ use crate::built_info; use directory_client::presence::mixnodes::MixNodePresence; -use directory_client::requests::presence_mixnodes_post::PresenceMixNodesPoster; use directory_client::DirectoryClient; use log::{error, trace}; use std::time::Duration; @@ -77,8 +76,12 @@ impl Notifier { } } - fn notify(&self) { - match self.net_client.presence_mix_nodes_post.post(&self.presence) { + async fn notify(&self) { + match self + .net_client + .post_mixnode_presence(self.presence.clone()) + .await + { Err(err) => error!("failed to send presence - {:?}", err), Ok(_) => trace!("sent presence information"), } @@ -89,7 +92,7 @@ impl Notifier { loop { // set the deadline in the future let sending_delay = tokio::time::delay_for(self.sending_delay); - self.notify(); + self.notify().await; // wait for however much is left sending_delay.await; }