From 713a7a6fe8599ebbf207544f55b50bc6616d5f3e Mon Sep 17 00:00:00 2001 From: Jedrzej Stuczynski Date: Wed, 29 Jan 2020 16:04:57 +0000 Subject: [PATCH] Initial fields for the client config --- nym-client/Cargo.toml | 6 +- nym-client/src/client/mod.rs | 3 + nym-client/src/config/mod.rs | 227 +++++++++++++++++++++++++----- nym-client/src/config/template.rs | 109 +++++++++----- 4 files changed, 273 insertions(+), 72 deletions(-) diff --git a/nym-client/Cargo.toml b/nym-client/Cargo.toml index 9a8873d7af..09939462d3 100644 --- a/nym-client/Cargo.toml +++ b/nym-client/Cargo.toml @@ -18,7 +18,6 @@ curve25519-dalek = "1.2.3" dirs = "2.0.2" dotenv = "0.15.0" futures = "0.3.1" -handlebars = "3.0.1" log = "0.4" pem = "0.7.0" pretty_env_logger = "0.3" @@ -30,6 +29,7 @@ tungstenite = "0.9.2" ## internal addressing = {path = "../common/addressing" } +config = {path = "../common/config"} crypto = {path = "../common/crypto"} directory-client = { path = "../common/clients/directory-client" } healthcheck = { path = "../common/healthcheck" } @@ -48,3 +48,7 @@ tokio-tungstenite = { git = "https://github.com/snapview/tokio-tungstenite", rev [build-dependencies] built = "0.3.2" + +[features] +qa = [] +local = [] \ No newline at end of file diff --git a/nym-client/src/client/mod.rs b/nym-client/src/client/mod.rs index 4d4e0da408..daa7865271 100644 --- a/nym-client/src/client/mod.rs +++ b/nym-client/src/client/mod.rs @@ -8,6 +8,7 @@ use directory_client::presence::Topology; use futures::channel::mpsc; use futures::join; use log::*; +use serde::{Deserialize, Serialize}; use sfw_provider_requests::AuthToken; use sphinx::route::Destination; use std::marker::PhantomData; @@ -31,6 +32,8 @@ const FETCH_MESSAGES_DELAY: f64 = 1.0; // seconds; const TOPOLOGY_REFRESH_RATE: f64 = 10.0; // seconds +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] pub enum SocketType { TCP, WebSocket, diff --git a/nym-client/src/config/mod.rs b/nym-client/src/config/mod.rs index 93c8b0af32..5ae2bd1e96 100644 --- a/nym-client/src/config/mod.rs +++ b/nym-client/src/config/mod.rs @@ -1,82 +1,243 @@ -use crate::config::persistance::pathfinder::ClientPathfinder; +use crate::client::SocketType; +use crate::config::template::config_template; +use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; pub mod persistance; mod template; -#[derive(Serialize, Deserialize)] +// all of the below are defined in seconds +const DEFAULT_LOOP_COVER_SENDING_AVERAGE_DELAY: f64 = 1.0; +const DEFAULT_MESSAGE_SENDING_AVERAGE_DELAY: f64 = 0.5; +const DEFAULT_AVERAGE_PACKET_DELAY: f64 = 0.2; +const DEFAULT_FETCH_MESSAGES_DELAY: f64 = 1.0; +const DEFAULT_TOPOLOGY_REFRESH_RATE: f64 = 10.0; + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] pub struct Config { client: Client, + socket: Socket, + + #[serde(default)] + logging: Logging, + #[serde(default)] + debug: Debug, } -impl Default for Config { - fn default() -> Self { - unimplemented!() +impl NymConfig for Config { + fn template() -> &'static str { + config_template() + } + + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("clients") + } + + fn root_directory(&self) -> PathBuf { + self.client.nym_root_directory.clone() + } + + fn config_directory(&self) -> PathBuf { + self.client + .nym_root_directory + .join(&self.client.id) + .join("config") + } + + fn data_directory(&self) -> PathBuf { + self.client + .nym_root_directory + .join(&self.client.id) + .join("data") } } -#[derive(Serialize, Deserialize)] -pub struct Client { - // nym_home_directory specifies absolute path to the home nym Clients directory. - // It is expected to use default value and hence .toml file should not redefine this field. - nym_home_directory: String, +impl Config { + pub fn new(id: String) -> Self { + Config::default().with_id(id) + } - // ID specifies the human readable ID of this particular client. - // If not provided a pseudorandom id will be generated instead. + pub fn with_id(mut self, id: String) -> Self { + if self.client.private_identity_key_file.as_os_str().is_empty() { + self.client.private_identity_key_file = + self::Client::default_private_identity_key_file(&id); + } + if self.client.public_identity_key_file.as_os_str().is_empty() { + self.client.public_identity_key_file = + self::Client::default_public_identity_key_file(&id); + } + self.client.id = id; + self + } + + pub fn with_provider_id(mut self, id: String) -> Self { + self.client.provider_id = id; + self + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Client { + /// ID specifies the human readable ID of this particular client. id: String, - // URL to the directory server. + /// URL to the directory server. directory_server: String, - // Path to file containing private identity key. + /// Path to file containing private identity key. private_identity_key_file: PathBuf, - // Path to file containing public identity key. + /// Path to file containing public identity key. public_identity_key_file: PathBuf, - // mix_apps_directory specifies directory for mixapps, such as a chat client, - // to store their app-specific data. - mix_apps_directory: String, - - // provider_id specifies ID of the provider to which the client should send messages. - // If initially omitted, a random provider will be chosen from the available topology. + /// provider_id specifies ID of the provider to which the client should send messages. + /// If initially omitted, a random provider will be chosen from the available topology. provider_id: String, + + /// nym_home_directory specifies absolute path to the home nym Clients directory. + /// It is expected to use default value and hence .toml file should not redefine this field. + nym_root_directory: PathBuf, } impl Default for Client { fn default() -> Self { - unimplemented!() + // there must be explicit checks for whether id is not empty later + Client { + id: "".to_string(), + directory_server: Self::default_directory_server(), + private_identity_key_file: Default::default(), + public_identity_key_file: Default::default(), + provider_id: "".to_string(), + nym_root_directory: Config::default_root_directory(), + } } } impl Client { - fn new() -> Self { - Default::default() + fn default_directory_server() -> String { + #[cfg(feature = "qa")] + return "https://qa-directory.nymtech.net".to_string(); + #[cfg(feature = "local")] + return "http://localhost:8080".to_string(); + + "https://directory.nymtech.net".to_string() } - fn with_id(mut self, id: String) -> Self { - self.id = id; - self + fn default_private_identity_key_file(id: &str) -> PathBuf { + Config::default_data_directory() + .join(id) + .join("private_identity.pem") } - fn with_pathfinder(mut self, pathfinder: ClientPathfinder) -> Self { - // pub config_dir: PathBuf, - // pub private_mix_key: PathBuf, - // pub public_mix_key: PathBuf, - self + fn default_public_identity_key_file(id: &str) -> PathBuf { + Config::default_data_directory() + .join(id) + .join("public_identity.pem") } } +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socket { + socket_type: SocketType, + listening_port: u64, +} + +impl Default for Socket { + fn default() -> Self { + Socket { + socket_type: SocketType::None, + listening_port: 0, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] pub struct Logging {} impl Default for Logging { fn default() -> Self { - unimplemented!() + Logging {} + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Debug { + /// The parameter of Poisson distribution determining how long, on average, + /// sent packet is going to be delayed at any given mix node. + /// So for a packet going through three mix nodes, on average, it will take three times this value + /// until the packet reaches its destination. + /// The provided value is interpreted as seconds. + average_packet_delay: f64, + + /// The parameter of Poisson distribution determining how long, on average, + /// it is going to take for another loop cover traffic message to be sent. + /// If set to a negative value, the loop cover traffic stream will be disabled. + /// The provided value is interpreted as seconds. + loop_cover_traffic_average_delay: f64, + + /// The uniform delay every which clients are querying the providers for received packets. + /// If set to a negative value, client will never try to fetch their messages. + /// The provided value is interpreted as seconds. + fetch_message_delay: f64, + + /// The parameter of Poisson distribution determining how long, on average, + /// it is going to take another 'real traffic stream' message to be sent. + /// If no real packets are available and cover traffic is enabled, + /// a loop cover message is sent instead in order to preserve the rate. + /// If set to a negative value, client will never try to send real traffic data. + /// The provided value is interpreted as seconds. + message_sending_average_delay: f64, + + /// Whether loop cover messages should be sent to respect message_sending_rate. + /// In the case of it being disabled and not having enough real traffic + /// waiting to be sent the actual sending rate is going be lower than the desired value + /// thus decreasing the anonymity. + rate_compliant_cover_messages_disabled: bool, + + /// The uniform delay every which clients are querying the directory server + /// to try to obtain a compatible network topology to send sphinx packets through. + /// If set to a negative value, client will never try to refresh its topology, + /// meaning it will always try to use whatever it obtained on startup. + /// The provided value is interpreted as seconds. + topology_refresh_rate: f64, +} + +impl Default for Debug { + fn default() -> Self { + Debug { + average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, + loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_SENDING_AVERAGE_DELAY, + fetch_message_delay: DEFAULT_FETCH_MESSAGES_DELAY, + message_sending_average_delay: DEFAULT_MESSAGE_SENDING_AVERAGE_DELAY, + rate_compliant_cover_messages_disabled: false, + topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, + } } } #[cfg(test)] mod client_config { use super::*; + + #[test] + fn after_saving_default_config_the_loaded_one_is_identical() { + let temp_location = std::env::temp_dir().join("config.toml"); + let default_config = Config::default().with_id("foomp".to_string()); + default_config + .save_to_file(Some(temp_location.clone())) + .unwrap(); + + let loaded_config = Config::load_from_file(Some(temp_location)).unwrap(); + + assert_eq!(default_config, loaded_config); + } } diff --git a/nym-client/src/config/template.rs b/nym-client/src/config/template.rs index 9233c2b5fe..65f7cb8cc7 100644 --- a/nym-client/src/config/template.rs +++ b/nym-client/src/config/template.rs @@ -1,7 +1,4 @@ -use super::Config; -use handlebars::{Handlebars, TemplateRenderError}; - -fn config_template() -> &'static str { +pub(crate) fn config_template() -> &'static str { // While using normal toml marshalling would have been way simpler with less overhead, // I think it's useful to have comments attached to the saved config file to explain behaviour of // particular fields. @@ -20,49 +17,85 @@ id = "{{ client.id }}" directory_server = "{{ client.directory_server }}" # Path to file containing private identity key. -private_identity_key_file = "{{ client.private_identity_key }}" +private_identity_key_file = "{{ client.private_identity_key_file }}" # Path to file containing public identity key. -public_identity_key_file = "{{ client.public_identity_key }}" +public_identity_key_file = "{{ client.public_identity_key_file }}" ##### additional client config options ##### -# ID of the provider to which the client should send messages. +# ID of the provider from which the client should be fetching messages. provider_id = "{{ client.provider_id }}" -# directory for mixapps, such as a chat client, to store their app-specific data. -mixapps_directory = "{{ client.mix_apps_directory }}" - ##### advanced configuration options ##### # Absolute path to the home Nym Clients directory. -nym_home_directory = "{{ client.home_directory }}" +nym_root_directory = "{{ client.nym_root_directory }}" + + +##### socket config options ##### + +[socket] + +# allowed values are 'TCP', 'WebSocket' or 'None' +socket_type = "{{ socket.socket_type }}" + +# if applicable (for the case of 'TCP' or 'WebSocket'), the port on which the client +# will be listening for incoming requests +listening_port = {{ socket.listening_port }} + + +##### logging configuration options ##### + +[logging] + +# TODO + + +##### debug configuration options ##### +# The following options should not be modified unless you know EXACTLY what you are doing +# as if set incorrectly, they may impact your anonymity. + +[debug] + +# The parameter of Poisson distribution determining how long, on average, +# sent packet is going to be delayed at any given mix node. +# So for a packet going through three mix nodes, on average, it will take three times this value +# until the packet reaches its destination. +# The provided value is interpreted as seconds. +average_packet_delay = {{ debug.average_packet_delay }} + +# The parameter of Poisson distribution determining how long, on average, +# it is going to take for another loop cover traffic message to be sent. +# If set to a negative value, the loop cover traffic stream will be disabled. +# The provided value is interpreted as seconds. +loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} + +# The uniform delay every which clients are querying the providers for received packets. +# If set to a negative value, client will never try to fetch their messages. +# The provided value is interpreted as seconds. +fetch_message_delay = {{ debug.fetch_message_delay }} + +# The parameter of Poisson distribution determining how long, on average, +# it is going to take another 'real traffic stream' message to be sent. +# If no real packets are available and cover traffic is enabled, +# a loop cover message is sent instead in order to preserve the rate. +# If set to a negative value, client will never try to send real traffic data. +# The provided value is interpreted as seconds. +message_sending_average_delay = {{ debug.message_sending_average_delay }} + +# Whether loop cover messages should be sent to respect message_sending_rate. +# In the case of it being disabled and not having enough real traffic +# waiting to be sent the actual sending rate is going be lower than the desired value +# thus decreasing the anonymity. +rate_compliant_cover_messages_disabled = {{ debug.rate_compliant_cover_messages_disabled }} + +# The uniform delay every which clients are querying the directory server +# to try to obtain a compatible network topology to send sphinx packets through. +# If set to a negative value, client will never try to refresh its topology, +# meaning it will always try to use whatever it obtained on startup. +# The provided value is interpreted as seconds. +topology_refresh_rate = {{ debug.topology_refresh_rate }} + "# } - -fn render_template(config: &Config) -> Result { - let reg = Handlebars::new(); - reg.render_template(config_template(), &config) -} - -#[cfg(test)] -mod config_template { - use super::*; - - #[test] - fn it_works_for_default_config() { - render_template(&Default::default()).unwrap(); - } - - #[test] - fn it_works_for_dummy_config() { - let dummy_cfg = Config { - client: crate::config::Client { - id: "foomp".to_string(), - }, - }; - let render = render_template(&dummy_cfg).unwrap(); - - println!("{}", render); - } -}