nym_client_core/init/
mod.rs

1// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4//! Collection of initialization steps used by client implementations
5
6use crate::client::base_client::storage::helpers::{
7    has_gateway_details, load_active_gateway_details, load_client_keys, load_gateway_details,
8    store_gateway_details, update_stored_published_data_gateway,
9};
10use crate::client::key_manager::persistence::KeyStore;
11use crate::client::key_manager::ClientKeys;
12use crate::error::ClientCoreError;
13use crate::init::helpers::{
14    choose_gateway_by_latency, get_specified_gateway, uniformly_random_gateway,
15};
16use crate::init::types::{
17    GatewaySelectionSpecification, GatewaySetup, InitialisationResult, SelectedGateway,
18};
19use nym_client_core_gateways_storage::{GatewayDetails, GatewayRegistration};
20use nym_client_core_gateways_storage::{GatewayPublishedData, GatewaysDetailsStore};
21use nym_gateway_client::client::InitGatewayClient;
22use nym_topology::node::RoutingNode;
23use rand::rngs::OsRng;
24use rand::{CryptoRng, RngCore};
25use serde::Serialize;
26#[cfg(unix)]
27use std::{os::fd::RawFd, sync::Arc};
28
29pub mod helpers;
30pub mod types;
31#[cfg(not(target_arch = "wasm32"))]
32pub(crate) mod websockets;
33
34// helpers for error wrapping
35
36pub async fn generate_new_client_keys<K, R>(
37    rng: &mut R,
38    key_store: &K,
39) -> Result<(), ClientCoreError>
40where
41    R: RngCore + CryptoRng,
42    K: KeyStore,
43    K::StorageError: Send + Sync + 'static,
44{
45    ClientKeys::generate_new(rng)
46        .persist_keys(key_store)
47        .await
48        .map_err(|source| ClientCoreError::KeyStoreError {
49            source: Box::new(source),
50        })
51}
52
53async fn setup_new_gateway<K, D>(
54    key_store: &K,
55    details_store: &D,
56    selection_specification: GatewaySelectionSpecification,
57    available_gateways: Vec<RoutingNode>,
58    #[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
59) -> Result<InitialisationResult, ClientCoreError>
60where
61    K: KeyStore,
62    D: GatewaysDetailsStore,
63    K::StorageError: Send + Sync + 'static,
64    D::StorageError: Send + Sync + 'static,
65{
66    tracing::trace!("Setting up new gateway");
67
68    // if we're setting up new gateway, we must have had generated long-term client keys before
69    let client_keys = load_client_keys(key_store).await?;
70
71    let mut rng = OsRng;
72
73    let selected_gateway = match selection_specification {
74        GatewaySelectionSpecification::UniformRemote {
75            must_use_tls,
76            no_hostname,
77        } => {
78            let gateway = uniformly_random_gateway(&mut rng, &available_gateways, must_use_tls)?;
79            SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
80        }
81        GatewaySelectionSpecification::RemoteByLatency {
82            must_use_tls,
83            no_hostname,
84        } => {
85            let gateway =
86                choose_gateway_by_latency(&mut rng, &available_gateways, must_use_tls).await?;
87            SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
88        }
89        GatewaySelectionSpecification::Specified {
90            must_use_tls,
91            no_hostname,
92            identity,
93        } => {
94            let gateway = get_specified_gateway(&identity, &available_gateways, must_use_tls)?;
95            SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
96        }
97        GatewaySelectionSpecification::Custom {
98            gateway_identity,
99            additional_data,
100        } => SelectedGateway::custom(gateway_identity, additional_data)?,
101    };
102
103    // check if we already have details associated with this particular gateway
104    // and if so, see if we can overwrite it
105    let selected_id = selected_gateway.gateway_id().to_base58_string();
106    if has_gateway_details(details_store, &selected_id).await? {
107        return Err(ClientCoreError::AlreadyRegistered {
108            gateway_id: selected_id,
109        });
110    }
111
112    let (gateway_details, authenticated_ephemeral_client) = match selected_gateway {
113        SelectedGateway::Remote {
114            gateway_id,
115
116            gateway_listeners,
117        } => {
118            // if we're using a 'normal' gateway setup, do register
119            let our_identity = client_keys.identity_keypair();
120
121            let registration = helpers::register_with_gateway(
122                gateway_id,
123                gateway_listeners.clone(),
124                our_identity,
125                #[cfg(unix)]
126                connection_fd_callback,
127            )
128            .await?;
129            (
130                GatewayDetails::new_remote(
131                    gateway_id,
132                    registration.shared_keys,
133                    GatewayPublishedData::new(gateway_listeners),
134                ),
135                Some(registration.authenticated_ephemeral_client),
136            )
137        }
138        SelectedGateway::Custom {
139            gateway_id,
140            additional_data,
141        } => (
142            GatewayDetails::new_custom(gateway_id, additional_data),
143            None,
144        ),
145    };
146
147    let gateway_registration = gateway_details.into();
148
149    // persist gateway details
150    store_gateway_details(details_store, &gateway_registration).await?;
151
152    Ok(InitialisationResult {
153        gateway_registration,
154        client_keys,
155        authenticated_ephemeral_client,
156    })
157}
158
159pub async fn refresh_gateway_published_data<D>(
160    details_store: &D,
161    registration: GatewayRegistration,
162    available_gateways: Vec<RoutingNode>,
163    must_use_tls: bool,
164    no_hostname: bool,
165) -> Result<(), ClientCoreError>
166where
167    D: GatewaysDetailsStore,
168    D::StorageError: Send + Sync + 'static,
169{
170    let gateway_id = registration.gateway_id().to_base58_string();
171    tracing::trace!("Updating gateway details : {gateway_id}");
172
173    let gateway = get_specified_gateway(&gateway_id, &available_gateways, must_use_tls)?;
174    let selected_gateway = SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?;
175
176    let new_gateway_listeners = match selected_gateway {
177        SelectedGateway::Remote {
178            gateway_listeners, ..
179        } => gateway_listeners,
180        SelectedGateway::Custom { .. } => {
181            // this should not happen, as `from_topology_node` returns a Remote
182            Err(ClientCoreError::UnexpectedCustomGatewaySelection)?
183        }
184    };
185
186    let new_published_data = GatewayPublishedData::new(new_gateway_listeners);
187
188    // update gateway details
189    update_stored_published_data_gateway(
190        details_store,
191        &registration.gateway_id(),
192        &new_published_data,
193    )
194    .await?;
195
196    Ok(())
197}
198
199async fn use_loaded_gateway_details<K, D>(
200    key_store: &K,
201    details_store: &D,
202    gateway_id: Option<String>,
203) -> Result<InitialisationResult, ClientCoreError>
204where
205    K: KeyStore,
206    D: GatewaysDetailsStore,
207    K::StorageError: Send + Sync + 'static,
208    D::StorageError: Send + Sync + 'static,
209{
210    let loaded_details = if let Some(gateway_id) = gateway_id {
211        load_gateway_details(details_store, &gateway_id).await?
212    } else {
213        load_active_gateway_details(details_store)
214            .await?
215            .registration
216            .ok_or(ClientCoreError::NoActiveGatewaySet)?
217    };
218
219    let loaded_keys = load_client_keys(key_store).await?;
220
221    // no need to persist anything as we got everything from the storage
222    Ok(InitialisationResult::new_loaded(
223        loaded_details,
224        loaded_keys,
225    ))
226}
227
228fn reuse_gateway_connection(
229    authenticated_ephemeral_client: InitGatewayClient,
230    gateway_registration: GatewayRegistration,
231    client_keys: ClientKeys,
232) -> InitialisationResult {
233    InitialisationResult {
234        gateway_registration,
235        client_keys,
236        authenticated_ephemeral_client: Some(authenticated_ephemeral_client),
237    }
238}
239
240pub async fn setup_gateway<K, D>(
241    setup: GatewaySetup,
242    key_store: &K,
243    details_store: &D,
244) -> Result<InitialisationResult, ClientCoreError>
245where
246    K: KeyStore,
247    D: GatewaysDetailsStore,
248    K::StorageError: Send + Sync + 'static,
249    D::StorageError: Send + Sync + 'static,
250{
251    tracing::debug!("Setting up gateway");
252    match setup {
253        GatewaySetup::MustLoad { gateway_id } => {
254            tracing::debug!("GatewaySetup::MustLoad with id: {gateway_id:?}");
255            use_loaded_gateway_details(key_store, details_store, gateway_id).await
256        }
257        GatewaySetup::New {
258            specification,
259            available_gateways,
260            #[cfg(unix)]
261            connection_fd_callback,
262        } => {
263            tracing::debug!("GatewaySetup::New with spec: {specification:?}");
264            setup_new_gateway(
265                key_store,
266                details_store,
267                specification,
268                available_gateways,
269                #[cfg(unix)]
270                connection_fd_callback,
271            )
272            .await
273        }
274        GatewaySetup::ReuseConnection {
275            authenticated_ephemeral_client,
276            gateway_details,
277            client_keys: managed_keys,
278        } => {
279            tracing::debug!("GatewaySetup::ReuseConnection");
280            Ok(reuse_gateway_connection(
281                *authenticated_ephemeral_client,
282                *gateway_details,
283                managed_keys,
284            ))
285        }
286    }
287}
288
289pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) {
290    match std::fs::File::create(output_file) {
291        Ok(file) => match serde_json::to_writer_pretty(file, init_results) {
292            Ok(_) => println!("Saved: {output_file}"),
293            Err(err) => eprintln!("Could not save {output_file}: {err}"),
294        },
295        Err(err) => eprintln!("Could not save {output_file}: {err}"),
296    }
297}