diff --git a/Cargo.lock b/Cargo.lock index 9d687961cb..9a7618ad3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3372,6 +3372,7 @@ dependencies = [ "proxy-helpers", "rand 0.7.3", "serde", + "serde_json", "snafu 0.6.10", "socks5-requests", "tap", @@ -5005,9 +5006,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa 1.0.1", "ryu", diff --git a/clients/client-core/src/init.rs b/clients/client-core/src/init.rs index 44c20a7597..5cbd4bfcc6 100644 --- a/clients/client-core/src/init.rs +++ b/clients/client-core/src/init.rs @@ -3,7 +3,7 @@ //! Collection of initialization steps used by client implementations -use std::{sync::Arc, time::Duration}; +use std::{fmt::Display, sync::Arc, time::Duration}; use config::NymConfig; use crypto::asymmetric::{encryption, identity}; @@ -14,6 +14,7 @@ use nymsphinx::addressing::nodes::NodeIdentity; use rand::rngs::OsRng; use rand::seq::SliceRandom; use rand::thread_rng; +use serde::Serialize; use tap::TapFallible; use topology::{filter::VersionFilterable, gateway}; use url::Url; @@ -24,6 +25,43 @@ use crate::{ error::ClientCoreError, }; +#[derive(Debug, Serialize)] +pub struct InitResults { + version: String, + id: String, + identity_key: String, + encryption_key: String, + gateway_id: String, + gateway_listener: String, +} + +impl InitResults { + pub fn new(config: &Config, address: &Recipient) -> Self + where + T: NymConfig, + { + Self { + version: config.get_version().to_string(), + id: config.get_id(), + identity_key: address.identity().to_base58_string(), + encryption_key: address.encryption_key().to_base58_string(), + gateway_id: config.get_gateway_id(), + gateway_listener: config.get_gateway_listener(), + } + } +} + +impl Display for InitResults { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Version: {}", self.version)?; + writeln!(f, "ID: {}", self.id)?; + writeln!(f, "Identity key: {}", self.identity_key)?; + writeln!(f, "Encryption: {}", self.encryption_key)?; + writeln!(f, "Gateway ID: {}", self.gateway_id)?; + write!(f, "Gateway: {}", self.gateway_listener) + } +} + pub async fn query_gateway_details( validator_servers: Vec, chosen_gateway_id: Option<&str>, @@ -102,7 +140,7 @@ async fn register_with_gateway( Ok(shared_keys) } -pub fn show_address(config: &Config) -> Result<(), ClientCoreError> +pub fn get_client_address(config: &Config) -> Result where T: config::NymConfig, { @@ -142,6 +180,5 @@ where NodeIdentity::from_base58_string(config.get_gateway_id())?, ); - println!("\nThe address of this client is: {}", client_recipient); - Ok(()) + Ok(client_recipient) } diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 0a8ad9f29f..1b19bf32ad 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -26,7 +26,9 @@ log = "0.4" # self explanatory pretty_env_logger = "0.4" # for formatting log messages rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization +serde_json = "1.0" sled = "0.34" # for storage of replySURB decryption keys +tap = "1.0.1" thiserror = "1.0.34" tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime tokio-tungstenite = "0.14" # websocket @@ -51,7 +53,6 @@ topology = { path = "../../common/topology" } validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] } version-checker = { path = "../../common/version-checker" } websocket-requests = { path = "websocket-requests" } -tap = "1.0.1" [features] coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"] diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index dfcd98bab2..f30c5c1fe3 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -1,9 +1,13 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::fmt::Display; + use clap::Args; use client_core::{config::GatewayEndpointConfig, error::ClientCoreError}; use config::NymConfig; +use nymsphinx::addressing::clients::Recipient; +use serde::Serialize; use crate::{ client::config::Config, @@ -55,6 +59,10 @@ pub(crate) struct Init { #[cfg(feature = "coconut")] #[clap(long)] enabled_credentials_mode: bool, + + /// Save a summary of the initialization to a json file + #[clap(long)] + output_json: bool, } impl From for OverrideConfig { @@ -73,6 +81,29 @@ impl From for OverrideConfig { } } +#[derive(Debug, Serialize)] +pub struct InitResults { + #[serde(flatten)] + client_core: client_core::init::InitResults, + client_listening_port: String, +} + +impl InitResults { + pub fn new(config: &Config, address: &Recipient) -> Self { + Self { + client_core: client_core::init::InitResults::new(config.get_base(), address), + client_listening_port: config.get_listening_port().to_string(), + } + } +} + +impl Display for InitResults { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.client_core)?; + write!(f, "Client listening port: {}", self.client_listening_port) + } +} + pub(crate) async fn execute(args: &Init) { println!("Initialising client..."); @@ -126,10 +157,27 @@ pub(crate) async fn execute(args: &Init) { ); println!("Client configuration completed."); - client_core::init::show_address(config.get_base()).unwrap_or_else(|err| { - eprintln!("Failed to show address\nError: {err}"); + let address = client_core::init::get_client_address(config.get_base()).unwrap_or_else(|err| { + eprintln!("Failed to get address\nError: {err}"); std::process::exit(1) }); + + println!("\nThe address of this client is: {}\n", address); + + let init_results = InitResults::new(&config, &address); + println!("{}", init_results); + + // Output summary to a json file, if specified + if args.output_json { + let output_file = "client_init_results.json"; + match std::fs::File::create(output_file) { + Ok(file) => match serde_json::to_writer_pretty(file, &init_results) { + Ok(_) => println!("Saved: {}", output_file), + Err(err) => eprintln!("Could not save {}: {}", output_file, err), + }, + Err(err) => eprintln!("Could not save {}: {}", output_file, err), + } + } } async fn setup_gateway( diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index cd00e23262..de5ecf33ee 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -19,7 +19,9 @@ pin-project = "1.0" pretty_env_logger = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization +serde_json = "1.0.89" snafu = "0.6" +tap = "1.0.1" thiserror = "1.0.34" tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } url = "2.2" @@ -46,7 +48,6 @@ task = { path = "../../common/task" } topology = { path = "../../common/topology" } validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] } version-checker = { path = "../../common/version-checker" } -tap = "1.0.1" [features] coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"] diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index a727d0d8e9..c0d46480f3 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -1,9 +1,13 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::fmt::Display; + use clap::Args; use client_core::{config::GatewayEndpointConfig, error::ClientCoreError}; use config::NymConfig; +use nymsphinx::addressing::clients::Recipient; +use serde::Serialize; use crate::{ client::config::Config, @@ -55,6 +59,10 @@ pub(crate) struct Init { #[cfg(feature = "coconut")] #[clap(long)] enabled_credentials_mode: bool, + + /// Save a summary of the initialization to a json file + #[clap(long)] + output_json: bool, } impl From for OverrideConfig { @@ -71,6 +79,29 @@ impl From for OverrideConfig { } } +#[derive(Debug, Serialize)] +pub struct InitResults { + #[serde(flatten)] + client_core: client_core::init::InitResults, + socks5_listening_port: String, +} + +impl InitResults { + pub fn new(config: &Config, address: &Recipient) -> Self { + Self { + client_core: client_core::init::InitResults::new(config.get_base(), address), + socks5_listening_port: config.get_listening_port().to_string(), + } + } +} + +impl Display for InitResults { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.client_core)?; + write!(f, "SOCKS5 listening port: {}", self.socks5_listening_port) + } +} + pub(crate) async fn execute(args: &Init) { println!("Initialising client..."); @@ -123,12 +154,29 @@ pub(crate) async fn execute(args: &Init) { "Gateway listener: {}", config.get_base().get_gateway_listener() ); - println!("Client configuration completed."); + println!("Client configuration completed.\n"); - client_core::init::show_address(config.get_base()).unwrap_or_else(|err| { - eprintln!("Failed to show address\nError: {err}"); + let address = client_core::init::get_client_address(config.get_base()).unwrap_or_else(|err| { + eprintln!("Failed to get address\nError: {err}"); std::process::exit(1) }); + + let init_results = InitResults::new(&config, &address); + println!("{}", init_results); + + // Output summary to a json file, if specified + if args.output_json { + let output_file = "socks5_client_init_results.json"; + match std::fs::File::create(output_file) { + Ok(file) => match serde_json::to_writer_pretty(file, &init_results) { + Ok(_) => println!("Saved: {}", output_file), + Err(err) => eprintln!("Could not save {}: {}", output_file, err), + }, + Err(err) => eprintln!("Could not save {}: {}", output_file, err), + } + } + + println!("\nThe address of this client is: {}\n", address); } async fn setup_gateway( diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index ae06ce7609..dea013dc0c 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -3376,6 +3376,7 @@ dependencies = [ "proxy-helpers", "rand 0.7.3", "serde", + "serde_json", "snafu", "socks5-requests", "tap", @@ -4818,9 +4819,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa 1.0.3", "ryu", diff --git a/nym-connect/src-tauri/src/config/mod.rs b/nym-connect/src-tauri/src/config/mod.rs index e9a46b26a1..91adefbc53 100644 --- a/nym-connect/src-tauri/src/config/mod.rs +++ b/nym-connect/src-tauri/src/config/mod.rs @@ -159,7 +159,8 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str ); log::info!("Client configuration completed."); - client_core::init::show_address(config.get_base())?; + let address = client_core::init::get_client_address(config.get_base())?; + println!("\nThe address of this client is: {}\n", address); Ok(()) }