rust-sdk: extract out builder

This commit is contained in:
Jon Häggblad
2023-01-10 01:36:27 +01:00
parent 96ab4325e3
commit 666cbcf2cc
6 changed files with 99 additions and 188 deletions
@@ -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;
+3 -3
View File
@@ -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
+2 -5
View File
@@ -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
+1 -1
View File
@@ -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;
+82 -74
View File
@@ -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;
}
}
+8 -102
View File
@@ -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),
}
}
}