rust-sdk: extract out builder
This commit is contained in:
@@ -10,7 +10,7 @@ async fn main() {
|
||||
|
||||
let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpoints);
|
||||
|
||||
let mut client = mixnet::Client::new(Some(config), None).unwrap();
|
||||
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.
|
||||
@@ -29,10 +29,10 @@ async fn main() {
|
||||
}
|
||||
|
||||
// Connect to the mixnet, now we're listening for incoming
|
||||
client.connect_to_mixnet().await.unwrap();
|
||||
let client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
println!("Our client address is {}", client.nym_address().unwrap());
|
||||
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;
|
||||
|
||||
@@ -14,13 +14,13 @@ async fn main() {
|
||||
let keys = mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir);
|
||||
|
||||
// Provide key paths for the client to read/write keys to.
|
||||
let mut client = mixnet::Client::new(None, Some(keys)).unwrap();
|
||||
let client = mixnet::ClientBuilder::new(None, Some(keys)).unwrap();
|
||||
|
||||
// Connect to the mixnet, now we're listening for incoming
|
||||
client.connect_to_mixnet().await.unwrap();
|
||||
let mut client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address().unwrap();
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send a message throught the mixnet to ourselves
|
||||
|
||||
@@ -5,13 +5,10 @@ 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::new(None, None).unwrap();
|
||||
|
||||
// Connect to the mixnet, now we're listening for incoming
|
||||
client.connect_to_mixnet().await.unwrap();
|
||||
let mut client = mixnet::Client::connect().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address().unwrap();
|
||||
let our_address = client.nym_address();
|
||||
println!("Our client nym address is: {our_address}");
|
||||
|
||||
// Send a message throught the mixnet to ourselves
|
||||
|
||||
@@ -13,5 +13,5 @@ pub use nymsphinx::{
|
||||
pub use keys::Keys;
|
||||
pub use paths::{GatewayKeyMode, KeyMode, StoragePaths};
|
||||
|
||||
pub use client::Client;
|
||||
pub use client::{Client, ClientBuilder};
|
||||
pub use config::Config;
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
use std::path::Path;
|
||||
|
||||
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 tap::TapOptional;
|
||||
|
||||
use client_connections::TransmissionLane;
|
||||
use client_core::{
|
||||
client::{
|
||||
base_client::{non_wasm_helpers, BaseClientBuilder, CredentialsToggle},
|
||||
inbound_messages::InputMessage,
|
||||
key_manager::KeyManager,
|
||||
},
|
||||
config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig},
|
||||
};
|
||||
use task::TaskManager;
|
||||
|
||||
use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, StoragePaths};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
use super::{connection_state::ConnectionState, Config, GatewayKeyMode, Keys, StoragePaths};
|
||||
|
||||
pub struct Client {
|
||||
pub struct ClientBuilder {
|
||||
/// Keys handled by the client
|
||||
key_manager: KeyManager,
|
||||
|
||||
@@ -33,16 +35,35 @@ pub struct Client {
|
||||
|
||||
/// 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,
|
||||
state: BuilderState,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
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 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<Client> {
|
||||
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
|
||||
@@ -71,29 +92,18 @@ impl Client {
|
||||
client_core::init::new_client_keys()
|
||||
};
|
||||
|
||||
Ok(Client {
|
||||
Ok(Self {
|
||||
key_manager,
|
||||
config,
|
||||
storage_paths: paths,
|
||||
connection_state: ConnectionState::New,
|
||||
state: BuilderState::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 {
|
||||
fn has_gateway_key(&self) -> bool {
|
||||
self.key_manager.gateway_key_set()
|
||||
}
|
||||
|
||||
@@ -110,12 +120,12 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> {
|
||||
self.connection_state.gateway_endpoint_config()
|
||||
self.state.gateway_endpoint_config()
|
||||
}
|
||||
|
||||
pub async fn register_with_gateway(&mut self) -> Result<()> {
|
||||
assert!(
|
||||
matches!(self.connection_state, ConnectionState::New),
|
||||
matches!(self.state, BuilderState::New),
|
||||
"can only setup gateway when in `New` connection state"
|
||||
);
|
||||
|
||||
@@ -126,11 +136,8 @@ impl Client {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let nym_address = client_core::init::get_client_address(&self.key_manager, &gateway_config);
|
||||
|
||||
self.connection_state = ConnectionState::Registered {
|
||||
self.state = BuilderState::Registered {
|
||||
gateway_endpoint_config: gateway_config,
|
||||
nym_address,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@@ -163,21 +170,17 @@ impl Client {
|
||||
std::fs::read_to_string(gateway_endpoint_config_path)
|
||||
.map(|str| toml::from_str(&str))??;
|
||||
|
||||
let nym_address =
|
||||
client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config);
|
||||
|
||||
self.connection_state = ConnectionState::Registered {
|
||||
self.state = BuilderState::Registered {
|
||||
gateway_endpoint_config,
|
||||
nym_address,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Connects to the mixnet via the gateway in the client config
|
||||
pub async fn connect_to_mixnet(&mut self) -> Result<()> {
|
||||
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.connection_state, ConnectionState::New) {
|
||||
if matches!(self.state, BuilderState::New) {
|
||||
if let Some(paths) = &self.storage_paths {
|
||||
let paths = paths.clone();
|
||||
if self.has_gateway_key() {
|
||||
@@ -201,12 +204,12 @@ impl Client {
|
||||
|
||||
// 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 BuilderState::Registered { gateway_endpoint_config } = self.state else {
|
||||
todo!();
|
||||
};
|
||||
|
||||
let gateway_config = self.connection_state.gateway_endpoint_config().unwrap();
|
||||
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;
|
||||
@@ -216,7 +219,7 @@ impl Client {
|
||||
non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config);
|
||||
|
||||
let base_builder = BaseClientBuilder::new(
|
||||
gateway_config,
|
||||
&gateway_endpoint_config,
|
||||
&self.config.debug_config,
|
||||
self.key_manager.clone(),
|
||||
bandwidth_controller,
|
||||
@@ -225,8 +228,6 @@ impl Client {
|
||||
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();
|
||||
@@ -235,15 +236,33 @@ impl Client {
|
||||
// Register our receiver
|
||||
let reconstructed_receiver = client_output.register_receiver().unwrap();
|
||||
|
||||
self.connection_state = ConnectionState::Connected {
|
||||
Ok(Client {
|
||||
nym_address,
|
||||
key_manager: self.key_manager,
|
||||
client_input,
|
||||
client_output,
|
||||
client_state,
|
||||
reconstructed_receiver,
|
||||
task_manager: started_client.task_manager,
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -256,25 +275,20 @@ impl Client {
|
||||
/// 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();
|
||||
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>> {
|
||||
let receiver = self
|
||||
.connection_state
|
||||
.reconstructed_receiver()
|
||||
.tap_none(|| log::error!("Error: trying to wait without being connected"))?;
|
||||
|
||||
receiver.next().await
|
||||
self.reconstructed_receiver.next().await
|
||||
}
|
||||
|
||||
pub fn wait_for_messages_split(&mut self) -> Option<ReconstructedMessage> {
|
||||
@@ -295,13 +309,7 @@ impl Client {
|
||||
/// 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;
|
||||
self.task_manager.signal_shutdown().ok();
|
||||
self.task_manager.wait_for_shutdown().await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,21 @@
|
||||
use client_core::{
|
||||
client::{
|
||||
base_client::{ClientInput, ClientOutput, ClientState},
|
||||
received_buffer::ReconstructedMessagesReceiver,
|
||||
},
|
||||
config::GatewayEndpointConfig,
|
||||
};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use task::TaskManager;
|
||||
use client_core::config::GatewayEndpointConfig;
|
||||
|
||||
/// 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 {
|
||||
#[derive(Debug)]
|
||||
pub(super) enum BuilderState {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
impl BuilderState {
|
||||
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,
|
||||
BuilderState::New => None,
|
||||
BuilderState::Registered {
|
||||
gateway_endpoint_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),
|
||||
} => Some(gateway_endpoint_config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user