use crate::client::base_client::storage::helpers::{
has_gateway_details, load_active_gateway_details, load_client_keys, load_gateway_details,
store_gateway_details, update_stored_published_data_gateway,
};
use crate::client::key_manager::ClientKeys;
use crate::client::key_manager::persistence::KeyStore;
use crate::error::ClientCoreError;
use crate::init::helpers::{
choose_gateway_by_latency, get_specified_gateway, uniformly_random_gateway,
};
use crate::init::types::{
GatewaySelectionSpecification, GatewaySetup, InitialisationResult, SelectedGateway,
};
use nym_client_core_gateways_storage::{GatewayDetails, GatewayRegistration};
use nym_client_core_gateways_storage::{GatewayPublishedData, GatewaysDetailsStore};
use nym_gateway_client::client::InitGatewayClient;
use nym_topology::node::RoutingNode;
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use serde::Serialize;
#[cfg(unix)]
use std::{os::fd::RawFd, sync::Arc};
pub mod helpers;
pub mod types;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod websockets;
pub async fn generate_new_client_keys<K, R>(
rng: &mut R,
key_store: &K,
) -> Result<(), ClientCoreError>
where
R: RngCore + CryptoRng,
K: KeyStore,
K::StorageError: Send + Sync + 'static,
{
ClientKeys::generate_new(rng)
.persist_keys(key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})
}
async fn setup_new_gateway<K, D>(
key_store: &K,
details_store: &D,
selection_specification: GatewaySelectionSpecification,
available_gateways: Vec<RoutingNode>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{
tracing::trace!("Setting up new gateway");
let client_keys = load_client_keys(key_store).await?;
let mut rng = OsRng;
let selected_gateway = match selection_specification {
GatewaySelectionSpecification::UniformRemote {
must_use_tls,
no_hostname,
} => {
let gateway = uniformly_random_gateway(&mut rng, &available_gateways, must_use_tls)?;
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
}
GatewaySelectionSpecification::RemoteByLatency {
must_use_tls,
no_hostname,
} => {
let gateway =
choose_gateway_by_latency(&mut rng, &available_gateways, must_use_tls).await?;
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
}
GatewaySelectionSpecification::Specified {
must_use_tls,
no_hostname,
identity,
} => {
let gateway = get_specified_gateway(&identity, &available_gateways, must_use_tls)?;
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
}
GatewaySelectionSpecification::Custom {
gateway_identity,
additional_data,
} => SelectedGateway::custom(gateway_identity, additional_data)?,
};
let selected_id = selected_gateway.gateway_id().to_base58_string();
if has_gateway_details(details_store, &selected_id).await? {
return Err(ClientCoreError::AlreadyRegistered {
gateway_id: selected_id,
});
}
let (gateway_details, authenticated_ephemeral_client) = match selected_gateway {
SelectedGateway::Remote {
gateway_id,
gateway_listeners,
} => {
let our_identity = client_keys.identity_keypair();
let registration = helpers::register_with_gateway(
gateway_id,
gateway_listeners.clone(),
our_identity,
#[cfg(unix)]
connection_fd_callback,
)
.await?;
(
GatewayDetails::new_remote(
gateway_id,
registration.shared_keys,
GatewayPublishedData::new(gateway_listeners),
),
Some(registration.authenticated_ephemeral_client),
)
}
SelectedGateway::Custom {
gateway_id,
additional_data,
} => (
GatewayDetails::new_custom(gateway_id, additional_data),
None,
),
};
let gateway_registration = gateway_details.into();
store_gateway_details(details_store, &gateway_registration).await?;
Ok(InitialisationResult {
gateway_registration,
client_keys,
authenticated_ephemeral_client,
})
}
pub async fn refresh_gateway_published_data<D>(
details_store: &D,
registration: GatewayRegistration,
available_gateways: Vec<RoutingNode>,
must_use_tls: bool,
no_hostname: bool,
) -> Result<(), ClientCoreError>
where
D: GatewaysDetailsStore,
D::StorageError: Send + Sync + 'static,
{
let gateway_id = registration.gateway_id().to_base58_string();
tracing::trace!("Updating gateway details : {gateway_id}");
let gateway = get_specified_gateway(&gateway_id, &available_gateways, must_use_tls)?;
let selected_gateway = SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?;
let new_gateway_listeners = match selected_gateway {
SelectedGateway::Remote {
gateway_listeners, ..
} => gateway_listeners,
SelectedGateway::Custom { .. } => {
Err(ClientCoreError::UnexpectedCustomGatewaySelection)?
}
};
let new_published_data = GatewayPublishedData::new(new_gateway_listeners);
update_stored_published_data_gateway(
details_store,
®istration.gateway_id(),
&new_published_data,
)
.await?;
Ok(())
}
async fn use_loaded_gateway_details<K, D>(
key_store: &K,
details_store: &D,
gateway_id: Option<String>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{
let loaded_details = if let Some(gateway_id) = gateway_id {
load_gateway_details(details_store, &gateway_id).await?
} else {
load_active_gateway_details(details_store)
.await?
.registration
.ok_or(ClientCoreError::NoActiveGatewaySet)?
};
let loaded_keys = load_client_keys(key_store).await?;
Ok(InitialisationResult::new_loaded(
loaded_details,
loaded_keys,
))
}
fn reuse_gateway_connection(
authenticated_ephemeral_client: InitGatewayClient,
gateway_registration: GatewayRegistration,
client_keys: ClientKeys,
) -> InitialisationResult {
InitialisationResult {
gateway_registration,
client_keys,
authenticated_ephemeral_client: Some(authenticated_ephemeral_client),
}
}
pub async fn setup_gateway<K, D>(
setup: GatewaySetup,
key_store: &K,
details_store: &D,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{
tracing::debug!("Setting up gateway");
match setup {
GatewaySetup::MustLoad { gateway_id } => {
tracing::debug!("GatewaySetup::MustLoad with id: {gateway_id:?}");
use_loaded_gateway_details(key_store, details_store, gateway_id).await
}
GatewaySetup::New {
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback,
} => {
tracing::debug!("GatewaySetup::New with spec: {specification:?}");
setup_new_gateway(
key_store,
details_store,
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback,
)
.await
}
GatewaySetup::ReuseConnection {
authenticated_ephemeral_client,
gateway_details,
client_keys: managed_keys,
} => {
tracing::debug!("GatewaySetup::ReuseConnection");
Ok(reuse_gateway_connection(
*authenticated_ephemeral_client,
*gateway_details,
managed_keys,
))
}
}
}
pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) {
match std::fs::File::create(output_file) {
Ok(file) => match serde_json::to_writer_pretty(file, init_results) {
Ok(_) => println!("Saved: {output_file}"),
Err(err) => eprintln!("Could not save {output_file}: {err}"),
},
Err(err) => eprintln!("Could not save {output_file}: {err}"),
}
}