Merge pull request #2764 from nymtech/feature/rust-sdk-initial-version
Initial Rust client SDK
This commit is contained in:
@@ -6,6 +6,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
### Added
|
||||
|
||||
- nym-sdk: added initial version of a Rust client sdk
|
||||
|
||||
### Changed
|
||||
|
||||
- renamed all references to validator_api to nym_api
|
||||
|
||||
Generated
+7166
File diff suppressed because it is too large
Load Diff
@@ -73,6 +73,7 @@ members = [
|
||||
"gateway/gateway-requests",
|
||||
"integrations/bity",
|
||||
"mixnode",
|
||||
"sdk/rust/nym-sdk",
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
"nym-api",
|
||||
|
||||
@@ -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,
|
||||
@@ -80,6 +103,32 @@ impl ClientOutputStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum CredentialsToggle {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl CredentialsToggle {
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self == &CredentialsToggle::Enabled
|
||||
}
|
||||
|
||||
pub fn is_disabled(&self) -> bool {
|
||||
self == &CredentialsToggle::Disabled
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for CredentialsToggle {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
CredentialsToggle::Enabled
|
||||
} else {
|
||||
CredentialsToggle::Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BaseClientBuilder<'a, B> {
|
||||
// due to wasm limitations I had to split it like this : (
|
||||
gateway_config: &'a GatewayEndpointConfig,
|
||||
@@ -119,13 +168,13 @@ where
|
||||
key_manager: KeyManager,
|
||||
bandwidth_controller: Option<BandwidthController>,
|
||||
reply_storage_backend: B,
|
||||
disabled_credentials: bool,
|
||||
credentials_toggle: CredentialsToggle,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
) -> BaseClientBuilder<'a, B> {
|
||||
BaseClientBuilder {
|
||||
gateway_config,
|
||||
debug_config,
|
||||
disabled_credentials,
|
||||
disabled_credentials: credentials_toggle.is_disabled(),
|
||||
nym_api_endpoints,
|
||||
reply_storage_backend,
|
||||
bandwidth_controller,
|
||||
@@ -248,7 +297,7 @@ where
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
|
||||
let shared_key = if self.key_manager.gateway_key_set() {
|
||||
let shared_key = if self.key_manager.is_gateway_key_set() {
|
||||
Some(self.key_manager.gateway_shared_key())
|
||||
} else {
|
||||
None
|
||||
@@ -475,11 +524,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 +539,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,
|
||||
self, 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) -> reply_storage::Empty {
|
||||
reply_storage::Empty {
|
||||
min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::sync::Arc;
|
||||
// use the old key after new one was issued.
|
||||
|
||||
// Remember that Arc<T> has Deref implementation for T
|
||||
#[derive(Clone)]
|
||||
pub struct KeyManager {
|
||||
/// identity key associated with the client instance.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
@@ -57,16 +58,22 @@ impl KeyManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)]
|
||||
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
|
||||
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
|
||||
self.gateway_shared_key = Some(gateway_shared_key)
|
||||
pub fn 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),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_client_keys(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 +85,51 @@ 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_client_keys(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)
|
||||
}
|
||||
|
||||
/// Loads previously stored keys from the disk. Fails if client keys are not availabe, but the
|
||||
/// shared gateway key is optional.
|
||||
pub fn load_keys_but_gateway_is_optional(
|
||||
client_pathfinder: &ClientKeyPathfinder,
|
||||
) -> io::Result<Self> {
|
||||
let mut key_manager = Self::load_client_keys(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 +156,7 @@ 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())?
|
||||
}
|
||||
@@ -128,16 +165,60 @@ impl KeyManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
|
||||
match self.gateway_shared_key.as_ref() {
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"trying to store a non-existing key",
|
||||
))
|
||||
}
|
||||
Some(gate_key) => {
|
||||
pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Overwrite the existing identity keypair
|
||||
pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) {
|
||||
self.identity_keypair = Arc::new(id_keypair);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
|
||||
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
|
||||
Arc::clone(&self.identity_keypair)
|
||||
}
|
||||
|
||||
/// Overwrite the existing encryption keypair
|
||||
pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) {
|
||||
self.encryption_keypair = Arc::new(enc_keypair);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
|
||||
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
|
||||
Arc::clone(&self.encryption_keypair)
|
||||
}
|
||||
|
||||
/// Overwrite the existing ack key
|
||||
pub fn set_ack_key(&mut self, ack_key: AckKey) {
|
||||
self.ack_key = Arc::new(ack_key);
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`AckKey`].
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.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)]
|
||||
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
|
||||
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
|
||||
self.gateway_shared_key = Some(gateway_shared_key)
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`SharedKey`].
|
||||
// since this function is not fully public, it is not expected to be used externally and
|
||||
// hence it's up to us to ensure it's called in correct context
|
||||
@@ -149,12 +230,7 @@ impl KeyManager {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn gateway_key_set(&self) -> bool {
|
||||
pub fn is_gateway_key_set(&self) -> bool {
|
||||
self.gateway_shared_key.is_some()
|
||||
}
|
||||
|
||||
/// Gets an atomically reference counted pointer to [`AckKey`].
|
||||
pub fn ack_key(&self) -> Arc<AckKey> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-8
@@ -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;
|
||||
@@ -275,10 +286,7 @@ impl ActionController {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("ActionController: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,9 +535,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
shutdown.recv_timeout().await;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -19,10 +19,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,8 +22,8 @@ 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"),
|
||||
}
|
||||
@@ -40,6 +40,28 @@ 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 any_file_exists_and_return(&self) -> Option<PathBuf> {
|
||||
file_exists(&self.identity_public_key)
|
||||
.or_else(|| file_exists(&self.identity_private_key))
|
||||
.or_else(|| file_exists(&self.encryption_public_key))
|
||||
.or_else(|| file_exists(&self.encryption_private_key))
|
||||
.or_else(|| file_exists(&self.gateway_shared_key))
|
||||
.or_else(|| file_exists(&self.ack_key))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -64,3 +86,10 @@ impl ClientKeyPathfinder {
|
||||
&self.ack_key
|
||||
}
|
||||
}
|
||||
|
||||
fn file_exists(path: &Path) -> Option<PathBuf> {
|
||||
if matches!(path.try_exists(), Ok(true)) {
|
||||
return Some(path.to_path_buf());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,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(),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
@@ -116,10 +114,7 @@ impl MixnetResponseListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
self.shutdown.recv_timeout().await;
|
||||
log::debug!("MixnetResponseListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ use self::config::Config;
|
||||
use crate::client::helpers::InputSender;
|
||||
use crate::client::response_pusher::ResponsePusher;
|
||||
use client_connections::TransmissionLane;
|
||||
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
|
||||
use client_core::client::base_client::{
|
||||
BaseClientBuilder, ClientInput, ClientOutput, CredentialsToggle,
|
||||
};
|
||||
use client_core::client::replies::reply_storage::browser_backend;
|
||||
use client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
@@ -92,13 +94,19 @@ impl NymClientBuilder {
|
||||
future_to_promise(async move {
|
||||
console_log!("Starting the wasm client");
|
||||
|
||||
let disabled_credentials = if self.disabled_credentials {
|
||||
CredentialsToggle::Disabled
|
||||
} else {
|
||||
CredentialsToggle::Enabled
|
||||
};
|
||||
|
||||
let base_builder = BaseClientBuilder::new(
|
||||
&self.config.gateway_endpoint,
|
||||
&self.config.debug,
|
||||
self.key_manager,
|
||||
self.bandwidth_controller,
|
||||
self.reply_surb_storage_backend,
|
||||
self.disabled_credentials,
|
||||
disabled_credentials,
|
||||
vec![self.config.nym_api_url.clone()],
|
||||
);
|
||||
|
||||
|
||||
@@ -254,11 +254,7 @@ impl Controller {
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
self.shutdown.recv_timeout().await;
|
||||
log::debug!("SOCKS5 Controller: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
|
||||
|
||||
fn new(
|
||||
notify: watch::Receiver<()>,
|
||||
@@ -231,7 +231,6 @@ impl TaskClient {
|
||||
let (_notify_tx, notify_rx) = watch::channel(());
|
||||
let (task_halt_tx, _task_halt_rx) = mpsc::unbounded_channel();
|
||||
let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel();
|
||||
//let (task_status_tx, _task_status_rx) = futures::channel::mpsc::unbounded();
|
||||
let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128);
|
||||
TaskClient {
|
||||
shutdown: false,
|
||||
@@ -280,9 +279,12 @@ impl TaskClient {
|
||||
return pending().await;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::timeout(Self::SHUTDOWN_TIMEOUT, self.recv())
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
tokio::time::timeout(
|
||||
Self::SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT,
|
||||
self.recv(),
|
||||
)
|
||||
.await
|
||||
.expect("Task stopped without shutdown called");
|
||||
}
|
||||
|
||||
pub fn is_shutdown_poll(&mut self) -> bool {
|
||||
@@ -300,8 +302,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
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1
-1
@@ -2932,7 +2932,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.5"
|
||||
version = "1.1.6"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2 0.3.4",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "nym-sdk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
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]
|
||||
pretty_env_logger = "0.4.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
logging = { path = "../../../common/logging" }
|
||||
@@ -0,0 +1,62 @@
|
||||
use nym_sdk::mixnet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
logging::setup_logging();
|
||||
|
||||
// We can set a few options
|
||||
let user_chosen_gateway_id = None;
|
||||
let nym_api_endpoints = vec!["https://validator.nymtech.net/api/".parse().unwrap()];
|
||||
|
||||
let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpoints);
|
||||
|
||||
let mut client = mixnet::ClientBuilder::new(Some(config), None).unwrap();
|
||||
|
||||
// Just some plain data to pretend we have some external storage that the application
|
||||
// implementer is using.
|
||||
let mut mock_storage = MockStorage::empty();
|
||||
|
||||
// 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).
|
||||
let first_run = true;
|
||||
if first_run {
|
||||
client.register_with_gateway().await.unwrap();
|
||||
mock_storage.write(client.get_keys(), client.get_gateway_endpoint().unwrap());
|
||||
} else {
|
||||
let (keys, gateway_config) = mock_storage.read();
|
||||
client.set_keys(keys);
|
||||
client.set_gateway_endpoint(gateway_config);
|
||||
}
|
||||
|
||||
// Connect to the mixnet, now we're listening for incoming
|
||||
let client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
println!("Our client address is {}", client.nym_address());
|
||||
|
||||
// Send important info up the pipe to a buddy
|
||||
client.send_str("foo.bar@blah", "flappappa").await;
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
struct MockStorage {
|
||||
pub gateway_config: Option<mixnet::GatewayEndpointConfig>,
|
||||
pub keys: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl MockStorage {
|
||||
fn read(&self) -> (mixnet::Keys, mixnet::GatewayEndpointConfig) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn write(&mut self, _keys: mixnet::KeysArc, _gateway_config: &mixnet::GatewayEndpointConfig) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Self {
|
||||
gateway_config: None,
|
||||
keys: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nym_sdk::mixnet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
logging::setup_logging();
|
||||
|
||||
// Specify some config options
|
||||
let config_dir = PathBuf::from("/tmp/mixnet-client");
|
||||
|
||||
// Setting `KeyMode::Keep` will use existing keys, and existing config, if there is one.
|
||||
// Regardles of `user_chosen_gateway`.
|
||||
let keys = mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir).unwrap();
|
||||
|
||||
// Provide key paths for the client to read/write keys to.
|
||||
let client = mixnet::ClientBuilder::new(None, Some(keys)).unwrap();
|
||||
|
||||
// Connect to the mixnet, now we're listening for incoming
|
||||
let mut client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
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,23 @@
|
||||
use nym_sdk::mixnet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
logging::setup_logging();
|
||||
|
||||
// Passing no config makes the client fire up an ephemeral session and figure shit out on its own
|
||||
let mut client = mixnet::Client::connect().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
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");
|
||||
client
|
||||
.on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
|
||||
.await;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use nym_sdk::mixnet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
logging::setup_logging();
|
||||
|
||||
// Create client builder, including ephemeral keys. The builder can be usable in the context
|
||||
// where you don't want to connect just yet
|
||||
let client = mixnet::ClientBuilder::new(None, None).unwrap();
|
||||
|
||||
// Now we connect to the mixnet, using ephemeral keys already created
|
||||
let mut client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
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");
|
||||
client
|
||||
.on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
|
||||
.await;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("i/o error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("toml serialization error: {0}")]
|
||||
TomlSerializationError(#[from] toml::ser::Error),
|
||||
#[error("toml deserialization error: {0}")]
|
||||
TomlDeserializationError(#[from] toml::de::Error),
|
||||
#[error(transparent)]
|
||||
ClientCoreError(#[from] client_core::error::ClientCoreError),
|
||||
|
||||
#[error("key file encountered that we don't want to overwrite: {0}")]
|
||||
DontOverwrite(PathBuf),
|
||||
#[error("shared gateway key file encountered that we don't want to overwrite: {0}")]
|
||||
DontOverwriteGatewayKey(PathBuf),
|
||||
#[error("no gateway config available for writing")]
|
||||
GatewayNotAvailableForWriting,
|
||||
|
||||
#[error("expected to received a directory, received: {0}")]
|
||||
ExpectedDirectory(PathBuf),
|
||||
}
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod error;
|
||||
pub mod mixnet;
|
||||
@@ -0,0 +1,17 @@
|
||||
mod client;
|
||||
mod config;
|
||||
mod connection_state;
|
||||
mod keys;
|
||||
mod paths;
|
||||
|
||||
pub use client_core::config::GatewayEndpointConfig;
|
||||
pub use nymsphinx::{
|
||||
addressing::clients::{ClientIdentity, Recipient},
|
||||
receiver::ReconstructedMessage,
|
||||
};
|
||||
|
||||
pub use keys::{Keys, KeysArc};
|
||||
pub use paths::{GatewayKeyMode, KeyMode, StoragePaths};
|
||||
|
||||
pub use client::{Client, ClientBuilder};
|
||||
pub use config::Config;
|
||||
@@ -0,0 +1,331 @@
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use client_connections::TransmissionLane;
|
||||
use client_core::{
|
||||
client::{
|
||||
base_client::{
|
||||
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
|
||||
CredentialsToggle,
|
||||
},
|
||||
inbound_messages::InputMessage,
|
||||
key_manager::KeyManager,
|
||||
received_buffer::ReconstructedMessagesReceiver,
|
||||
},
|
||||
config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig},
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use nymsphinx::{
|
||||
addressing::clients::{ClientIdentity, Recipient},
|
||||
receiver::ReconstructedMessage,
|
||||
};
|
||||
use task::TaskManager;
|
||||
|
||||
use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, KeysArc, StoragePaths};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub struct ClientBuilder {
|
||||
/// Keys handled by the client
|
||||
key_manager: KeyManager,
|
||||
|
||||
/// Client configuration
|
||||
config: Config,
|
||||
|
||||
/// Paths for client keys, including identity, encryption, ack and shared gateway keys.
|
||||
storage_paths: Option<StoragePaths>,
|
||||
|
||||
/// The client can be in one of multiple states, depending on how it is created and if it's
|
||||
/// connected to the mixnet.
|
||||
state: BuilderState,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
/// 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>, paths: Option<StoragePaths>) -> Result<Self> {
|
||||
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 paths) = paths {
|
||||
let path_finder = ClientKeyPathfinder::from(paths.clone());
|
||||
|
||||
// Try load keys
|
||||
match KeyManager::load_keys_but_gateway_is_optional(&path_finder) {
|
||||
Ok(key_manager) => key_manager,
|
||||
Err(err) => {
|
||||
log::debug!("Not loading keys: {err}");
|
||||
if let Some(path) = path_finder.any_file_exists_and_return() {
|
||||
if paths.operating_mode.is_keep() {
|
||||
return Err(Error::DontOverwrite(path));
|
||||
}
|
||||
}
|
||||
|
||||
// Double check using a function that has slightly different internal logic. I
|
||||
// know this is a bit defensive, but I don't want to overwrite
|
||||
assert!(!(path_finder.any_file_exists() && paths.operating_mode.is_keep()));
|
||||
|
||||
// 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(Self {
|
||||
key_manager,
|
||||
config,
|
||||
storage_paths: paths,
|
||||
state: BuilderState::New,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn has_gateway_key(&self) -> bool {
|
||||
self.key_manager.is_gateway_key_set()
|
||||
}
|
||||
|
||||
pub fn set_keys(&mut self, keys: Keys) {
|
||||
self.key_manager.set_identity_keypair(keys.identity_keypair);
|
||||
self.key_manager
|
||||
.set_encryption_keypair(keys.encryption_keypair);
|
||||
self.key_manager.set_ack_key(keys.ack_key);
|
||||
|
||||
self.key_manager
|
||||
.insert_gateway_shared_key(Arc::new(keys.gateway_shared_key));
|
||||
}
|
||||
|
||||
pub fn get_keys(&self) -> KeysArc {
|
||||
KeysArc::from(&self.key_manager)
|
||||
}
|
||||
|
||||
pub fn set_gateway_endpoint(&mut self, gateway_endpoint_config: GatewayEndpointConfig) {
|
||||
self.state = BuilderState::Registered {
|
||||
gateway_endpoint_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> {
|
||||
self.state.gateway_endpoint_config()
|
||||
}
|
||||
|
||||
pub async fn register_with_gateway(&mut self) -> Result<()> {
|
||||
assert!(
|
||||
matches!(self.state, BuilderState::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?;
|
||||
|
||||
self.state = BuilderState::Registered {
|
||||
gateway_endpoint_config: gateway_config,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_gateway_key(&self, paths: StoragePaths, key_mode: &GatewayKeyMode) -> Result<()> {
|
||||
let path_finder = ClientKeyPathfinder::from(paths);
|
||||
if path_finder.gateway_key_file_exists() && key_mode.is_keep() {
|
||||
return Err(Error::DontOverwriteGatewayKey(
|
||||
path_finder.gateway_shared_key().to_path_buf(),
|
||||
));
|
||||
};
|
||||
self.key_manager.store_gateway_key(&path_finder)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_gateway_endpoint_config(&self, gateway_endpoint_config_path: &Path) -> Result<()> {
|
||||
let gateway_endpoint_config = toml::to_string(
|
||||
self.get_gateway_endpoint()
|
||||
.ok_or(Error::GatewayNotAvailableForWriting)?,
|
||||
)?;
|
||||
|
||||
// Ensure the whole directory structure exists
|
||||
if let Some(parent_dir) = gateway_endpoint_config_path.parent() {
|
||||
std::fs::create_dir_all(parent_dir)?;
|
||||
}
|
||||
std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_gateway_endpoint_config(&mut self, gateway_endpoint_config_path: &Path) -> Result<()> {
|
||||
let gateway_endpoint_config: GatewayEndpointConfig =
|
||||
std::fs::read_to_string(gateway_endpoint_config_path)
|
||||
.map(|str| toml::from_str(&str))??;
|
||||
|
||||
self.state = BuilderState::Registered {
|
||||
gateway_endpoint_config,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Connects to the mixnet via the gateway in the client config
|
||||
pub async fn connect_to_mixnet(mut self) -> Result<Client> {
|
||||
// For some simple cases we can figure how to setup gateway without it having to have been
|
||||
// called in advance.
|
||||
if matches!(self.state, BuilderState::New) {
|
||||
if let Some(paths) = &self.storage_paths {
|
||||
let paths = paths.clone();
|
||||
if self.has_gateway_key() {
|
||||
// If we have a gateway key from client, then we can just read the corresponding
|
||||
// config
|
||||
log::trace!("Gateway key found: loading");
|
||||
self.read_gateway_endpoint_config(&paths.gateway_endpoint_config)?;
|
||||
} else {
|
||||
// If we didn't find any shared gateway key during creation, that means we first
|
||||
// need to register a gateway
|
||||
log::trace!("Gateway key NOT found: registering new");
|
||||
self.register_with_gateway().await?;
|
||||
self.write_gateway_key(paths.clone(), &GatewayKeyMode::Overwrite)?;
|
||||
self.write_gateway_endpoint_config(&paths.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.
|
||||
let BuilderState::Registered { gateway_endpoint_config } = self.state else {
|
||||
todo!();
|
||||
};
|
||||
|
||||
let nym_address =
|
||||
client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config);
|
||||
|
||||
// TODO: we currently don't support having a bandwidth controller
|
||||
let bandwidth_controller = None;
|
||||
|
||||
// TODO: currently we only support in-memory reply surb storage.
|
||||
let reply_storage_backend =
|
||||
non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config);
|
||||
|
||||
let base_builder = BaseClientBuilder::new(
|
||||
&gateway_endpoint_config,
|
||||
&self.config.debug_config,
|
||||
self.key_manager.clone(),
|
||||
bandwidth_controller,
|
||||
reply_storage_backend,
|
||||
CredentialsToggle::Disabled,
|
||||
self.config.nym_api_endpoints.clone(),
|
||||
);
|
||||
|
||||
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();
|
||||
|
||||
Ok(Client {
|
||||
nym_address,
|
||||
key_manager: self.key_manager,
|
||||
client_input,
|
||||
client_output,
|
||||
client_state,
|
||||
reconstructed_receiver,
|
||||
task_manager: started_client.task_manager,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
nym_address: Recipient,
|
||||
|
||||
/// Keys handled by the client
|
||||
key_manager: KeyManager,
|
||||
|
||||
client_input: ClientInput,
|
||||
|
||||
#[allow(dead_code)]
|
||||
client_output: ClientOutput,
|
||||
|
||||
#[allow(dead_code)]
|
||||
client_state: ClientState,
|
||||
|
||||
reconstructed_receiver: ReconstructedMessagesReceiver,
|
||||
|
||||
task_manager: TaskManager,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub async fn connect() -> Result<Self> {
|
||||
let client = ClientBuilder::new(None, None)?;
|
||||
client.connect_to_mixnet().await
|
||||
}
|
||||
|
||||
/// 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) -> &Recipient {
|
||||
&self.nym_address
|
||||
}
|
||||
|
||||
/// 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 lane = TransmissionLane::General;
|
||||
let recipient = Recipient::try_from_base58_string(address).unwrap();
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane);
|
||||
self.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>> {
|
||||
self.reconstructed_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) {
|
||||
self.task_manager.signal_shutdown().ok();
|
||||
self.task_manager.wait_for_shutdown().await;
|
||||
}
|
||||
}
|
||||
@@ -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::NYM_API.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,21 @@
|
||||
use client_core::config::GatewayEndpointConfig;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum BuilderState {
|
||||
New,
|
||||
Registered {
|
||||
gateway_endpoint_config: GatewayEndpointConfig,
|
||||
},
|
||||
}
|
||||
|
||||
impl BuilderState {
|
||||
pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> {
|
||||
match self {
|
||||
BuilderState::New => None,
|
||||
BuilderState::Registered {
|
||||
gateway_endpoint_config,
|
||||
..
|
||||
} => Some(gateway_endpoint_config),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
|
||||
pub struct Keys {
|
||||
pub identity_keypair: identity::KeyPair,
|
||||
pub encryption_keypair: encryption::KeyPair,
|
||||
pub ack_key: AckKey,
|
||||
pub gateway_shared_key: SharedKeys,
|
||||
}
|
||||
|
||||
pub struct KeysArc {
|
||||
pub identity_keypair: Arc<identity::KeyPair>,
|
||||
pub encryption_keypair: Arc<encryption::KeyPair>,
|
||||
pub ack_key: Arc<AckKey>,
|
||||
pub gateway_shared_key: Arc<SharedKeys>,
|
||||
}
|
||||
|
||||
impl From<Keys> for KeyManager {
|
||||
fn from(keys: Keys) -> Self {
|
||||
KeyManager::from_keys(
|
||||
keys.identity_keypair,
|
||||
keys.encryption_keypair,
|
||||
keys.gateway_shared_key,
|
||||
keys.ack_key,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keys> for KeysArc {
|
||||
fn from(keys: Keys) -> Self {
|
||||
KeysArc {
|
||||
identity_keypair: keys.identity_keypair.into(),
|
||||
encryption_keypair: keys.encryption_keypair.into(),
|
||||
ack_key: keys.ack_key.into(),
|
||||
gateway_shared_key: keys.gateway_shared_key.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KeyManager> for KeysArc {
|
||||
fn from(key_manager: &KeyManager) -> Self {
|
||||
KeysArc {
|
||||
identity_keypair: key_manager.identity_keypair(),
|
||||
encryption_keypair: key_manager.encryption_keypair(),
|
||||
ack_key: key_manager.ack_key(),
|
||||
gateway_shared_key: key_manager.gateway_shared_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
#[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 StoragePaths {
|
||||
// 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.
|
||||
pub gateway_endpoint_config: PathBuf,
|
||||
|
||||
// The database containing credentials
|
||||
pub credential_database_path: PathBuf,
|
||||
|
||||
// The database storing reply surbs in-between sessions
|
||||
pub reply_surb_database_path: PathBuf,
|
||||
}
|
||||
|
||||
impl StoragePaths {
|
||||
pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Result<Self> {
|
||||
if !dir.is_file() {
|
||||
return Err(Error::ExpectedDirectory(dir.to_owned()));
|
||||
}
|
||||
|
||||
Ok(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<StoragePaths> for ClientKeyPathfinder {
|
||||
fn from(paths: StoragePaths) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user