diff --git a/rust/examples/mobile-client.rs b/rust/examples/mobile-client.rs index 1fb4d0e11..7ed81c295 100644 --- a/rust/examples/mobile-client.rs +++ b/rust/examples/mobile-client.rs @@ -119,9 +119,9 @@ fn main() -> Result<(), ()> { // perform the participant task (this function should be triggered regularly on the phone while the // app is active or in a background task) fn perform_task(url: &str, bytes: &[u8], model: Model) -> Vec { - let mut client = MobileClient::deserialize(url, bytes).unwrap(); + let mut client = MobileClient::restore(url, bytes).unwrap(); client.set_local_model(model); - client = match client.perform_task() { + client = match client.try_to_proceed() { Ok(client) => client, Err((client, _)) => client, }; diff --git a/rust/xaynet-client/src/mobile_client/client.rs b/rust/xaynet-client/src/mobile_client/client.rs index e5c0d7c82..b718c53af 100644 --- a/rust/xaynet-client/src/mobile_client/client.rs +++ b/rust/xaynet-client/src/mobile_client/client.rs @@ -252,16 +252,6 @@ impl ClientState { } } -pub async fn get_global_model(api: &mut T) -> Option { - if let Ok(model) = api.get_model().await { - debug!("fetched global model"); - model - } else { - debug!("global model not ready yet"); - None - } -} - #[derive(From, Serialize, Deserialize)] pub enum ClientStateMachine { Awaiting(ClientState), diff --git a/rust/xaynet-client/src/mobile_client/mod.rs b/rust/xaynet-client/src/mobile_client/mod.rs index cc1175a49..ee4b04dfb 100644 --- a/rust/xaynet-client/src/mobile_client/mod.rs +++ b/rust/xaynet-client/src/mobile_client/mod.rs @@ -2,9 +2,9 @@ pub mod client; pub mod participant; use crate::{ - api::HttpApiClient, + api::{ApiClient, HttpApiClient, HttpApiClientError}, mobile_client::{ - client::{get_global_model, ClientStateMachine, LocalModel}, + client::{ClientStateMachine, LocalModel}, participant::ParticipantSettings, }, }; @@ -27,6 +27,9 @@ pub enum MobileClientError { #[error("failed to initialize runtime: {0}")] /// Failed to initialize runtime. Runtime(#[from] std::io::Error), + #[error("failed to request API: {0}")] + /// Failed to request API. + Api(#[from] HttpApiClientError), } pub struct MobileClient { @@ -36,6 +39,14 @@ pub struct MobileClient { } impl MobileClient { + /// Initializes a fresh client. This method only needs to called once. + /// + /// To serialize and restore a client, please use the [`MobileClient::serialize`] and + /// [`MobileClient::restore`] + /// + /// # Errors + /// + /// Fails if the crypto module cannot be initialized. pub fn init( url: &str, participant_settings: ParticipantSettings, @@ -50,7 +61,13 @@ impl MobileClient { Ok(Self::new(url, client_state)) } - pub fn deserialize(url: &str, bytes: &[u8]) -> Result { + /// Restores a client from its serialized state. + /// + /// # Errors + /// + /// Fails if the serialized state is corrupted and the client cannot be restored + /// or if the crypto module cannot be initialized. + pub fn restore(url: &str, bytes: &[u8]) -> Result { let client_state: ClientStateMachine = bincode::deserialize(bytes)?; Ok(Self::new(url, client_state)) } @@ -65,6 +82,13 @@ impl MobileClient { } } + /// Serializes the current state of the client. + /// + /// # Note + /// + /// The serialized state is **not encrypted** and contains sensitive data such as the + /// participant's private key. Therefore, the user of the [`MobileClient`] **must** ensure + /// that the serialized state is stored in a safe place. pub fn serialize(&self) -> Vec { // Safe to unwrap: // @@ -79,13 +103,27 @@ impl MobileClient { bincode::serialize(&self.client_state).unwrap() } + /// Fetches and returns the latest global model from the coordinator. + /// Returns `None` if no global model is available. + /// + /// # Errors + /// + /// Fails if the runtime cannot be initialized or if an API request has failed. pub fn get_global_model(&mut self) -> Result, MobileClientError> { - let global_model = - Self::runtime()?.block_on(async { get_global_model(&mut self.api).await }); - Ok(global_model) + Self::runtime()? + .block_on(async { self.api.get_model().await }) + .map_err(|err| err.into()) } - pub fn perform_task(self) -> Result { + /// Tries to proceed with the current client task. + /// This will consume the current state of the client and produces a new one. + /// + /// # Errors + /// + /// Fails if the runtime cannot be initialized. + /// In this case the state of the client remains unchanged and is returned + /// together with the error. + pub fn try_to_proceed(self) -> Result { let mut runtime = match Self::runtime() { Ok(runtime) => runtime, // We don't want to loose the current client because of a runtime error. @@ -109,10 +147,20 @@ impl MobileClient { }) } + /// Sets the local model. + /// + /// The local model is only sent if the client has been selected as an update client. + /// If the client is an update client and no local model is available, the client remains + /// in this state until a local model has been set or a new round has been started by the + /// coordinator. pub fn set_local_model(&mut self, model: Model) { self.local_model.set_local_model(model); } + /// Creates a new participant secret key. + /// + /// The secret key is part of the [`ParticipantSettings`] which are required for the first + /// initialization of the client. pub fn create_participant_secret_key() -> SecretSigningKey { let SigningKeyPair { secret, .. } = SigningKeyPair::generate(); secret diff --git a/rust/xaynet-client/src/mod.rs b/rust/xaynet-client/src/mod.rs deleted file mode 100644 index e69de29bb..000000000