rust-sdk: initial version

This commit is contained in:
Jon Häggblad
2022-12-20 00:48:00 +01:00
parent 6c857b5daf
commit 9b9c01fb8f
29 changed files with 1077 additions and 177 deletions
Generated
+47 -24
View File
@@ -1778,6 +1778,14 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "examples-common"
version = "0.1.0"
dependencies = [
"log",
"pretty_env_logger",
]
[[package]]
name = "execute"
version = "0.1.0"
@@ -1970,9 +1978,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
dependencies = [
"futures-channel",
"futures-core",
@@ -1985,9 +1993,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
dependencies = [
"futures-core",
"futures-sink",
@@ -1995,15 +2003,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]]
name = "futures-executor"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
dependencies = [
"futures-core",
"futures-task",
@@ -2023,15 +2031,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]]
name = "futures-macro"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [
"proc-macro2",
"quote",
@@ -2040,21 +2048,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
[[package]]
name = "futures-task"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
[[package]]
name = "futures-util"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [
"futures-channel",
"futures-core",
@@ -3579,9 +3587,24 @@ dependencies = [
name = "nym-sdk"
version = "0.1.0"
dependencies = [
"client-connections",
"client-core",
"crypto",
"examples-common",
"futures",
"gateway-client",
"gateway-requests",
"log",
"network-defaults",
"nymsphinx",
"pretty_env_logger",
"rand 0.7.3",
"tap",
"task",
"thiserror",
"tokio",
"toml",
"url",
]
[[package]]
@@ -5986,18 +6009,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
@@ -6217,9 +6240,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.8"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [
"serde",
]
@@ -33,12 +33,15 @@ use log::{debug, info};
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::receiver::ReconstructedMessage;
use std::sync::Arc;
use std::time::Duration;
use tap::TapFallible;
use task::{TaskClient, TaskManager};
use url::Url;
use super::received_buffer::ReceivedBufferMessage;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub mod non_wasm_helpers;
@@ -48,10 +51,30 @@ pub struct ClientInput {
}
pub struct ClientOutput {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub received_buffer_request_sender: ReceivedBufferRequestSender,
}
impl ClientOutput {
pub fn register_receiver(
&mut self,
) -> Result<mpsc::UnboundedReceiver<Vec<ReconstructedMessage>>, ClientCoreError> {
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
self.received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.map_err(|_| ClientCoreError::FailedToRegisterReceiver)?;
Ok(reconstructed_receiver)
}
}
pub struct ClientState {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub reply_controller_sender: ReplyControllerSender,
}
pub enum ClientInputStatus {
AwaitingProducer { client_input: ClientInput },
Connected,
@@ -475,11 +498,13 @@ where
},
client_output: ClientOutputStatus::AwaitingConsumer {
client_output: ClientOutput {
shared_lane_queue_lengths,
received_buffer_request_sender,
},
},
reply_controller_sender,
client_state: ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
},
task_manager,
})
}
@@ -488,9 +513,7 @@ where
pub struct BaseClient {
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
// it feels very wrong to put this channel here, but I can't think of any other way of passing it to the native client
pub reply_controller_sender: ReplyControllerSender,
pub client_state: ClientState,
pub task_manager: TaskManager,
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_storage::{
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
browser_backend, fs_backend, CombinedReplyStorage, ReplyStorageBackend,
};
use crate::config::DebugConfig;
use crate::error::ClientCoreError;
@@ -85,3 +85,10 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
setup_fresh_backend(db_path, debug_config).await
}
}
pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> browser_backend::Backend {
browser_backend::Backend::new(
debug_config.minimum_reply_surb_storage_threshold,
debug_config.maximum_reply_surb_storage_threshold,
)
}
+65 -9
View File
@@ -17,6 +17,8 @@ use std::sync::Arc;
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
// WIP(JON): let's try not to have the clone
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
@@ -57,6 +59,20 @@ impl KeyManager {
}
}
pub fn new_from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: SharedKeys,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
ack_key: Arc::new(ack_key),
}
}
// this is actually **NOT** dead code
// I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute
#[allow(dead_code)]
@@ -65,8 +81,8 @@ impl KeyManager {
self.gateway_shared_key = Some(gateway_shared_key)
}
/// Loads previously stored keys from the disk.
pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
/// Loads previously stored client keys from the disk.
fn load_keys_client_only(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let identity_keypair: identity::KeyPair =
pemstore::load_keypair(&pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
@@ -78,21 +94,47 @@ impl KeyManager {
client_pathfinder.public_encryption_key().to_owned(),
))?;
let gateway_shared_key: SharedKeys =
pemstore::load_key(client_pathfinder.gateway_shared_key())?;
let ack_key: AckKey = pemstore::load_key(client_pathfinder.ack_key())?;
// TODO: ack key is never stored so it is generated now. But perhaps it should be stored
// after all for consistency sake?
Ok(KeyManager {
identity_keypair: Arc::new(identity_keypair),
encryption_keypair: Arc::new(encryption_keypair),
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
gateway_shared_key: None,
ack_key: Arc::new(ack_key),
})
}
/// Loads previously stored keys from the disk. Fails if not all, including the shared gateway
/// key, is available.
pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let mut key_manager = Self::load_keys_client_only(client_pathfinder)?;
let gateway_shared_key: SharedKeys =
pemstore::load_key(client_pathfinder.gateway_shared_key())?;
key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key));
Ok(key_manager)
}
pub fn load_keys_maybe_gateway(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let mut key_manager = Self::load_keys_client_only(client_pathfinder)?;
let gateway_shared_key: Result<SharedKeys, io::Error> =
pemstore::load_key(client_pathfinder.gateway_shared_key());
// It's ok if the gateway key was not found
let gateway_shared_key = match gateway_shared_key {
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
Ok(key) => Ok(Some(key)),
}?;
key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new);
Ok(key_manager)
}
// this is actually **NOT** dead code
// I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute
#[allow(dead_code)]
@@ -119,7 +161,21 @@ impl KeyManager {
pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?;
match self.gateway_shared_key.as_ref() {
None => warn!("No gateway shared key available to store!"),
None => debug!("No gateway shared key available to store!"),
Some(gate_key) => {
pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
}
Ok(())
}
pub fn store_key_gateway_only(
&self,
client_pathfinder: &ClientKeyPathfinder,
) -> io::Result<()> {
match self.gateway_shared_key.as_ref() {
None => panic!("WIP(JON)"),
Some(gate_key) => {
pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
@@ -213,7 +213,11 @@ impl ActionController {
}
// note: when the entry expires it's automatically removed from pending_acks_timers
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
fn handle_expired_ack_timer(
&mut self,
expired_ack: Expired<FragmentIdentifier>,
task_client: &mut task::TaskClient,
) {
// I'm honestly not sure how to handle it, because getting it means other things in our
// system are already misbehaving. If we ever see this panic, then I guess we should worry
// about it. Perhaps just reschedule it at later point?
@@ -231,9 +235,16 @@ impl ActionController {
// downgrading an arc and then upgrading vs cloning is difference of 30ns vs 15ns
// so it's literally a NO difference while it might prevent us from unnecessarily
// resending data (in maybe 1 in 1 million cases, but it's something)
self.retransmission_sender
if self
.retransmission_sender
.unbounded_send(Arc::downgrade(pending_ack_data))
.unwrap()
.is_err()
{
assert!(
task_client.is_shutdown_poll(),
"Failed to send pending ack for retransmission"
);
}
} else {
// this shouldn't cause any issues but shouldn't have happened to begin with!
error!("An already removed pending ack has expired")
@@ -264,7 +275,7 @@ impl ActionController {
}
},
expired_ack = self.pending_acks_timers.next() => match expired_ack {
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack),
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack, &mut shutdown),
None => {
log::trace!("ActionController: Stopping since ack channel closed");
break;
@@ -99,6 +99,24 @@ impl ReplyControllerSender {
}
}
pub struct ReplyQueueLengths {
reply_controller_sender: ReplyControllerSender,
}
impl ReplyQueueLengths {
pub fn new(reply_controller_sender: ReplyControllerSender) -> Self {
Self {
reply_controller_sender,
}
}
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
self.reply_controller_sender
.get_lane_queue_length(connection_id)
.await
}
}
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
#[derive(Debug)]
@@ -7,6 +7,7 @@ use async_trait::async_trait;
// well, right now we don't have the browser storage : (
// so we keep everything in memory
#[derive(Debug)]
pub struct Backend {
empty: Empty,
}
@@ -6,10 +6,12 @@ use async_trait::async_trait;
use std::error::Error;
use thiserror::Error;
#[cfg(target_arch = "wasm32")]
//#[cfg(target_arch = "wasm32")]
//#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub mod browser_backend;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
//#[cfg(target_arch = "wasm32")]
pub mod fs_backend;
// #[cfg(all(test, feature = "std"))]
@@ -19,10 +21,11 @@ pub mod fs_backend;
#[error("no information provided")]
pub struct UndefinedError;
#[derive(Debug)]
pub struct Empty {
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
min_surb_threshold: usize,
max_surb_threshold: usize,
pub min_surb_threshold: usize,
pub max_surb_threshold: usize,
}
#[async_trait]
@@ -7,12 +7,12 @@ use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct ClientKeyPathfinder {
identity_private_key: PathBuf,
identity_public_key: PathBuf,
encryption_private_key: PathBuf,
encryption_public_key: PathBuf,
gateway_shared_key: PathBuf,
ack_key: PathBuf,
pub identity_private_key: PathBuf,
pub identity_public_key: PathBuf,
pub encryption_private_key: PathBuf,
pub encryption_public_key: PathBuf,
pub gateway_shared_key: PathBuf,
pub ack_key: PathBuf,
}
impl ClientKeyPathfinder {
@@ -22,13 +22,25 @@ impl ClientKeyPathfinder {
ClientKeyPathfinder {
identity_private_key: config_dir.join("private_identity.pem"),
identity_public_key: config_dir.join("public_identity.pem"),
encryption_private_key: config_dir.join("public_encryption.pem"),
encryption_public_key: config_dir.join("private_encryption.pem"),
encryption_private_key: config_dir.join("private_encryption.pem"),
encryption_public_key: config_dir.join("public_encryption.pem"),
gateway_shared_key: config_dir.join("gateway_shared.pem"),
ack_key: config_dir.join("ack_key.pem"),
}
}
//pub fn new_from_dir(config_dir: &PathBuf) -> Self {
// dbg!(&config_dir);
// ClientKeyPathfinder {
// identity_private_key: config_dir.join("private_identity.pem"),
// identity_public_key: config_dir.join("public_identity.pem"),
// encryption_private_key: config_dir.join("public_encryption.pem"),
// encryption_public_key: config_dir.join("private_encryption.pem"),
// gateway_shared_key: config_dir.join("gateway_shared.pem"),
// ack_key: config_dir.join("ack_key.pem"),
// }
//}
pub fn new_from_config<T: NymConfig>(config: &Config<T>) -> Self {
ClientKeyPathfinder {
identity_private_key: config.get_private_identity_key_file(),
@@ -40,6 +52,19 @@ impl ClientKeyPathfinder {
}
}
pub fn any_file_exists(&self) -> bool {
matches!(self.identity_public_key.try_exists(), Ok(true))
|| matches!(self.identity_private_key.try_exists(), Ok(true))
|| matches!(self.encryption_public_key.try_exists(), Ok(true))
|| matches!(self.encryption_private_key.try_exists(), Ok(true))
|| matches!(self.gateway_shared_key.try_exists(), Ok(true))
|| matches!(self.ack_key.try_exists(), Ok(true))
}
pub fn gateway_key_file_exists(&self) -> bool {
matches!(self.gateway_shared_key.try_exists(), Ok(true))
}
pub fn private_identity_key(&self) -> &Path {
&self.identity_private_key
}
+3
View File
@@ -55,6 +55,9 @@ pub enum ClientCoreError {
#[error("The address of the gateway is unknown - did you run init?")]
GatwayAddressUnknown,
#[error("failed to register receiver for reconstructed mixnet messages")]
FailedToRegisterReceiver,
#[error("Unexpected exit")]
UnexpectedExit,
}
+4 -11
View File
@@ -10,7 +10,7 @@ use config::NymConfig;
use crypto::asymmetric::identity;
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use rand::{rngs::OsRng, seq::SliceRandom, thread_rng};
use rand::{seq::SliceRandom, thread_rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use topology::{filter::VersionFilterable, gateway};
@@ -51,7 +51,7 @@ pub(super) async fn query_gateway_details(
}
}
async fn register_with_gateway(
pub(super) async fn register_with_gateway(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
) -> Result<Arc<SharedKeys>, ClientCoreError> {
@@ -74,20 +74,13 @@ async fn register_with_gateway(
Ok(shared_keys)
}
pub(super) async fn register_with_gateway_and_store_keys<T>(
gateway_details: gateway::Node,
pub(super) fn store_keys<T>(
key_manager: &KeyManager,
config: &Config<T>,
) -> Result<(), ClientCoreError>
where
T: NymConfig,
{
let mut rng = OsRng;
let mut key_manager = KeyManager::new(&mut rng);
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?;
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config);
Ok(key_manager
.store_keys(&pathfinder)
+63 -13
View File
@@ -6,23 +6,26 @@
use std::fmt::Display;
use nymsphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
use rand::rngs::OsRng;
use serde::Serialize;
use tap::TapFallible;
use config::NymConfig;
use crypto::asymmetric::{encryption, identity};
use url::Url;
use crate::client::key_manager::KeyManager;
use crate::{
config::{
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
GatewayEndpointConfig,
},
error::ClientCoreError,
init::helpers::{query_gateway_details, register_with_gateway_and_store_keys},
};
mod helpers;
/// Struct describing the results of the client initialization procedure.
#[derive(Debug, Serialize)]
pub struct InitResults {
version: String,
@@ -60,6 +63,12 @@ impl Display for InitResults {
}
}
/// Create a new set of client keys.
pub fn new_client_keys() -> KeyManager {
let mut rng = OsRng;
KeyManager::new(&mut rng)
}
/// Convenience function for setting up the gateway for a client. Depending on the arguments given
/// it will do the sensible thing.
pub async fn setup_gateway<C, T>(
@@ -74,7 +83,7 @@ where
{
let id = config.get_id();
if register_gateway {
register_with_gateway(user_chosen_gateway_id, config).await
register_with_gateway_and_store(user_chosen_gateway_id, config).await
} else if let Some(user_chosen_gateway_id) = user_chosen_gateway_id {
config_gateway_with_existing_keys(user_chosen_gateway_id, config).await
} else {
@@ -82,27 +91,51 @@ where
}
}
/// Get the gateway details by querying the validator-api. Either pick one at random or use
/// the chosen one if it's among the available ones.
pub async fn register_with_gateway(
key_manager: &mut KeyManager,
nym_api_endpoints: Vec<Url>,
chosen_gateway_id: Option<String>,
) -> Result<GatewayEndpointConfig, ClientCoreError> {
// Our identity is derived from our key
let our_identity = key_manager.identity_keypair();
// Get the gateway details of the gateway we will use
let gateway = helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id).await?;
log::debug!("Querying gateway gives: {}", gateway);
// Establish connection, authenticate and generate keys for talking with the gateway
let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?;
key_manager.insert_gateway_shared_key(shared_keys);
Ok(gateway.into())
}
/// Get the gateway details by querying the validator-api. Either pick one at random or use
/// the chosen one if it's among the available ones.
/// Saves keys to disk, specified by the paths in `config`.
pub async fn register_with_gateway<T>(
user_chosen_gateway_id: Option<String>,
pub async fn register_with_gateway_and_store<T>(
chosen_gateway_id: Option<String>,
config: &Config<T>,
) -> Result<GatewayEndpointConfig, ClientCoreError>
where
T: NymConfig,
{
println!("Configuring gateway");
let gateway =
query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id).await?;
log::debug!("Querying gateway gives: {}", gateway);
let mut key_manager = new_client_keys();
// Registering with gateway by setting up and writing shared keys to disk
log::trace!("Registering gateway");
register_with_gateway_and_store_keys(gateway.clone(), config).await?;
let gateway = register_with_gateway(
&mut key_manager,
config.get_nym_api_endpoints(),
chosen_gateway_id,
)
.await?;
helpers::store_keys(&key_manager, config)?;
println!("Saved all generated keys");
Ok(gateway.into())
Ok(gateway)
}
/// Set the gateway using the usual procedue of querying the validator-api, but don't register or
@@ -117,8 +150,11 @@ where
T: NymConfig,
{
println!("Using gateway provided by user, keeping existing keys");
let gateway =
query_gateway_details(config.get_nym_api_endpoints(), Some(user_chosen_gateway_id)).await?;
let gateway = helpers::query_gateway_details(
config.get_nym_api_endpoints(),
Some(user_chosen_gateway_id),
)
.await?;
log::debug!("Querying gateway gives: {}", gateway);
Ok(gateway.into())
}
@@ -143,6 +179,20 @@ where
})
}
/// Get the full client address from the client keys and the gateway identity
pub fn get_client_address(
key_manager: &KeyManager,
gateway_config: &GatewayEndpointConfig,
) -> Recipient {
Recipient::new(
*key_manager.identity_keypair().public_key(),
*key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(),
)
}
/// Get the client address by loading the keys from stored files.
pub fn get_client_address_from_stored_keys<T>(
config: &Config<T>,
+9 -5
View File
@@ -8,12 +8,11 @@ use crate::error::ClientError;
use crate::websocket;
use client_connections::TransmissionLane;
use client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput,
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use client_core::client::inbound_messages::InputMessage;
use client_core::client::key_manager::KeyManager;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use client_core::client::replies::reply_controller::requests::ReplyControllerSender;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
@@ -87,8 +86,8 @@ impl SocketClient {
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
client_state: ClientState,
self_address: &Recipient,
reply_controller_sender: ReplyControllerSender,
shutdown: task::TaskClient,
) {
info!("Starting websocket listener...");
@@ -99,10 +98,14 @@ impl SocketClient {
} = client_input;
let ClientOutput {
shared_lane_queue_lengths,
received_buffer_request_sender,
} = client_output;
let ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
} = client_state;
let websocket_handler = websocket::HandlerBuilder::new(
input_sender,
connection_command_sender,
@@ -151,13 +154,14 @@ impl SocketClient {
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let client_state = started_client.client_state;
Self::start_websocket_listener(
&self.config,
client_input,
client_output,
client_state,
&self_address,
started_client.reply_controller_sender,
started_client.task_manager.subscribe(),
);
+9 -2
View File
@@ -9,7 +9,7 @@ use crate::socks::{
server::SphinxSocksServer,
};
use client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput,
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
@@ -95,6 +95,7 @@ impl NymClient {
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
client_status: ClientState,
self_address: Recipient,
shutdown: TaskClient,
) {
@@ -108,10 +109,14 @@ impl NymClient {
} = client_input;
let ClientOutput {
shared_lane_queue_lengths,
received_buffer_request_sender,
} = client_output;
let ClientState {
shared_lane_queue_lengths,
reply_controller_sender: _,
} = client_status;
let authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = SphinxSocksServer::new(
config.get_listening_port(),
@@ -218,11 +223,13 @@ impl NymClient {
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let client_state = started_client.client_state;
Self::start_socks5_listener(
&self.config,
client_input,
client_output,
client_state,
self_address,
started_client.task_manager.subscribe(),
);
+3 -3
View File
@@ -208,7 +208,7 @@ pub struct TaskClient {
impl TaskClient {
#[cfg(not(target_arch = "wasm32"))]
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1);
fn new(
notify: watch::Receiver<()>,
@@ -300,8 +300,8 @@ impl TaskClient {
has_changed
}
Err(err) => {
log::debug!("Polling shutdown failed: {err}");
log::debug!("Assuming this means we should shutdown...");
log::error!("Polling shutdown failed: {err}");
log::error!("Assuming this means we should shutdown...");
true
}
}
+18 -2
View File
@@ -6,8 +6,24 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
client-core = { path = "../../../clients/client-core" }
client-connections = { path = "../../../common/client-connections" }
client-core = { path = "../../../clients/client-core", features = ["fs-surb-storage"]}
crypto = { path = "../../../common/crypto" }
gateway-client = { path = "../../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../../gateway/gateway-requests" }
network-defaults = { path = "../../../common/network-defaults" }
nymsphinx = { path = "../../../common/nymsphinx" }
task = { path = "../../../common/task" }
futures = "0.3"
log = "0.4"
rand = { version = "0.7.3" }
tap = "1.0.1"
thiserror = "1.0.38"
url = "2.2"
toml = "0.5.10"
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
pretty_env_logger = "0.4.0"
tokio = { version = "1", features = ["full"] }
examples-common = { path = "examples-common" }
@@ -0,0 +1,10 @@
[package]
name = "examples-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4.17"
pretty_env_logger = "0.4.0"
@@ -0,0 +1,21 @@
pub fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("handlebars", log::LevelFilter::Warn)
.filter_module("sled", log::LevelFilter::Warn)
.init();
}
+28 -12
View File
@@ -1,24 +1,40 @@
use std::path::PathBuf;
use examples_common::setup_logging;
use nym_sdk::mixnet;
#[tokio::main]
async fn main() {
// specify some config options
let keys = Some(PathBuf::from("~/.nym/clients/superfoomp"));
let config = mixnet::Config { keys };
setup_logging();
let client = mixnet::Client::new(Some(config)); // passing a config allows the user to set values
// Specify some config options
let config_dir = PathBuf::from("/tmp/mixnet-client");
// let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnOnce`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
// client.on_receive(show_receive); // have some way to pipe any received info to a function for processing
// Setting `KeyMode::Keep` will use existing keys, and existing config, if there is one.
// Regardles of `user_chosen_gateway`.
let keys = mixnet::KeyPaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir);
// connect to the mixnet, now we're listening for incoming
client.connect_to_mixnet();
// Provide key paths for the client to read/write keys to.
let mut client = mixnet::Client::new(None, Some(keys)).unwrap();
// be able to get our client address
println!("Our client address is {}", client.nym_address);
// Connect to the mixnet, now we're listening for incoming
client.connect_to_mixnet().await;
// send important info up the pipe to a buddy
client.send_str("foo.bar@blah", "flappappa");
// Be able to get our client address
let our_address = client.nym_address().unwrap();
println!("Our client nym address is: {our_address}");
// Send a message throught the mixnet to ourselves
client
.send_str(&our_address.to_string(), "hello there")
.await;
println!("Waiting for message");
if let Some(received) = client.wait_for_messages().await {
for r in received {
println!("Received: {}", String::from_utf8_lossy(&r.message));
}
}
client.disconnect().await;
}
@@ -0,0 +1,45 @@
use examples_common::setup_logging;
use nym_sdk::mixnet;
#[tokio::main]
async fn main() {
setup_logging();
// We can set a few options
let user_chosen_gateway_id = None;
let nym_api_endpointgs = vec![];
let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpointgs);
let mut client = mixnet::Client::new(Some(config), None).unwrap();
// In this we want to provide our own gateway config struct, and handle persisting this info to disk
// ourselves (e.g., as part of our own configuration file).
// NOTE: gateway shared key is written to disk according to the path given earlier
// Checks if we have a shared gateway key loaded
let first_run = true;
if first_run {
client.register_with_gateway().await;
write_to_storage(client.get_keys(), client.get_gateway_endpoint().unwrap());
} else {
let (keys, gateway_config) = read_from_storage();
client.set_keys(&keys);
client.set_gateway_endpoint(&gateway_config);
}
// Connect to the mixnet, now we're listening for incoming
client.connect_to_mixnet().await;
// Be able to get our client address
println!("Our client address is {}", client.nym_address().unwrap());
// Send important info up the pipe to a buddy
client.send_str("foo.bar@blah", "flappappa").await;
}
fn write_to_storage(_keys: &mixnet::Keys, _gateway_config: &mixnet::GatewayEndpointConfig) {
todo!();
}
fn read_from_storage() -> (mixnet::Keys, mixnet::GatewayEndpointConfig) {
todo!();
}
+17 -12
View File
@@ -1,22 +1,27 @@
use examples_common::setup_logging;
use nym_sdk::mixnet;
#[tokio::main]
async fn main() {
// here's what I'd actually like to write
let client = mixnet::Client::new(None); // passing no config makes the client fire up an ephemeral session and figure shit out on its own
setup_logging();
// let show_receive = move || println!("got a message from the mixnet: {}", message); // might need to bury this in a struct as a `FnMut`, see https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
// connect to the mixnet, now we're listening for incoming
client.connect_to_mixnet();
// Passing no config makes the client fire up an ephemeral session and figure shit out on its own
let mut client = mixnet::Client::new(None, None).unwrap();
// be able to get our client address
println!("Our client address is {}", client.nym_address);
// Connect to the mixnet, now we're listening for incoming
client.connect_to_mixnet().await;
// send important string info up the pipe to a buddy
client.send_str("foo.bar@blah", "flappappa");
// Be able to get our client address
let our_address = client.nym_address().unwrap();
println!("Our client nym address is: {our_address}");
// send some bytes to a buddy
client.send_bytes("foo.bar@blah", "flappappa".as_bytes().to_vec());
// Send a message throught the mixnet to ourselves
client
.send_str(&our_address.to_string(), "hello there")
.await;
println!("Waiting for message");
client
.on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
.await;
}
+10
View File
@@ -0,0 +1,10 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("i/o error: {0}")]
IoError(#[from] std::io::Error),
#[error("key file encountered that we don't want to overwrite")]
DontOverwrite,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
+1
View File
@@ -1 +1,2 @@
pub mod error;
pub mod mixnet;
+15
View File
@@ -0,0 +1,15 @@
mod client;
mod config;
mod connection_state;
mod key_paths;
pub use client_core::config::GatewayEndpointConfig;
pub use nymsphinx::{
addressing::clients::{ClientIdentity, Recipient},
receiver::ReconstructedMessage,
};
pub use key_paths::{GatewayKeyMode, KeyMode, KeyPaths, Keys};
pub use client::Client;
pub use config::Config;
+306
View File
@@ -0,0 +1,306 @@
use futures::StreamExt;
use nymsphinx::{
addressing::clients::{ClientIdentity, Recipient},
receiver::ReconstructedMessage,
};
use tap::TapOptional;
use client_connections::TransmissionLane;
use client_core::{
client::{
base_client::{non_wasm_helpers, BaseClientBuilder},
inbound_messages::InputMessage,
key_manager::KeyManager,
},
config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig},
};
use crate::error::{Error, Result};
use super::{connection_state::ConnectionState, Config, GatewayKeyMode, KeyPaths, Keys};
pub struct Client {
/// Keys handled by the client
key_manager: KeyManager,
/// Client configuration
config: Config,
/// Paths for client keys, including identity, encryption, ack and shared gateway keys.
key_paths: Option<KeyPaths>,
/// The client can be in one of multiple states, depending on how it is created and if it's
/// connected to the mixnet.
connection_state: ConnectionState,
}
impl Client {
/// Create a new mixnet client. If no config options are supplied, creates a new client with
/// ephemeral keys stored in RAM, which will be discarded at application close.
///
/// Callers have the option of supplying futher parameters to store persistent identities at a
/// location on-disk, if desired.
pub fn new(config_option: Option<Config>, key_paths: Option<KeyPaths>) -> Result<Client> {
let config = config_option.unwrap_or_default();
// If we are provided paths to keys, use them if they are available. And if they are
// not, write the generated keys back to storage.
let key_manager = if let Some(ref key_paths) = key_paths {
let path_finder = ClientKeyPathfinder::from(key_paths.clone());
// Try load keys
match KeyManager::load_keys_maybe_gateway(&path_finder) {
Ok(key_manager) => key_manager,
Err(err) => {
log::debug!("Not loading keys: {err}");
if path_finder.any_file_exists() && key_paths.operating_mode.is_keep() {
return Err(Error::DontOverwrite);
}
// Create new keys and write to storage
let key_manager = client_core::init::new_client_keys();
// WARN: this will overwrite!
key_manager.store_keys(&path_finder)?;
key_manager
}
}
} else {
// Ephemeral keys that we only store in memory
client_core::init::new_client_keys()
};
Ok(Client {
key_manager,
config,
key_paths,
connection_state: ConnectionState::New,
})
}
/// Get the client identity, which is the public key of the identity key pair.
pub fn identity(&self) -> ClientIdentity {
*self.key_manager.identity_keypair().public_key()
}
/// Get the nym address for this client, if it is available. The nym address is composed of the
/// client identity, the client encryption key, and the gateway identity.
pub fn nym_address(&self) -> Option<&Recipient> {
self.connection_state.nym_address()
}
/// Client keys are generated at client creation if none were found. The gateway shared
/// key, however, is created during the gateway registration handshake so it might not
/// necessarily be available.
pub fn has_gateway_key(&self) -> bool {
self.key_manager.gateway_key_set()
}
pub fn set_keys(&mut self, _keys: &Keys) {
todo!();
}
pub fn get_keys(&self) -> &Keys {
todo!();
}
pub fn set_gateway_endpoint(&mut self, _gateway_endpoint_config: &GatewayEndpointConfig) {
todo!();
}
pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> {
self.connection_state.gateway_endpoint_config()
}
pub async fn register_with_gateway(&mut self) {
assert!(
matches!(self.connection_state, ConnectionState::New),
"can only setup gateway when in `New` connection state"
);
let gateway_config = client_core::init::register_with_gateway(
&mut self.key_manager,
self.config.nym_api_endpoints.clone(),
self.config.user_chosen_gateway.clone(),
)
.await
.expect("WIP");
let nym_address = client_core::init::get_client_address(&self.key_manager, &gateway_config);
self.connection_state = ConnectionState::Registered {
gateway_endpoint_config: gateway_config,
nym_address,
};
}
fn write_gateway_key(&self, key_mode: &GatewayKeyMode) {
let key_paths = self.key_paths.as_ref().unwrap().clone();
let path_finder = ClientKeyPathfinder::from(key_paths);
if path_finder.gateway_key_file_exists() && key_mode.is_keep() {
todo!("WIP");
};
self.key_manager
.store_key_gateway_only(&path_finder)
.expect("WIP");
}
fn write_gateway_endpoint_config(&self) {
let key_paths = self.key_paths.as_ref().unwrap().clone();
let gateway_endpoint_config_path = key_paths.gateway_endpoint_config;
let gateway_endpoint_config =
toml::to_string(self.get_gateway_endpoint().unwrap()).unwrap();
// Ensure the whole directory structure exists
if let Some(parent_dir) = gateway_endpoint_config_path.parent() {
std::fs::create_dir_all(parent_dir).expect("WIP");
}
std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config).expect("WIP");
}
fn read_gateway_endpoint_config(&mut self) {
let key_paths = self.key_paths.as_ref().unwrap().clone();
let gateway_endpoint_config: GatewayEndpointConfig = toml::from_str(
&std::fs::read_to_string(key_paths.gateway_endpoint_config).expect("WIP"),
)
.expect("WIP");
let nym_address =
client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config);
self.connection_state = ConnectionState::Registered {
gateway_endpoint_config,
nym_address,
};
}
/// Connects to the mixnet via the gateway in the client config
pub async fn connect_to_mixnet(&mut self) {
// For some simple cases we can figure how to setup gateway without it having to have been
// called in advance.
if matches!(self.connection_state, ConnectionState::New) {
if self.key_paths.is_some() {
if self.has_gateway_key() {
// If we have a gateway key from client, then we can just read the corresponding
// config
println!("Has gateway key: loading");
self.read_gateway_endpoint_config();
} else {
// If we didn't find any shared gateway key during creation, that means we first
// need to register a gateway
println!("NO gateway key: registering new");
self.register_with_gateway().await;
self.write_gateway_key(&GatewayKeyMode::Overwrite);
self.write_gateway_endpoint_config();
}
} else {
// If we don't have any key paths, just use ephemeral keys
self.register_with_gateway().await;
}
}
// At this point we should be in a registered state, either at function entry or by the
// above convenience logic.
assert!(matches!(
self.connection_state,
ConnectionState::Registered { .. }
));
let bandwidth_controller = None;
let reply_storage_backend =
non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config);
let disabled_credentials = true;
let gateway_config = self.connection_state.gateway_endpoint_config().unwrap();
let base_builder = BaseClientBuilder::new(
gateway_config,
&self.config.debug_config,
self.key_manager.clone(),
bandwidth_controller,
reply_storage_backend,
disabled_credentials,
self.config.nym_api_endpoints.clone(),
);
let nym_address = base_builder.as_mix_recipient();
let mut started_client = base_builder.start_base().await.unwrap();
let client_input = started_client.client_input.register_producer();
let mut client_output = started_client.client_output.register_consumer();
let client_state = started_client.client_state;
// Register our receiver
let reconstructed_receiver = client_output.register_receiver().unwrap();
self.connection_state = ConnectionState::Connected {
nym_address,
client_input,
client_output,
client_state,
reconstructed_receiver,
task_manager: started_client.task_manager,
};
}
/// Sends stringy data to the supplied Nym address
pub async fn send_str(&self, address: &str, message: &str) {
log::debug!("send_str");
let message_bytes = message.to_string().into_bytes();
self.send_bytes(address, message_bytes).await;
}
/// Sends bytes to the supplied Nym address
pub async fn send_bytes(&self, address: &str, message: Vec<u8>) {
log::debug!("send_bytes");
let Some(client_input) = self.connection_state.client_input() else {
log::error!("Error: trying to send without being connected");
return;
};
let lane = TransmissionLane::General;
let recipient = Recipient::try_from_base58_string(address).unwrap();
let input_msg = InputMessage::new_regular(recipient, message, lane);
client_input.input_sender.send(input_msg).await.unwrap();
}
/// Wait for messages from the mixnet
pub async fn wait_for_messages(&mut self) -> Option<Vec<ReconstructedMessage>> {
let receiver = self
.connection_state
.reconstructed_receiver()
.tap_none(|| log::error!("Error: trying to wait without being connected"))?;
receiver.next().await
}
pub fn wait_for_messages_split(&mut self) -> Option<ReconstructedMessage> {
todo!();
}
pub async fn on_messages<F>(&mut self, fun: F)
where
F: Fn(ReconstructedMessage),
{
while let Some(msgs) = self.wait_for_messages().await {
for msg in msgs {
fun(msg)
}
}
}
/// Disconnect from the mixnet. Currently it is not supported to reconnect a disconnected
/// client.
pub async fn disconnect(&mut self) {
let Some(task_manager) = self.connection_state.task_manager() else {
log::error!("Trying to disconnect when not connected!");
return;
};
task_manager.signal_shutdown().ok();
task_manager.wait_for_shutdown().await;
self.connection_state = ConnectionState::Disconnected;
}
}
+36
View File
@@ -0,0 +1,36 @@
use client_core::config::DebugConfig;
use network_defaults::mainnet;
use url::Url;
pub struct Config {
/// If the user has explicitly specified a gateway.
pub user_chosen_gateway: Option<String>,
/// List of nym-api endpoints
pub nym_api_endpoints: Vec<Url>,
/// Flags controlling all sorts of internal client behaviour.
/// Changing these risk compromising network anonymity!
pub debug_config: DebugConfig,
}
impl Default for Config {
fn default() -> Self {
let nym_api_endpoints = vec![mainnet::API_VALIDATOR.to_string().parse().unwrap()];
Self {
user_chosen_gateway: Default::default(),
nym_api_endpoints,
debug_config: Default::default(),
}
}
}
impl Config {
pub fn new(user_chosen_gateway: Option<String>, nym_api_endpoints: Vec<Url>) -> Self {
Self {
user_chosen_gateway,
nym_api_endpoints,
debug_config: DebugConfig::default(),
}
}
}
@@ -0,0 +1,115 @@
use client_core::{
client::{
base_client::{ClientInput, ClientOutput, ClientState},
received_buffer::ReconstructedMessagesReceiver,
},
config::GatewayEndpointConfig,
};
use nymsphinx::addressing::clients::Recipient;
use task::TaskManager;
/// States that the client connection can be in. Currently the states can only progress linearly
/// from New -> Registered -> Connected -> Disconnected. In the future it could be useful to be able
/// to re-connect a disconnected client.
// WIP(JON): consider adding inner types
#[allow(clippy::large_enum_variant)]
pub(super) enum ConnectionState {
New,
Registered {
nym_address: Recipient,
gateway_endpoint_config: GatewayEndpointConfig,
},
Connected {
nym_address: Recipient,
client_input: ClientInput,
#[allow(dead_code)]
client_output: ClientOutput,
#[allow(dead_code)]
client_state: ClientState,
reconstructed_receiver: ReconstructedMessagesReceiver,
task_manager: TaskManager,
},
Disconnected,
}
impl std::fmt::Debug for ConnectionState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::New => write!(f, "New"),
Self::Registered {
nym_address,
gateway_endpoint_config: gateway_config,
} => f
.debug_struct("Registered")
.field("nym_address", nym_address)
.field("gateway_config", gateway_config)
.finish(),
Self::Connected {
nym_address,
client_input: _,
client_output: _,
client_state: _,
reconstructed_receiver,
task_manager,
} => f
.debug_struct("Connected")
.field("nym_address", nym_address)
.field("reconstructed_receiver", reconstructed_receiver)
.field("task_manager", task_manager)
.finish(),
Self::Disconnected => write!(f, "Disconnected"),
}
}
}
impl ConnectionState {
pub(super) fn client_input(&self) -> Option<&ClientInput> {
match self {
ConnectionState::New
| ConnectionState::Registered { .. }
| ConnectionState::Disconnected => None,
ConnectionState::Connected { client_input, .. } => Some(client_input),
}
}
pub(super) fn reconstructed_receiver(&mut self) -> Option<&mut ReconstructedMessagesReceiver> {
match self {
ConnectionState::New
| ConnectionState::Registered { .. }
| ConnectionState::Disconnected => None,
ConnectionState::Connected {
reconstructed_receiver,
..
} => Some(reconstructed_receiver),
}
}
pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> {
match self {
ConnectionState::New
| ConnectionState::Connected { .. }
| ConnectionState::Disconnected => None,
ConnectionState::Registered {
gateway_endpoint_config: gateway_config,
..
} => Some(gateway_config),
}
}
pub(super) fn nym_address(&self) -> Option<&Recipient> {
match self {
ConnectionState::New | ConnectionState::Disconnected => None,
ConnectionState::Registered { nym_address, .. }
| ConnectionState::Connected { nym_address, .. } => Some(nym_address),
}
}
pub(super) fn task_manager(&mut self) -> Option<&mut TaskManager> {
match self {
ConnectionState::New
| ConnectionState::Registered { .. }
| ConnectionState::Disconnected => None,
ConnectionState::Connected { task_manager, .. } => Some(task_manager),
}
}
}
+142
View File
@@ -0,0 +1,142 @@
use client_core::{
client::key_manager::KeyManager, config::persistence::key_pathfinder::ClientKeyPathfinder,
};
use crypto::asymmetric::{encryption, identity};
use gateway_requests::registration::handshake::SharedKeys;
use nymsphinx::acknowledgements::AckKey;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug)]
pub enum KeyMode {
/// Use existing key files if they exists, otherwise create new ones.
Keep,
/// Create new keys, overwriting any potential previously existing keys.
Overwrite,
}
impl KeyMode {
pub(crate) fn is_keep(&self) -> bool {
matches!(self, KeyMode::Keep)
}
}
pub enum GatewayKeyMode {
/// Keep shared gateway key if found, otherwise create a new one.
Keep,
/// Create a new shared key and overwrite any potential existing one.
Overwrite,
}
impl GatewayKeyMode {
pub(crate) fn is_keep(&self) -> bool {
matches!(self, GatewayKeyMode::Keep)
}
}
#[derive(Clone, Debug)]
pub struct KeyPaths {
// Determines how to handle existing key files found.
pub operating_mode: KeyMode,
// Client identity keys
pub private_identity: PathBuf,
pub public_identity: PathBuf,
// Client encryption keys
pub private_encryption: PathBuf,
pub public_encryption: PathBuf,
// Key for handling acks
pub ack_key: PathBuf,
// Key setup after authenticating with a gateway
pub gateway_shared_key: PathBuf,
// The key isn't much use without knowing which entity it refers to.
// This is an `Option` in case the end user might want to read write keys to storage, but
// handle the gateway configuration manually through `set_gateway_endpoint`.
// WIP(JON): make it an option?
pub gateway_endpoint_config: PathBuf,
pub credential_database_path: PathBuf,
pub reply_surb_database_path: PathBuf,
}
pub struct Keys {
pub identity_keypair: identity::KeyPair,
pub encryption_keypair: encryption::KeyPair,
pub ack_key: AckKey,
pub gateway_shared_key: SharedKeys,
}
//#[derive(Clone, Debug)]
//pub struct GatewaySetup {
// pub gateway_shared_key: PathBuf,
// pub gateway_endpoint_config: GatewayEndpointConfig,
// //pub gateway_endpoint_config: GatewayConfig,
//}
//#[derive(Clone, Debug)]
//pub enum GatewayConfig {
// File(PathBuf),
// Struct(GatewayEndpointConfig),
//}
impl KeyPaths {
pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Self {
assert!(!dir.is_file(), "WIP");
Self {
// These filenames were chosen to match the ones we use in `nym-client`. Consider
// changing the defaults
operating_mode,
private_identity: dir.join("private_identity.pem"),
public_identity: dir.join("public_identity.pem"),
private_encryption: dir.join("private_encryption.pem"),
public_encryption: dir.join("public_encryption.pem"),
ack_key: dir.join("ack_key.pem"),
gateway_shared_key: dir.join("gateway_shared.pem"),
gateway_endpoint_config: dir.join("gateway_endpoint_config.toml"),
credential_database_path: dir.join("db.sqlite"),
reply_surb_database_path: dir.join("persistent_reply_store.sqlite"),
}
}
}
impl From<KeyPaths> for ClientKeyPathfinder {
fn from(paths: KeyPaths) -> Self {
Self {
identity_private_key: paths.private_identity,
identity_public_key: paths.public_identity,
encryption_private_key: paths.private_encryption,
encryption_public_key: paths.public_encryption,
gateway_shared_key: paths.gateway_shared_key,
ack_key: paths.ack_key,
}
}
}
impl From<Keys> for KeyManager {
fn from(keys: Keys) -> Self {
KeyManager::new_from_keys(
keys.identity_keypair,
keys.encryption_keypair,
keys.gateway_shared_key,
keys.ack_key,
)
}
}
//pub struct GatewayConfigPath {
// pub path: PathBuf,
//}
//
//impl GatewayConfigPath {
// pub fn new_from_dir(dir: PathBuf) -> Self {
// assert!(!dir.is_file(), "WIP");
// Self {
// path: dir.join("gateway_endpoint_config.toml"),
// }
// }
//}
-62
View File
@@ -1,62 +0,0 @@
use std::path::PathBuf;
pub struct Client {
pub nym_address: String,
config: Config,
}
impl Client {
/// Create a new mixnet client. If no config options are supplied, creates a new client with ephemeral keys
/// stored in RAM, which will be discarded at application close.
///
/// Callers have the option of supplying futher parameters to store persistent identities at a location on-disk,
/// if desired.
pub fn new(config_option: Option<Config>) -> Client {
if config_option.is_none() {
let config = Self::some_minimal_config();
let nym_address = Self::new_nym_address();
Client {
config,
nym_address,
}
} else {
let self_address = Self::new_nym_address();
let nym_address = Self::new_nym_address();
Client {
config: config_option.unwrap(),
nym_address,
}
}
}
/// Connects to the mixnet via the gateway in the client config
pub fn connect_to_mixnet(&self) {}
/// Sets the callback function which is run when the client receives a message
/// from the mixnet
pub fn on_receive(&mut self, message: &str) {
println!("Message received: {}", message);
}
/// Sends stringy data to the supplied Nym address
pub fn send_str(&self, address: &str, message: &str) {}
/// Sends bytes to the supplied Nym address
pub fn send_bytes(&self, address: &str, message: Vec<u8>) {}
fn new_nym_address() -> String {
"the.nym@address".to_string()
}
fn some_minimal_config() -> Config {
let mut keys_path = PathBuf::new();
Config {
keys: Some(keys_path),
}
}
}
pub struct Config {
pub keys: Option<PathBuf>,
}