From 82d4fbe4655337f5fd94085e06e91d5ec06e6de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 28 Jan 2025 09:29:30 +0100 Subject: [PATCH 1/9] Move account controller checks into connecting state --- nym-vpn-core/Cargo.lock | 3 + .../nym-vpn-account-controller/Cargo.toml | 1 + .../src/commander.rs | 10 +- .../src/commands/mod.rs | 127 +--- .../src/commands/register_device.rs | 50 +- .../src/commands/request_zknym/cached_data.rs | 9 +- .../src/commands/request_zknym/error.rs | 163 ------ .../src/commands/request_zknym/handler.rs | 21 +- .../src/commands/request_zknym/mod.rs | 3 - .../src/commands/request_zknym/request.rs | 36 +- .../src/commands/sync_account.rs | 33 +- .../src/commands/sync_device.rs | 22 +- .../src/controller.rs | 50 +- .../nym-vpn-account-controller/src/lib.rs | 7 +- .../src/shared_state.rs | 95 +-- .../nym-vpn-account-controller/src/util.rs | 12 + .../crates/nym-vpn-api-client/src/error.rs | 8 + .../crates/nym-vpn-api-client/src/response.rs | 2 +- .../crates/nym-vpn-lib-types/Cargo.toml | 13 +- .../src/account/forget_account.rs | 31 + .../nym-vpn-lib-types/src/account/mod.rs | 88 +++ .../src/account/register_device.rs | 44 ++ .../src/account/request_zknym.rs | 230 ++++++++ .../src/account/store_account.rs | 44 ++ .../src/account/sync_account.rs | 42 ++ .../src/account/sync_device.rs | 42 ++ .../crates/nym-vpn-lib-types/src/lib.rs | 8 + .../nym-vpn-lib-types/src/tunnel_state.rs | 69 ++- .../nym-vpn-lib/src/platform/account.rs | 2 +- .../crates/nym-vpn-lib/src/platform/error.rs | 200 ++----- .../crates/nym-vpn-lib/src/platform/mod.rs | 19 +- .../nym-vpn-lib/src/platform/state_machine.rs | 13 +- .../src/platform/uniffi_custom_impls.rs | 11 +- .../src/platform/uniffi_lib_types.rs | 249 +++++++- .../src/tunnel_state_machine/account.rs | 63 ++ .../src/tunnel_state_machine/mod.rs | 23 +- .../states/connecting_state.rs | 13 + .../tunnel_state_machine/tunnel_monitor.rs | 49 +- .../src/conversions/from_proto/account.rs | 168 ++++++ .../src/conversions/from_proto/mod.rs | 1 + .../conversions/from_proto/tunnel_state.rs | 99 +++- .../src/conversions/into_proto/account.rs | 540 +----------------- .../into_proto/account_shared_state.rs | 178 ++++++ .../src/conversions/into_proto/mod.rs | 2 + .../conversions/into_proto/tunnel_state.rs | 247 +++++++- .../conversions/into_proto/vpn_api_client.rs | 65 +++ nym-vpn-core/crates/nym-vpnc/src/cli.rs | 3 - nym-vpn-core/crates/nym-vpnc/src/main.rs | 15 - .../command_interface/connection_handler.rs | 9 +- .../src/command_interface/listener.rs | 49 +- .../src/command_interface/protobuf/error.rs | 92 +-- .../protobuf/info_response.rs | 4 +- .../crates/nym-vpnd/src/service/error.rs | 100 +--- .../crates/nym-vpnd/src/service/mod.rs | 3 +- .../nym-vpnd/src/service/vpn_service.rs | 55 +- proto/nym/account.proto | 370 ++++++++++++ proto/nym/vpn.proto | 521 +---------------- 57 files changed, 2350 insertions(+), 2076 deletions(-) delete mode 100644 nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/error.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib-types/src/account/forget_account.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib-types/src/account/store_account.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs create mode 100644 nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs create mode 100644 nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs create mode 100644 nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs create mode 100644 nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/vpn_api_client.rs create mode 100644 proto/nym/account.proto diff --git a/nym-vpn-core/Cargo.lock b/nym-vpn-core/Cargo.lock index 498f592c66..5ef7266559 100644 --- a/nym-vpn-core/Cargo.lock +++ b/nym-vpn-core/Cargo.lock @@ -4871,6 +4871,7 @@ dependencies = [ "nym-sdk", "nym-validator-client", "nym-vpn-api-client", + "nym-vpn-lib-types", "nym-vpn-network-config", "nym-vpn-store", "nym-wg-gateway-client", @@ -5008,8 +5009,10 @@ dependencies = [ "nym-connection-monitor", "nym-gateway-directory", "nym-statistics-common", + "nym-vpn-api-client", "nym-wg-gateway-client", "si-scale", + "thiserror 2.0.11", "time", ] diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml b/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml index 69647377c5..e31d34f74b 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml +++ b/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml @@ -23,6 +23,7 @@ nym-http-api-client.workspace = true nym-sdk.workspace = true nym-validator-client.workspace = true nym-vpn-api-client = { path = "../nym-vpn-api-client" } +nym-vpn-lib-types = { path = "../nym-vpn-lib-types" } nym-vpn-network-config = { path = "../nym-vpn-network-config" } nym-vpn-store = { path = "../nym-vpn-store" } nym-wg-gateway-client = { path = "../nym-wg-gateway-client" } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs index c28c6df9a2..db21ff0115 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs @@ -2,13 +2,12 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_vpn_api_client::response::{NymVpnAccountSummaryResponse, NymVpnDevice, NymVpnUsage}; +use nym_vpn_lib_types::AccountCommandError; use nym_vpn_store::mnemonic::Mnemonic; use tokio::sync::mpsc::UnboundedSender; use crate::{ - commands::{ - request_zknym::RequestZkNymSummary, AccountCommand, AccountCommandError, ReturnSender, - }, + commands::{request_zknym::RequestZkNymSummary, AccountCommand, ReturnSender}, error::Error, shared_state::{AccountRegistered, DeviceState, SharedAccountState}, AvailableTicketbooks, @@ -136,6 +135,7 @@ impl AccountControllerCommander { pub async fn ensure_update_account( &self, ) -> Result, AccountCommandError> { + tracing::debug!("Ensuring account is synced"); let state = self.shared_state.lock().await.clone(); match state.account_registered { Some(AccountRegistered::Registered) => return Ok(None), @@ -145,6 +145,7 @@ impl AccountControllerCommander { } pub async fn ensure_update_device(&self) -> Result { + tracing::debug!("Ensuring device is synced"); let state = self.shared_state.lock().await.clone(); match state.device { Some(DeviceState::Active) => return Ok(DeviceState::Active), @@ -157,6 +158,7 @@ impl AccountControllerCommander { } pub async fn ensure_register_device(&self) -> Result<(), AccountCommandError> { + tracing::debug!("Ensuring device is registered"); let state = self.shared_state.lock().await.clone(); match state.device { Some(DeviceState::Active) => return Ok(()), @@ -169,6 +171,7 @@ impl AccountControllerCommander { } pub async fn ensure_available_zk_nyms(&self) -> Result<(), AccountCommandError> { + tracing::debug!("Ensuring available zk-nyms in the local credential store"); if self .get_available_tickets() .await? @@ -194,6 +197,7 @@ impl AccountControllerCommander { &self, credential_mode: bool, ) -> Result<(), AccountCommandError> { + tracing::debug!("Waiting for account to be ready to connect"); self.ensure_update_account().await?; self.ensure_update_device().await?; self.ensure_register_device().await?; diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs index a92efed965..7511b52f0e 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs @@ -6,15 +6,13 @@ pub(crate) mod request_zknym; pub(crate) mod sync_account; pub(crate) mod sync_device; +use nym_vpn_lib_types::AccountCommandError; use nym_vpn_store::mnemonic::Mnemonic; -pub use register_device::RegisterDeviceError; use request_zknym::RequestZkNymSummary; -pub use request_zknym::{RequestZkNymError, RequestZkNymSuccess}; use std::{collections::HashMap, sync::Arc}; use nym_vpn_api_client::response::{NymVpnAccountSummaryResponse, NymVpnDevice, NymVpnUsage}; -use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; use crate::{shared_state::DeviceState, AvailableTicketbooks}; @@ -52,129 +50,6 @@ impl RunningCommands { } } -#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] -pub enum AccountCommandError { - #[error("failed to sync account state: {0}")] - SyncAccountEndpointFailure(VpnApiEndpointFailure), - - #[error("failed to get account state: {0}")] - GetAccountEndpointFailure(VpnApiEndpointFailure), - - #[error("failed to sync device state: {0}")] - SyncDeviceEndpointFailure(VpnApiEndpointFailure), - - #[error("failed to register device: {0}")] - RegisterDeviceEndpointFailure(VpnApiEndpointFailure), - - #[error("failed to request zk nym")] - RequestZkNym { - successes: Vec, - failed: Vec, - }, - - #[error("failed to request zk nym")] - RequestZkNymGeneral(RequestZkNymError), - - #[error("no account stored")] - NoAccountStored, - - #[error("no device stored")] - NoDeviceStored, - - #[error("device registration is in progress")] - RegistrationInProgress, - - #[error("failed to remove account: {0}")] - RemoveAccount(String), - - #[error("failed to remove device from nym vpn api: {0}")] - UnregisterDeviceApiClientFailure(String), - - #[error("failed to remove device identity: {0}")] - RemoveDeviceIdentity(String), - - #[error("failed to reset credential storage: {0}")] - ResetCredentialStorage(String), - - #[error("failed to remove account files: {0}")] - RemoveAccountFiles(String), - - #[error("failed to init device keys: {0}")] - InitDeviceKeys(String), - - // Catch all for any other error - #[error("general error: {0}")] - General(String), - - // Internal error that should not happen - #[error("internal error: {0}")] - Internal(String), -} - -impl From for AccountCommandError { - fn from(err: RegisterDeviceError) -> Self { - match err { - RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { - AccountCommandError::RegisterDeviceEndpointFailure(failure) - } - RegisterDeviceError::General(message) => AccountCommandError::General(message), - } - } -} - -impl From for AccountCommandError { - fn from(err: RequestZkNymError) -> Self { - AccountCommandError::RequestZkNymGeneral(err) - } -} - -impl From for AccountCommandError { - fn from(summary: RequestZkNymSummary) -> Self { - let (successes, failed): (Vec<_>, Vec<_>) = summary.into_iter().partition(Result::is_ok); - let successes = successes.into_iter().map(Result::unwrap).collect(); - let failed = failed.into_iter().map(Result::unwrap_err).collect(); - Self::RequestZkNym { successes, failed } - } -} - -impl AccountCommandError { - pub fn internal(message: impl ToString) -> Self { - AccountCommandError::Internal(message.to_string()) - } - - pub fn general(message: impl ToString) -> Self { - AccountCommandError::General(message.to_string()) - } -} - -#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[error("nym-vpn-api error: message={message}, message_id={message_id:?}, code_reference_id={code_reference_id:?}")] -pub struct VpnApiEndpointFailure { - pub message: String, - pub message_id: Option, - pub code_reference_id: Option, -} - -impl From for VpnApiEndpointFailure { - fn from(response: nym_vpn_api_client::response::NymErrorResponse) -> Self { - Self { - message: response.message, - message_id: response.message_id, - code_reference_id: response.code_reference_id, - } - } -} - -impl TryFrom for VpnApiEndpointFailure { - type Error = nym_vpn_api_client::VpnApiClientError; - - fn try_from(response: nym_vpn_api_client::VpnApiClientError) -> Result { - nym_vpn_api_client::response::extract_error_response(&response) - .map(VpnApiEndpointFailure::from) - .ok_or(response) - } -} - #[derive(Debug)] pub struct ReturnSender { sender: oneshot::Sender>, diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs index c7e59e6390..94b09ecde4 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs @@ -6,14 +6,14 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; -use serde::{Deserialize, Serialize}; +use nym_vpn_lib_types::RegisterDeviceError; use crate::{ shared_state::{DeviceState, RegisterDeviceResult}, SharedAccountState, }; -use super::{AccountCommandError, AccountCommandResult, VpnApiEndpointFailure}; +use super::{AccountCommandError, AccountCommandResult}; pub(crate) struct RegisterDeviceCommandHandler { id: uuid::Uuid, @@ -86,43 +86,12 @@ impl RegisterDeviceCommandHandler { self.account_state .set_device_registration(RegisterDeviceResult::Failed(err.clone())) .await; - Err(AccountCommandError::from(err)) + Err(AccountCommandError::RegisterDevice(err)) } } } } -#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum RegisterDeviceError { - #[error("failed to register device: {0}")] - RegisterDeviceEndpointFailure(VpnApiEndpointFailure), - - #[error("failed to register device: {0}")] - General(String), -} - -impl RegisterDeviceError { - pub(crate) fn general(message: impl ToString) -> Self { - RegisterDeviceError::General(message.to_string()) - } - - pub fn message(&self) -> String { - match self { - RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => failure.message.clone(), - RegisterDeviceError::General(message) => message.clone(), - } - } - - pub fn message_id(&self) -> Option { - match self { - RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { - failure.message_id.clone() - } - RegisterDeviceError::General(_) => None, - } - } -} - pub(crate) async fn register_device( account: &VpnApiAccount, device: &Device, @@ -133,16 +102,9 @@ pub(crate) async fn register_device( .register_device(account, device) .await .map_err(|err| { - nym_vpn_api_client::response::extract_error_response(&err) - .map(|e| { - tracing::warn!(message = %e.message, message_id=?e.message_id, code_reference_id=?e.code_reference_id, "nym-vpn-api reports"); - RegisterDeviceError::RegisterDeviceEndpointFailure(VpnApiEndpointFailure { - message_id: e.message_id.clone(), - message: e.message.clone(), - code_reference_id: e.code_reference_id.clone(), - }) - }) - .unwrap_or_else(|| RegisterDeviceError::general(err)) + crate::util::into_endpoint_failure(err) + .map(RegisterDeviceError::RegisterDeviceEndpointFailure) + .unwrap_or_else(RegisterDeviceError::unexpected_response) })?; tracing::info!("Response: {:#?}", response); diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs index 3ad760ca02..f36b7945ff 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs @@ -5,10 +5,7 @@ use std::{collections::HashMap, sync::Arc}; use nym_credential_proxy_requests::api::v1::ticketbook::models::PartialVerificationKeysResponse; use nym_vpn_api_client::VpnApiClient; - -use crate::VpnApiEndpointFailure; - -use super::RequestZkNymError; +use nym_vpn_lib_types::RequestZkNymError; // Generic struct to store cached data during the request process, both between concurrent requests // for different types, and between requests for the same type. @@ -47,10 +44,10 @@ impl CachedData { .get_directory_zk_nyms_ticketbook_partial_verification_keys() .await .map_err(|err| { - VpnApiEndpointFailure::try_from(err) + crate::util::into_endpoint_failure(err) .map(|source| { RequestZkNymError::GetPartialVerificationKeysEndpointFailure { - source, + response: source, epoch_id, } }) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/error.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/error.rs deleted file mode 100644 index 6c522f54b2..0000000000 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/error.rs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_vpn_api_client::response::NymVpnZkNymStatus; -use serde::{Deserialize, Serialize}; - -use crate::VpnApiEndpointFailure; - -use super::ZkNymId; - -#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq, Serialize, Deserialize)] -pub enum RequestZkNymError { - #[error("failed to get zk-nyms available for download: {source}")] - GetZkNymsAvailableForDownloadEndpointFailure { source: VpnApiEndpointFailure }, - - #[error("failed to create ecash keypair: {0}")] - CreateEcashKeyPair(String), - - #[error("failed to construct withdrawal request: {0}")] - ConstructWithdrawalRequest(String), - - #[error("failed to request zk-nym endpoint for {ticket_type}: {source}")] - RequestZkNymEndpointFailure { - ticket_type: String, - source: VpnApiEndpointFailure, - }, - - #[error("response contains invalid ticketbook type: {0}")] - InvalidTicketTypeInResponse(String), - - #[error("ticket type mismatch")] - TicketTypeMismatch, - - #[error("error polling for zknym result: {source}")] - PollZkNymEndpointFailure { source: VpnApiEndpointFailure }, - - #[error("polling task failed")] - PollingTaskError, - - #[error("timeout polling for zknym {id}")] - PollingTimeout { id: ZkNymId }, - - #[error("polling for zknym {id} finished with error for ticket type: {ticket_type}")] - FinishedWithError { - id: ZkNymId, - ticket_type: String, - status: NymVpnZkNymStatus, - }, - - #[error("response is missing blinded shares")] - MissingBlindedShares, - - #[error("response contains invalid master verification key: {0}")] - ResponseHasInvalidMasterVerificationKey(String), - - #[error("epoch id mismatch")] - EpochIdMismatch, - - #[error("expiration date mismatch")] - ExpirationDateMismatch, - - #[error("failed to request partial verification keys for epoch {epoch_id}: {source}")] - GetPartialVerificationKeysEndpointFailure { - epoch_id: u64, - source: VpnApiEndpointFailure, - }, - - #[error("no master verification key in storage")] - NoMasterVerificationKeyInStorage, - - #[error("no coin index signatures in storage")] - NoCoinIndexSignaturesInStorage, - - #[error("no expiration date signatures in storage")] - NoExpirationDateSignaturesInStorage, - - #[error("invalid verification key: {0}")] - InvalidVerificationKey(String), - - #[error("failed to deserialize blinded signature: {0}")] - DeserializeBlindedSignature(String), - - #[error("decoded keys missing index")] - DecodedKeysMissingIndex, - - #[error("failed to import zknym")] - ImportZkNym { ticket_type: String, error: String }, - - #[error("failed to aggregate wallets: {0}")] - AggregateWallets(String), - - #[error("failed to confirm zknym {id} download: {source}")] - ConfirmZkNymDownloadEndpointFailure { - id: ZkNymId, - source: VpnApiEndpointFailure, - }, - - #[error("missing pending request: {0}")] - MissingPendingRequest(ZkNymId), - - #[error("failed to remove pending zk-nym request {id}: {error}")] - RemovePendingRequest { id: String, error: String }, - - #[error("credential storage error: {0}")] - CredentialStorage(String), - - #[error("internal error: {0}")] - Internal(String), - - #[error("unexpected error response: {0}")] - UnexpectedErrorResponse(String), -} - -impl RequestZkNymError { - pub fn internal(message: impl ToString) -> Self { - RequestZkNymError::Internal(message.to_string()) - } - - pub fn unexpected_response(message: impl ToString) -> Self { - RequestZkNymError::UnexpectedErrorResponse(message.to_string()) - } - - pub fn message(&self) -> String { - match self { - RequestZkNymError::RequestZkNymEndpointFailure { - source, - ticket_type: _, - } - | RequestZkNymError::PollZkNymEndpointFailure { source } => source.message.clone(), - other => other.to_string(), - } - } - - pub fn message_id(&self) -> Option { - match self { - RequestZkNymError::RequestZkNymEndpointFailure { - source, - ticket_type: _, - } - | RequestZkNymError::PollZkNymEndpointFailure { source } => source.message_id.clone(), - _ => None, - } - } - - pub fn ticket_type(&self) -> Option { - match self { - RequestZkNymError::RequestZkNymEndpointFailure { - source: _, - ticket_type, - } => Some(ticket_type.clone()), - RequestZkNymError::FinishedWithError { - id: _, - ticket_type, - status: _, - } - | RequestZkNymError::ImportZkNym { - ticket_type, - error: _, - } => Some(ticket_type.clone()), - _ => None, - } - } -} diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs index e297f5025d..98d71a1c72 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs @@ -14,18 +14,15 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; +use nym_vpn_lib_types::{AccountCommandError, RequestZkNymError, RequestZkNymSuccess}; use tokio::task::JoinSet; use crate::{ - commands::{AccountCommandResult, VpnApiEndpointFailure}, - shared_state::RequestZkNymResult, - storage::VpnCredentialStorage, - AccountCommandError, SharedAccountState, + commands::AccountCommandResult, shared_state::RequestZkNymResult, + storage::VpnCredentialStorage, SharedAccountState, }; -use super::{ - cached_data::CachedData, request::RequestZkNymTask, RequestZkNymError, RequestZkNymSuccess, -}; +use super::{cached_data::CachedData, request::RequestZkNymTask}; // The maximum number of zk-nym requests that can fail in a row before we disable background // refresh @@ -249,12 +246,10 @@ impl RequestZkNymCommandHandler { .await .map(|response| response.items.into_iter().map(|item| item.id).collect()) .map_err(|err| { - VpnApiEndpointFailure::try_from(err) - .map( - |source| RequestZkNymError::GetZkNymsAvailableForDownloadEndpointFailure { - source, - }, - ) + crate::util::into_endpoint_failure(err) + .map(|response| { + RequestZkNymError::GetZkNymsAvailableForDownloadEndpointFailure { response } + }) .unwrap_or_else(RequestZkNymError::unexpected_response) }) } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/mod.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/mod.rs index bb3a271f16..220aca8ceb 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/mod.rs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only mod cached_data; -mod error; mod handler; mod request; -pub use error::RequestZkNymError; pub use handler::RequestZkNymSummary; -pub use request::RequestZkNymSuccess; pub(crate) use handler::{WaitingRequestZkNymCommandHandler, ZkNymId}; diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs index 4ca5200d7a..6063049440 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs @@ -20,30 +20,16 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; -use serde::{Deserialize, Serialize}; +use nym_vpn_lib_types::{RequestZkNymError, RequestZkNymSuccess}; use time::Date; -use crate::{ - commands::VpnApiEndpointFailure, - storage::{PendingCredentialRequest, VpnCredentialStorage}, -}; +use crate::storage::{PendingCredentialRequest, VpnCredentialStorage}; -use super::{cached_data::CachedData, RequestZkNymError, ZkNymId}; +use super::{cached_data::CachedData, ZkNymId}; const ZK_NYM_POLLING_TIMEOUT: Duration = Duration::from_secs(60); const ZK_NYM_POLLING_INTERVAL: Duration = Duration::from_secs(5); -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct RequestZkNymSuccess { - pub id: ZkNymId, -} - -impl RequestZkNymSuccess { - pub fn new(id: ZkNymId) -> Self { - RequestZkNymSuccess { id } - } -} - pub(super) struct RequestZkNymTask { account: VpnApiAccount, device: Device, @@ -172,9 +158,9 @@ impl RequestZkNymTask { ) .await .map_err(|err| { - VpnApiEndpointFailure::try_from(err) - .map(|source| RequestZkNymError::RequestZkNymEndpointFailure { - source, + crate::util::into_endpoint_failure(err) + .map(|response| RequestZkNymError::RequestZkNymEndpointFailure { + response, ticket_type: request.ticketbook_type.to_string(), }) .unwrap_or_else(RequestZkNymError::unexpected_response) @@ -240,8 +226,8 @@ impl RequestZkNymTask { } } Err(error) => { - return Err(VpnApiEndpointFailure::try_from(error) - .map(|source| RequestZkNymError::PollZkNymEndpointFailure { source }) + return Err(crate::util::into_endpoint_failure(error) + .map(|response| RequestZkNymError::PollZkNymEndpointFailure { response }) .unwrap_or_else(RequestZkNymError::unexpected_response)); } } @@ -578,10 +564,10 @@ impl RequestZkNymTask { .confirm_zk_nym_download_by_id(&self.account, &self.device, id) .await .map_err(|err| { - VpnApiEndpointFailure::try_from(err) + crate::util::into_endpoint_failure(err) .map( - |source| RequestZkNymError::ConfirmZkNymDownloadEndpointFailure { - source, + |response| RequestZkNymError::ConfirmZkNymDownloadEndpointFailure { + response, id: id.to_string(), }, ) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs index e2810f44c4..79621a31b7 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs @@ -4,12 +4,10 @@ use std::sync::Arc; use nym_vpn_api_client::{response::NymVpnAccountSummaryResponse, types::VpnApiAccount}; +use nym_vpn_lib_types::SyncAccountError; use tracing::Level; -use crate::{ - commands::VpnApiEndpointFailure, - shared_state::{AccountRegistered, AccountSummary, SharedAccountState}, -}; +use crate::shared_state::{AccountRegistered, AccountSummary, SharedAccountState}; use super::{AccountCommandError, AccountCommandResult}; @@ -83,7 +81,9 @@ impl SyncStateCommandHandler { &self.vpn_api_client, &self.previous_account_summary_response, ) - .await; + .await + .map_err(AccountCommandError::SyncAccount); + tracing::debug!("Current state: {:?}", self.account_state.lock().await); update_result } @@ -94,28 +94,19 @@ async fn update_state( account_state: &SharedAccountState, vpn_api_client: &nym_vpn_api_client::VpnApiClient, previous_account_summary_response: &PreviousAccountSummaryResponse, -) -> Result { +) -> Result { tracing::debug!("Updating account state"); let response = vpn_api_client.get_account_summary(account).await; let account_summary = match response { Ok(account_summary) => account_summary, Err(err) => { - if let Some(e) = nym_vpn_api_client::response::extract_error_response(&err) { - tracing::warn!(message = %e.message, message_id=?e.message_id, code_reference_id=?e.code_reference_id, "nym-vpn-api reports"); - // TODO: check the message_id to confirm it's an error saying we are not registered - account_state - .set_account_registered(AccountRegistered::NotRegistered) - .await; - return Err(AccountCommandError::SyncAccountEndpointFailure( - VpnApiEndpointFailure { - message: e.message.clone(), - message_id: e.message_id.clone(), - code_reference_id: e.code_reference_id.clone(), - }, - )); - } - return Err(AccountCommandError::General(err.to_string())); + account_state + .set_account_registered(AccountRegistered::NotRegistered) + .await; + return Err(crate::util::into_endpoint_failure(err) + .map(SyncAccountError::SyncAccountEndpointFailure) + .unwrap_or_else(SyncAccountError::unexpected_response)); } }; diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs index 2c3bb0b519..ac73f2dc9f 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs @@ -7,12 +7,10 @@ use nym_vpn_api_client::{ response::NymVpnDevicesResponse, types::{Device, VpnApiAccount}, }; +use nym_vpn_lib_types::SyncDeviceError; use tracing::Level; -use crate::{ - commands::VpnApiEndpointFailure, - shared_state::{DeviceState, SharedAccountState}, -}; +use crate::shared_state::{DeviceState, SharedAccountState}; use super::{AccountCommandError, AccountCommandResult}; @@ -92,6 +90,7 @@ impl SyncDeviceStateCommandHandler { &self.previous_devices_response, ) .await + .map_err(AccountCommandError::SyncDevice) } } @@ -101,20 +100,13 @@ async fn update_state( account_state: &SharedAccountState, vpn_api_client: &nym_vpn_api_client::VpnApiClient, previous_devices_response: &PreviousDevicesResponse, -) -> Result { +) -> Result { tracing::debug!("Updating device state"); let devices = vpn_api_client.get_devices(account).await.map_err(|err| { - nym_vpn_api_client::response::extract_error_response(&err) - .map(|e| { - tracing::warn!(message = %e.message, message_id=?e.message_id, code_reference_id=?e.code_reference_id, "nym-vpn-api reports"); - AccountCommandError::SyncDeviceEndpointFailure(VpnApiEndpointFailure { - message: e.message.clone(), - message_id: e.message_id.clone(), - code_reference_id: e.code_reference_id.clone(), - }) - }) - .unwrap_or(AccountCommandError::General(err.to_string())) + crate::util::into_endpoint_failure(err) + .map(SyncDeviceError::SyncDeviceEndpointFailure) + .unwrap_or_else(SyncDeviceError::unexpected_response) })?; if previous_devices_response diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs index 028b7a8d7b..a604f65f2b 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs @@ -8,6 +8,7 @@ use nym_vpn_api_client::{ response::{NymVpnAccountResponse, NymVpnDevice, NymVpnUsage}, types::{DeviceStatus, VpnApiAccount}, }; +use nym_vpn_lib_types::{AccountCommandError, ForgetAccountError, StoreAccountError}; use nym_vpn_network_config::Network; use nym_vpn_store::{mnemonic::Mnemonic, VpnStorage}; use tokio::{ @@ -21,13 +22,13 @@ use crate::{ register_device::RegisterDeviceCommandHandler, request_zknym::WaitingRequestZkNymCommandHandler, sync_account::WaitingSyncAccountCommandHandler, - sync_device::WaitingSyncDeviceCommandHandler, AccountCommand, AccountCommandError, - AccountCommandResult, Command, RunningCommands, + sync_device::WaitingSyncDeviceCommandHandler, AccountCommand, AccountCommandResult, + Command, RunningCommands, }, error::Error, shared_state::{MnemonicState, ReadyToRegisterDevice, ReadyToRequestZkNym, SharedAccountState}, storage::{AccountStorage, VpnCredentialStorage}, - AccountControllerCommander, AvailableTicketbooks, VpnApiEndpointFailure, + AccountControllerCommander, AvailableTicketbooks, }; // The interval at which we automatically request zk-nyms @@ -272,13 +273,10 @@ where .get_account(&account) .await .map_err(|e| { - AccountCommandError::GetAccountEndpointFailure( - VpnApiEndpointFailure::try_from(e).unwrap_or_else(|e| VpnApiEndpointFailure { - message: e.to_string(), - message_id: None, - code_reference_id: None, - }), - ) + crate::util::into_endpoint_failure(e) + .map(StoreAccountError::GetAccountEndpointFailure) + .unwrap_or_else(|e| StoreAccountError::UnexpectedResponse(e.to_string())) + .into() }) } @@ -288,11 +286,11 @@ where self.account_storage .store_account(mnemonic) .await - .map_err(AccountCommandError::general)?; + .map_err(|err| StoreAccountError::Storage(err.to_string()))?; self.update_mnemonic_state() .await - .map_err(|_err| AccountCommandError::NoAccountStored)?; + .map_err(AccountCommandError::internal)?; // We don't need to wait for the sync to finish, so queue it up and return self.queue_command(AccountCommand::SyncAccountState(None)); @@ -322,7 +320,7 @@ where .await .map_err(|source| { tracing::error!("Failed to remove account: {source:?}"); - AccountCommandError::RemoveAccount(source.to_string()) + ForgetAccountError::RemoveAccount(source.to_string()) })?; self.account_storage @@ -330,7 +328,7 @@ where .await .map_err(|source| { tracing::error!("Failed to remove device identity: {source:?}"); - AccountCommandError::RemoveAccount(source.to_string()) + ForgetAccountError::RemoveDeviceKeys(source.to_string()) })?; self.credential_storage @@ -340,7 +338,7 @@ where .await .map_err(|source| { tracing::error!("Failed to reset credential storage: {source:?}"); - AccountCommandError::ResetCredentialStorage(source.to_string()) + ForgetAccountError::ResetCredentialStorage(source.to_string()) })?; // Purge all files in the data directory that we are not explicitly deleting through it's @@ -369,15 +367,17 @@ where .await; if let Err(err) = remove_files_result { - return Err(AccountCommandError::RemoveAccountFiles(format!( + return Err(ForgetAccountError::RemoveAccountFiles(format!( "Failed to remove files for account: {err}" - ))); + )) + .into()); } if let Err(err) = reinit_keys_result { - return Err(AccountCommandError::InitDeviceKeys(format!( + return Err(ForgetAccountError::InitDeviceKeys(format!( "Failed to reinitialize device keys: {err}" - ))); + )) + .into()); } Ok(()) @@ -387,7 +387,7 @@ where tracing::info!("Unregistering device from API"); if self.shared_state().ready_to_register_device().await == ReadyToRegisterDevice::InProgress { - return Err(AccountCommandError::RegistrationInProgress); + return Err(ForgetAccountError::RegistrationInProgress.into()); } let device = self @@ -397,14 +397,20 @@ where .map_err(|_err| AccountCommandError::NoDeviceStored)?; let account = self - .update_mnemonic_state() + .account_storage + .load_account() .await .map_err(|_err| AccountCommandError::NoAccountStored)?; self.vpn_api_client .update_device(&account, &device, DeviceStatus::DeleteMe) .await - .map_err(|err| AccountCommandError::UnregisterDeviceApiClientFailure(err.to_string())) + .map_err(|err| { + crate::util::into_endpoint_failure(err) + .map(ForgetAccountError::UpdateDeviceErrorResponse) + .unwrap_or_else(|err| ForgetAccountError::UnexpectedResponse(err.to_string())) + .into() + }) } async fn handle_sync_account_state(&mut self, command: AccountCommand) { diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs index cb9c7bcf9a..6419772e10 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs @@ -18,11 +18,8 @@ mod storage; mod ticketbooks; pub use commander::AccountControllerCommander; -pub use commands::{ - AccountCommand, AccountCommandError, RegisterDeviceError, RequestZkNymError, - RequestZkNymSuccess, VpnApiEndpointFailure, -}; +pub use commands::AccountCommand; pub use controller::AccountController; pub use error::Error; -pub use shared_state::{AccountStateSummary, ReadyToConnect, SharedAccountState}; +pub use shared_state::{AccountStateSummary, SharedAccountState}; pub use ticketbooks::{AvailableTicketbook, AvailableTicketbooks}; diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/shared_state.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/shared_state.rs index d41c07821e..ca53491461 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/shared_state.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/shared_state.rs @@ -8,14 +8,10 @@ use nym_vpn_api_client::response::{ NymVpnAccountSummaryFairUsage, NymVpnAccountSummaryResponse, NymVpnAccountSummarySubscription, NymVpnDeviceStatus, NymVpnSubscriptionStatus, }; +use nym_vpn_lib_types::{RegisterDeviceError, RequestZkNymError, RequestZkNymSuccess}; use serde::Serialize; use tokio::sync::MutexGuard; -use crate::commands::{ - register_device::RegisterDeviceError, - request_zknym::{RequestZkNymError, RequestZkNymSuccess}, -}; - #[derive(Clone)] pub struct SharedAccountState { inner: Arc>, @@ -85,33 +81,6 @@ impl fmt::Display for ReadyToRequestZkNym { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub enum ReadyToConnect { - Ready, - NoMnemonicStored, - AccountNotSynced, - AccountNotRegistered, - AccountNotActive, - NoActiveSubscription, - DeviceNotRegistered, - DeviceNotActive, -} - -impl fmt::Display for ReadyToConnect { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ReadyToConnect::Ready => write!(f, "ready to connect"), - ReadyToConnect::NoMnemonicStored => write!(f, "no mnemonic stored"), - ReadyToConnect::AccountNotSynced => write!(f, "account not synced"), - ReadyToConnect::AccountNotRegistered => write!(f, "account not registered"), - ReadyToConnect::AccountNotActive => write!(f, "account not active"), - ReadyToConnect::NoActiveSubscription => write!(f, "no active subscription"), - ReadyToConnect::DeviceNotRegistered => write!(f, "device not registered"), - ReadyToConnect::DeviceNotActive => write!(f, "device not active"), - } - } -} - impl SharedAccountState { pub(crate) fn new(state: MnemonicState) -> Self { let mut summary = AccountStateSummary::default(); @@ -208,13 +177,9 @@ impl SharedAccountState { pub(crate) async fn ready_to_request_zk_nym(&self) -> ReadyToRequestZkNym { self.lock().await.ready_to_request_zk_nym() } - - pub async fn is_ready_to_connect(&self) -> ReadyToConnect { - self.lock().await.is_ready_to_connect() - } } -#[derive(Debug, Clone, Default, PartialEq, Serialize)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct AccountStateSummary { // The locally stored recovery phrase that is deeply tied to the account pub mnemonic: Option, @@ -241,7 +206,7 @@ pub enum AccountRegistered { Registered, } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub struct AccountSummary { pub account: AccountState, pub subscription: SubscriptionState, @@ -271,7 +236,7 @@ impl MnemonicState { } } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub enum AccountState { // The account is registered but not active Inactive, @@ -283,21 +248,21 @@ pub enum AccountState { DeleteMe, } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub struct DeviceSummary { pub active: u64, pub max: u64, pub remaining: u64, } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub struct FairUsage { pub used_gb: Option, pub limit_gb: Option, pub resets_on_utc: Option, } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub enum SubscriptionState { // There is no active subscription NotActive, @@ -312,7 +277,7 @@ pub enum SubscriptionState { Active, } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub enum DeviceState { // The device is not registered on the remote server NotRegistered, @@ -327,7 +292,7 @@ pub enum DeviceState { DeleteMe, } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub enum RegisterDeviceResult { // The device registration is in progress InProgress, @@ -339,7 +304,7 @@ pub enum RegisterDeviceResult { Failed(RegisterDeviceError), } -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq)] pub enum RequestZkNymResult { // The zk-nym request is in progress InProgress, @@ -476,46 +441,6 @@ impl AccountStateSummary { ReadyToRequestZkNym::Ready } - - // If we are ready right right now. - pub(crate) fn is_ready_to_connect(&self) -> ReadyToConnect { - match self.mnemonic { - Some(MnemonicState::NotStored) => return ReadyToConnect::NoMnemonicStored, - Some(MnemonicState::Stored { .. }) => {} - None => return ReadyToConnect::NoMnemonicStored, - } - - match self.account_registered { - Some(AccountRegistered::Registered) => {} - Some(AccountRegistered::NotRegistered) => return ReadyToConnect::AccountNotRegistered, - None => return ReadyToConnect::AccountNotSynced, - } - - if let Some(ref account_summary) = self.account_summary { - match account_summary.account { - AccountState::Inactive => return ReadyToConnect::AccountNotActive, - AccountState::DeleteMe => return ReadyToConnect::AccountNotActive, - AccountState::Active => {} - } - - match account_summary.subscription { - SubscriptionState::NotActive => return ReadyToConnect::NoActiveSubscription, - SubscriptionState::Pending => return ReadyToConnect::NoActiveSubscription, - SubscriptionState::Complete => return ReadyToConnect::NoActiveSubscription, - SubscriptionState::Active => {} - } - } - - match self.device { - Some(DeviceState::Active) => {} - Some(DeviceState::NotRegistered) => return ReadyToConnect::DeviceNotRegistered, - Some(DeviceState::Inactive) => return ReadyToConnect::DeviceNotActive, - Some(DeviceState::DeleteMe) => return ReadyToConnect::DeviceNotActive, - None => return ReadyToConnect::DeviceNotRegistered, - } - - ReadyToConnect::Ready - } } impl fmt::Display for AccountStateSummary { diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs index 1cda20cb7a..7b75704f54 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs @@ -85,3 +85,15 @@ pub fn remove_files_for_account(data_dir: &Path) -> Result<(), Error> { Ok(()) } + +pub fn into_endpoint_failure( + err: nym_vpn_api_client::VpnApiClientError, +) -> Result { + nym_vpn_api_client::response::NymErrorResponse::try_from(err).map(|res| { + nym_vpn_lib_types::VpnApiErrorResponse { + message: res.message, + message_id: res.message_id, + code_reference_id: res.code_reference_id, + } + }) +} diff --git a/nym-vpn-core/crates/nym-vpn-api-client/src/error.rs b/nym-vpn-core/crates/nym-vpn-api-client/src/error.rs index 46a4c0be6b..a36c373be1 100644 --- a/nym-vpn-core/crates/nym-vpn-api-client/src/error.rs +++ b/nym-vpn-core/crates/nym-vpn-api-client/src/error.rs @@ -108,3 +108,11 @@ pub enum VpnApiClientError { } pub type Result = std::result::Result; + +impl TryFrom for NymErrorResponse { + type Error = VpnApiClientError; + + fn try_from(response: VpnApiClientError) -> std::result::Result { + crate::response::extract_error_response(&response).ok_or(response) + } +} diff --git a/nym-vpn-core/crates/nym-vpn-api-client/src/response.rs b/nym-vpn-core/crates/nym-vpn-api-client/src/response.rs index ebb7f30290..955e29a30a 100644 --- a/nym-vpn-core/crates/nym-vpn-api-client/src/response.rs +++ b/nym-vpn-core/crates/nym-vpn-api-client/src/response.rs @@ -479,7 +479,7 @@ impl From for NymDirectoryCountry { } } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct NymErrorResponse { pub message: String, diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/Cargo.toml b/nym-vpn-core/crates/nym-vpn-lib-types/Cargo.toml index d17adf5697..7b07870ad8 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/Cargo.toml +++ b/nym-vpn-core/crates/nym-vpn-lib-types/Cargo.toml @@ -10,19 +10,22 @@ license.workspace = true [features] nym-type-conversions = [ - "dep:nym-statistics-common", "dep:nym-bandwidth-controller", + "dep:nym-connection-monitor", "dep:nym-gateway-directory", + "dep:nym-statistics-common", + "dep:nym-vpn-api-client", "dep:nym-wg-gateway-client", - "dep:nym-connection-monitor", ] [dependencies] -time.workspace = true si-scale.workspace = true +thiserror.workspace = true +time.workspace = true -nym-statistics-common = { workspace = true, optional = true } nym-bandwidth-controller = { workspace = true, optional = true } +nym-connection-monitor = { path = "../nym-connection-monitor", optional = true } nym-gateway-directory = { path = "../nym-gateway-directory", optional = true } +nym-statistics-common = { workspace = true, optional = true } +nym-vpn-api-client = { path = "../nym-vpn-api-client", optional = true } nym-wg-gateway-client = { path = "../nym-wg-gateway-client", optional = true } -nym-connection-monitor = { path = "../nym-connection-monitor", optional = true } diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/forget_account.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/forget_account.rs new file mode 100644 index 0000000000..b67cea7df4 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/forget_account.rs @@ -0,0 +1,31 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::VpnApiErrorResponse; + +#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] +pub enum ForgetAccountError { + #[error("registration is in progress")] + RegistrationInProgress, + + #[error("failed to remove device from nym vpn api: {0}")] + UpdateDeviceErrorResponse(VpnApiErrorResponse), + + #[error("unexpected response: {0}")] + UnexpectedResponse(String), + + #[error("failed to remove account: {0}")] + RemoveAccount(String), + + #[error("failed to remove device keys: {0}")] + RemoveDeviceKeys(String), + + #[error("failed to reset credential storage: {0}")] + ResetCredentialStorage(String), + + #[error("failed to remove account files: {0}")] + RemoveAccountFiles(String), + + #[error("failed to init device keys: {0}")] + InitDeviceKeys(String), +} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs new file mode 100644 index 0000000000..5fcaa44be8 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs @@ -0,0 +1,88 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod forget_account; +pub mod register_device; +pub mod request_zknym; +pub mod store_account; +pub mod sync_account; +pub mod sync_device; + +#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] +pub enum AccountCommandError { + // Catch all for any error not specifically handled + #[error("general error: {0}")] + General(String), + + // Internal error that should not happen + #[error("internal error: {0}")] + Internal(String), + + // Top level error that can happen for any command + #[error("no account stored")] + NoAccountStored, + + // Top level error that can happen for any command + #[error("no device stored")] + NoDeviceStored, + + // + // --- Error cases for specific commands --- + // + #[error("failed to store account: {0}")] + StoreAccount(#[from] store_account::StoreAccountError), + + #[error("failed to sync account state: {0}")] + SyncAccount(#[from] sync_account::SyncAccountError), + + #[error("failed to sync device state: {0}")] + SyncDevice(#[from] sync_device::SyncDeviceError), + + #[error("failed to register device: {0}")] + RegisterDevice(#[from] register_device::RegisterDeviceError), + + #[error("failed to request zk nym: {0}")] + RequestZkNym(#[from] request_zknym::RequestZkNymError), + + #[error("failed to request zk nym")] + RequestZkNymBundle { + successes: Vec, + failed: Vec, + }, + + #[error("failed to forget account: {0}")] + ForgetAccount(#[from] forget_account::ForgetAccountError), +} + +impl AccountCommandError { + pub fn internal(message: impl ToString) -> Self { + AccountCommandError::Internal(message.to_string()) + } + + pub fn general(message: impl ToString) -> Self { + AccountCommandError::General(message.to_string()) + } +} + +// Local alias for syntactic simplification +type RequestZkNymVec = + Vec>; + +impl From for AccountCommandError { + fn from(summary: RequestZkNymVec) -> Self { + let (successes, failed): (Vec<_>, Vec<_>) = summary.into_iter().partition(Result::is_ok); + let successes = successes.into_iter().map(Result::unwrap).collect(); + let failed = failed.into_iter().map(Result::unwrap_err).collect(); + Self::RequestZkNymBundle { successes, failed } + } +} + +#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] +#[error( + "nym-vpn-api: message: {message}, message_id: {message_id:?}, code_reference_id: {code_reference_id:?}" +)] +pub struct VpnApiErrorResponse { + pub message: String, + pub message_id: Option, + pub code_reference_id: Option, +} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs new file mode 100644 index 0000000000..5e1e330527 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs @@ -0,0 +1,44 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::VpnApiErrorResponse; + +#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] +pub enum RegisterDeviceError { + #[error("failed to register device: {0}")] + RegisterDeviceEndpointFailure(VpnApiErrorResponse), + + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl RegisterDeviceError { + pub fn unexpected_response(message: impl ToString) -> Self { + RegisterDeviceError::UnexpectedResponse(message.to_string()) + } + + pub fn message(&self) -> String { + match self { + RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => failure.message.clone(), + RegisterDeviceError::UnexpectedResponse(message) => message.clone(), + } + } + + pub fn message_id(&self) -> Option { + match self { + RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { + failure.message_id.clone() + } + RegisterDeviceError::UnexpectedResponse(_) => None, + } + } + + pub fn code_reference_id(&self) -> Option { + match self { + RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { + failure.code_reference_id.clone() + } + RegisterDeviceError::UnexpectedResponse(_) => None, + } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs new file mode 100644 index 0000000000..3ca472adb4 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs @@ -0,0 +1,230 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::VpnApiErrorResponse; + +#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] +pub enum RequestZkNymError { + #[error("failed to get zk-nyms available for download: {response}")] + GetZkNymsAvailableForDownloadEndpointFailure { response: VpnApiErrorResponse }, + + #[error("failed to create ecash keypair: {0}")] + CreateEcashKeyPair(String), + + #[error("failed to construct withdrawal request: {0}")] + ConstructWithdrawalRequest(String), + + #[error("failed to request zk-nym endpoint for {ticket_type}: {response}")] + RequestZkNymEndpointFailure { + ticket_type: String, + response: VpnApiErrorResponse, + }, + + #[error("response contains invalid ticketbook type: {0}")] + InvalidTicketTypeInResponse(String), + + #[error("ticket type mismatch")] + TicketTypeMismatch, + + #[error("error polling for zknym result: {response}")] + PollZkNymEndpointFailure { response: VpnApiErrorResponse }, + + #[error("polling task failed")] + PollingTaskError, + + #[error("timeout polling for zknym {id}")] + PollingTimeout { id: ZkNymId }, + + #[error("response is missing blinded shares")] + MissingBlindedShares, + + #[error("response contains invalid master verification key: {0}")] + ResponseHasInvalidMasterVerificationKey(String), + + #[error("epoch id mismatch")] + EpochIdMismatch, + + #[error("expiration date mismatch")] + ExpirationDateMismatch, + + #[error("failed to request partial verification keys for epoch {epoch_id}: {response}")] + GetPartialVerificationKeysEndpointFailure { + epoch_id: u64, + response: VpnApiErrorResponse, + }, + + #[error("no master verification key in storage")] + NoMasterVerificationKeyInStorage, + + #[error("no coin index signatures in storage")] + NoCoinIndexSignaturesInStorage, + + #[error("no expiration date signatures in storage")] + NoExpirationDateSignaturesInStorage, + + #[error("invalid verification key: {0}")] + InvalidVerificationKey(String), + + #[error("failed to deserialize blinded signature: {0}")] + DeserializeBlindedSignature(String), + + #[error("decoded keys missing index")] + DecodedKeysMissingIndex, + + #[error("failed to import zknym")] + ImportZkNym { ticket_type: String, error: String }, + + #[error("failed to aggregate wallets: {0}")] + AggregateWallets(String), + + #[error("failed to confirm zknym {id} download: {response}")] + ConfirmZkNymDownloadEndpointFailure { + id: ZkNymId, + response: VpnApiErrorResponse, + }, + + #[error("missing pending request: {0}")] + MissingPendingRequest(ZkNymId), + + #[error("failed to remove pending zk-nym request {id}: {error}")] + RemovePendingRequest { id: String, error: String }, + + #[error("credential storage error: {0}")] + CredentialStorage(String), + + #[error("internal error: {0}")] + Internal(String), + + #[error("nym-vpn-api: unexpected error response: {0}")] + UnexpectedErrorResponse(String), +} + +impl RequestZkNymError { + pub fn internal(message: impl ToString) -> Self { + RequestZkNymError::Internal(message.to_string()) + } + + pub fn unexpected_response(message: impl ToString) -> Self { + RequestZkNymError::UnexpectedErrorResponse(message.to_string()) + } + + pub fn vpn_api_error(&self) -> Option { + match self { + RequestZkNymError::GetZkNymsAvailableForDownloadEndpointFailure { response } + | RequestZkNymError::RequestZkNymEndpointFailure { + response, + ticket_type: _, + } + | RequestZkNymError::PollZkNymEndpointFailure { response } + | RequestZkNymError::GetPartialVerificationKeysEndpointFailure { + response, + epoch_id: _, + } + | RequestZkNymError::ConfirmZkNymDownloadEndpointFailure { response, id: _ } => { + Some(response.clone()) + } + _ => None, + } + } + + pub fn message(&self) -> Option { + self.vpn_api_error().map(|err| err.message.clone()) + } + + pub fn message_id(&self) -> Option { + self.vpn_api_error().and_then(|err| err.message_id.clone()) + } + + pub fn code_reference_id(&self) -> Option { + self.vpn_api_error() + .and_then(|err| err.code_reference_id.clone()) + } + + pub fn ticket_type(&self) -> Option { + match self { + RequestZkNymError::RequestZkNymEndpointFailure { + response: _, + ticket_type, + } => Some(ticket_type.clone()), + RequestZkNymError::ImportZkNym { + ticket_type, + error: _, + } => Some(ticket_type.clone()), + _ => None, + } + } +} + +// Simplified version of the error enum suitable for app API +#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] +pub enum RequestZkNymErrorReason { + #[error(transparent)] + VpnApi(VpnApiErrorResponse), + + #[error("nym-vpn-api: unexpected error response: {0}")] + UnexpectedVpnApiResponse(String), + + #[error("storage error: {0}")] + Storage(String), + + #[error("{0}")] + Internal(String), +} + +impl From for RequestZkNymErrorReason { + fn from(err: RequestZkNymError) -> Self { + match err { + RequestZkNymError::GetZkNymsAvailableForDownloadEndpointFailure { response } + | RequestZkNymError::RequestZkNymEndpointFailure { + response, + ticket_type: _, + } + | RequestZkNymError::PollZkNymEndpointFailure { response } + | RequestZkNymError::GetPartialVerificationKeysEndpointFailure { + response, + epoch_id: _, + } + | RequestZkNymError::ConfirmZkNymDownloadEndpointFailure { response, id: _ } => { + Self::VpnApi(response) + } + RequestZkNymError::UnexpectedErrorResponse(message) => { + Self::UnexpectedVpnApiResponse(message) + } + RequestZkNymError::CredentialStorage(message) => Self::Storage(message), + RequestZkNymError::CreateEcashKeyPair(_) + | RequestZkNymError::ConstructWithdrawalRequest(_) + | RequestZkNymError::InvalidTicketTypeInResponse(_) + | RequestZkNymError::TicketTypeMismatch + | RequestZkNymError::PollingTaskError + | RequestZkNymError::PollingTimeout { .. } + | RequestZkNymError::MissingBlindedShares + | RequestZkNymError::ResponseHasInvalidMasterVerificationKey(_) + | RequestZkNymError::EpochIdMismatch + | RequestZkNymError::ExpirationDateMismatch + | RequestZkNymError::NoMasterVerificationKeyInStorage + | RequestZkNymError::NoCoinIndexSignaturesInStorage + | RequestZkNymError::NoExpirationDateSignaturesInStorage + | RequestZkNymError::InvalidVerificationKey(_) + | RequestZkNymError::DeserializeBlindedSignature(_) + | RequestZkNymError::DecodedKeysMissingIndex + | RequestZkNymError::ImportZkNym { .. } + | RequestZkNymError::AggregateWallets(_) + | RequestZkNymError::MissingPendingRequest(_) + | RequestZkNymError::RemovePendingRequest { .. } + | RequestZkNymError::Internal(_) => Self::Internal(err.to_string()), + } + } +} + +pub type ZkNymId = String; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RequestZkNymSuccess { + pub id: ZkNymId, +} + +impl RequestZkNymSuccess { + pub fn new(id: ZkNymId) -> Self { + RequestZkNymSuccess { id } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/store_account.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/store_account.rs new file mode 100644 index 0000000000..cb6bfc50f7 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/store_account.rs @@ -0,0 +1,44 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::VpnApiErrorResponse; + +#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] +pub enum StoreAccountError { + #[error("storage: {0}")] + Storage(String), + + #[error("vpn api endpoint failure: {0}")] + GetAccountEndpointFailure(VpnApiErrorResponse), + + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl StoreAccountError { + pub fn message(&self) -> String { + match self { + StoreAccountError::Storage(message) => message.clone(), + StoreAccountError::GetAccountEndpointFailure(failure) => failure.message.clone(), + StoreAccountError::UnexpectedResponse(response) => response.clone(), + } + } + + pub fn message_id(&self) -> Option { + match self { + StoreAccountError::Storage(_) => None, + StoreAccountError::GetAccountEndpointFailure(failure) => failure.message_id.clone(), + StoreAccountError::UnexpectedResponse(_) => None, + } + } + + pub fn code_reference_id(&self) -> Option { + match self { + StoreAccountError::Storage(_) => None, + StoreAccountError::GetAccountEndpointFailure(failure) => { + failure.code_reference_id.clone() + } + StoreAccountError::UnexpectedResponse(_) => None, + } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs new file mode 100644 index 0000000000..e02e24bc40 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs @@ -0,0 +1,42 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::VpnApiErrorResponse; + +#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)] +pub enum SyncAccountError { + #[error("vpn api endpoint failure: {0}")] + SyncAccountEndpointFailure(VpnApiErrorResponse), + + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl SyncAccountError { + pub fn unexpected_response(err: impl ToString) -> Self { + SyncAccountError::UnexpectedResponse(err.to_string()) + } + + pub fn message(&self) -> String { + match self { + SyncAccountError::SyncAccountEndpointFailure(failure) => failure.message.clone(), + SyncAccountError::UnexpectedResponse(response) => response.to_string(), + } + } + + pub fn message_id(&self) -> Option { + match self { + SyncAccountError::SyncAccountEndpointFailure(failure) => failure.message_id.clone(), + SyncAccountError::UnexpectedResponse(_) => None, + } + } + + pub fn code_reference_id(&self) -> Option { + match self { + SyncAccountError::SyncAccountEndpointFailure(failure) => { + failure.code_reference_id.clone() + } + SyncAccountError::UnexpectedResponse(_) => None, + } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs new file mode 100644 index 0000000000..4f0bc0eed6 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs @@ -0,0 +1,42 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::VpnApiErrorResponse; + +#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)] +pub enum SyncDeviceError { + #[error("vpn api endpoint failure: {0}")] + SyncDeviceEndpointFailure(VpnApiErrorResponse), + + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl SyncDeviceError { + pub fn unexpected_response(err: impl ToString) -> Self { + SyncDeviceError::UnexpectedResponse(err.to_string()) + } + + pub fn message(&self) -> String { + match self { + SyncDeviceError::SyncDeviceEndpointFailure(failure) => failure.message.clone(), + SyncDeviceError::UnexpectedResponse(response) => response.to_string(), + } + } + + pub fn message_id(&self) -> Option { + match self { + SyncDeviceError::SyncDeviceEndpointFailure(failure) => failure.message_id.clone(), + SyncDeviceError::UnexpectedResponse(_) => None, + } + } + + pub fn code_reference_id(&self) -> Option { + match self { + SyncDeviceError::SyncDeviceEndpointFailure(failure) => { + failure.code_reference_id.clone() + } + SyncDeviceError::UnexpectedResponse(_) => None, + } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/lib.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/lib.rs index 4c16c57504..501a54f99a 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/lib.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/lib.rs @@ -3,10 +3,18 @@ //! Types shared between nym-vpn-lib and other crates in the workspace. +mod account; mod connection_data; mod tunnel_event; mod tunnel_state; +pub use account::{ + forget_account::ForgetAccountError, register_device::RegisterDeviceError, + request_zknym::RequestZkNymError, request_zknym::RequestZkNymErrorReason, + request_zknym::RequestZkNymSuccess, store_account::StoreAccountError, + sync_account::SyncAccountError, sync_device::SyncDeviceError, AccountCommandError, + VpnApiErrorResponse, +}; pub use connection_data::{ ConnectionData, Gateway, MixnetConnectionData, NymAddress, TunnelConnectionData, WireguardConnectionData, WireguardNode, diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs index cde78a234d..68e67f61d9 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs @@ -3,7 +3,16 @@ use std::fmt; -use super::connection_data::{ConnectionData, TunnelConnectionData}; +use crate::{account::AccountCommandError, RequestZkNymErrorReason}; + +use super::{ + account::{ + forget_account::ForgetAccountError, register_device::RegisterDeviceError, + request_zknym::RequestZkNymSuccess, store_account::StoreAccountError, + sync_account::SyncAccountError, sync_device::SyncDeviceError, + }, + connection_data::{ConnectionData, TunnelConnectionData}, +}; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum TunnelType { @@ -117,7 +126,7 @@ pub enum ActionAfterDisconnect { Error, } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum ErrorStateReason { /// Issues related to firewall configuration. Firewall, @@ -150,6 +159,62 @@ pub enum ErrorStateReason { /// Failure to duplicate tunnel file descriptor. DuplicateTunFd, + /// Account related error not specifically handled. + Account(String), + + /// Failure to perform the requested action due to no account stored. + NoAccountStored, + + /// Failure to perform the requested action due to no device stored. + NoDeviceStored, + + /// Failure to store account. + StoreAccount(StoreAccountError), + + /// Failure to sync account with the VPN API. + SyncAccount(SyncAccountError), + + /// Failure to sync device with the VPN API. + SyncDevice(SyncDeviceError), + + /// Failure to register device with the VPN API. + RegisterDevice(RegisterDeviceError), + + /// Failure to request a zknym from the VPN API. + RequestZkNym(RequestZkNymErrorReason), + + /// Zknym ticketbooks were requested, some succeeded and some failed. + RequestZkNymBundle { + successes: Vec, + failed: Vec, + }, + + /// Failure to forget account. + ForgetAccount(ForgetAccountError), + /// Program errors that must not happen. Internal, } + +impl From for ErrorStateReason { + fn from(value: AccountCommandError) -> Self { + match value { + AccountCommandError::General(err) => ErrorStateReason::Account(err), + AccountCommandError::Internal(err) => ErrorStateReason::Account(err), + AccountCommandError::NoAccountStored => ErrorStateReason::NoAccountStored, + AccountCommandError::NoDeviceStored => ErrorStateReason::NoDeviceStored, + AccountCommandError::StoreAccount(err) => ErrorStateReason::StoreAccount(err), + AccountCommandError::SyncAccount(err) => ErrorStateReason::SyncAccount(err), + AccountCommandError::SyncDevice(err) => ErrorStateReason::SyncDevice(err), + AccountCommandError::RegisterDevice(err) => ErrorStateReason::RegisterDevice(err), + AccountCommandError::RequestZkNym(err) => ErrorStateReason::RequestZkNym(err.into()), + AccountCommandError::RequestZkNymBundle { successes, failed } => { + ErrorStateReason::RequestZkNymBundle { + successes, + failed: failed.into_iter().map(Into::into).collect(), + } + } + AccountCommandError::ForgetAccount(err) => ErrorStateReason::ForgetAccount(err), + } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs index c66adc692f..e0dbd8d856 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs @@ -355,7 +355,7 @@ pub(crate) mod raw { vpn_api_client .update_device(&account, &device, DeviceStatus::DeleteMe) .await - .map_err(|err| VpnError::UnregisterDeviceApiClientFailure { + .map_err(|err| VpnError::UnregisterDevice { details: err.to_string(), })?; Ok(()) diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs index 5cb4b80479..a370e868d0 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs @@ -1,6 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_vpn_lib_types::AccountCommandError; + #[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] pub enum VpnError { #[error("{details}")] @@ -9,196 +11,74 @@ pub enum VpnError { #[error("{details}")] NetworkConnectionError { details: String }, - #[error("{details}")] - GatewayError { details: String }, - - #[error("{details}")] - InvalidCredential { details: String }, - - #[error("Client is out of bandwidth")] - OutOfBandwidth, - #[error("{details}")] InvalidStateError { details: String }, - #[error("account state is ready to connect")] - AccountReady, - #[error("no account stored")] NoAccountStored, - #[error("account not synced")] - AccountNotSynced, - - #[error("account not registered")] + #[error("attempting to access an account that is not registered")] AccountNotRegistered, - #[error("account not active")] - AccountNotActive, - - #[error("no active subscription")] - NoActiveSubscription, - - #[error("device not registered")] - AccountDeviceNotRegistered, - - #[error("device not active")] - AccountDeviceNotActive, - #[error("no device identity stored")] NoDeviceIdentity, #[error("timeout connecting to nym-vpn-api")] VpnApiTimeout, - #[error("account update failed: {details}")] - UpdateAccountEndpointFailure { - details: String, - message_id: Option, - code_reference_id: Option, - }, - - #[error("device update failed: {details}")] - UpdateDeviceEndpointFailure { - details: String, - message_id: Option, - code_reference_id: Option, - }, - - #[error("device registration failed: {details}")] - DeviceRegistrationFailed { - details: String, - message_id: Option, - code_reference_id: Option, - }, - - #[error("failed to request zk nym")] - RequestZkNym { - successes: Vec, - failed: Vec, - }, + #[error("failed to parse mnemonic with error: {details}")] + InvalidMnemonic { details: String }, #[error("invalid account storage path: {details}")] InvalidAccountStoragePath { details: String }, - #[error("invalid statistics recipient")] - StatisticsRecipient, - #[error("failed to remove device from nym vpn api: {details}")] - UnregisterDeviceApiClientFailure { details: String }, + UnregisterDevice { details: String }, - #[error("failed to parse mnemonic with error: {details}")] - InvalidMnemonic { details: String }, -} + #[error("failed to store account: {0}")] + StoreAccount(super::uniffi_lib_types::StoreAccountError), -#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)] -pub struct RequestZkNymSuccess { - pub id: String, -} + #[error("sync account failed: {0}")] + SyncAccount(super::uniffi_lib_types::SyncAccountError), -impl From for RequestZkNymSuccess { - fn from(value: nym_vpn_account_controller::RequestZkNymSuccess) -> Self { - Self { id: value.id } - } -} + #[error("sync device failed: {0}")] + SyncDevice(super::uniffi_lib_types::SyncDeviceError), -#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)] -pub struct RequestZkNymError { - pub message: String, - pub message_id: Option, - pub ticket_type: Option, -} + #[error("device registration failed: {0}")] + RegisterDevice(super::uniffi_lib_types::RegisterDeviceError), -impl From for RequestZkNymError { - fn from(error: nym_vpn_account_controller::RequestZkNymError) -> Self { - Self { - message: error.message(), - message_id: error.message_id(), - ticket_type: error.ticket_type(), - } - } -} + #[error("failed to request zk nym")] + RequestZkNym(super::uniffi_lib_types::RequestZkNymError), -impl From for VpnError { - fn from(value: nym_vpn_account_controller::ReadyToConnect) -> Self { - match value { - nym_vpn_account_controller::ReadyToConnect::Ready => Self::AccountReady, - nym_vpn_account_controller::ReadyToConnect::NoMnemonicStored => Self::NoAccountStored, - nym_vpn_account_controller::ReadyToConnect::AccountNotSynced => Self::AccountNotSynced, - nym_vpn_account_controller::ReadyToConnect::AccountNotRegistered => { - Self::AccountNotRegistered - } - nym_vpn_account_controller::ReadyToConnect::AccountNotActive => Self::AccountNotActive, - nym_vpn_account_controller::ReadyToConnect::NoActiveSubscription => { - Self::NoActiveSubscription - } - nym_vpn_account_controller::ReadyToConnect::DeviceNotRegistered => { - Self::AccountDeviceNotRegistered - } - nym_vpn_account_controller::ReadyToConnect::DeviceNotActive => { - Self::AccountDeviceNotActive - } - } - } + #[error("when requesting zk nym, some were reported as failed")] + RequestZkNymBundle { + successes: Vec, + failed: Vec, + }, + + #[error("failed to forget account: {0}")] + ForgetAccount(super::uniffi_lib_types::ForgetAccountError), } -impl From for VpnError { - fn from(value: nym_vpn_account_controller::AccountCommandError) -> Self { - use nym_vpn_account_controller::AccountCommandError; +impl From for VpnError { + fn from(value: AccountCommandError) -> Self { match value { - AccountCommandError::SyncAccountEndpointFailure(e) => { - VpnError::UpdateAccountEndpointFailure { - details: e.message, - message_id: e.message_id, - code_reference_id: e.code_reference_id, - } - } - AccountCommandError::SyncDeviceEndpointFailure(e) => { - VpnError::UpdateDeviceEndpointFailure { - details: e.message, - message_id: e.message_id, - code_reference_id: e.code_reference_id, - } - } - AccountCommandError::RegisterDeviceEndpointFailure(e) => { - VpnError::DeviceRegistrationFailed { - details: e.message, - message_id: e.message_id, - code_reference_id: e.code_reference_id, - } - } - AccountCommandError::RequestZkNym { successes, failed } => VpnError::RequestZkNym { - successes: successes.into_iter().map(|e| e.into()).collect(), - failed: failed.into_iter().map(|e| e.into()).collect(), - }, - AccountCommandError::RequestZkNymGeneral(e) => VpnError::RequestZkNym { - successes: vec![], - failed: vec![e.into()], - }, - AccountCommandError::NoAccountStored => VpnError::NoAccountStored, - AccountCommandError::NoDeviceStored => VpnError::NoDeviceIdentity, - AccountCommandError::RemoveAccount(e) => VpnError::InternalError { details: e }, - AccountCommandError::RemoveDeviceIdentity(e) => VpnError::InternalError { details: e }, - AccountCommandError::ResetCredentialStorage(e) => { - VpnError::InternalError { details: e } - } - AccountCommandError::RemoveAccountFiles(e) => VpnError::InternalError { details: e }, - AccountCommandError::InitDeviceKeys(e) => VpnError::InternalError { details: e }, - AccountCommandError::General(err) => VpnError::InternalError { details: err }, - AccountCommandError::Internal(err) => VpnError::InternalError { details: err }, - AccountCommandError::UnregisterDeviceApiClientFailure(err) => { - VpnError::InternalError { details: err } - } - AccountCommandError::RegistrationInProgress => VpnError::InternalError { - details: value.to_string(), - }, - AccountCommandError::GetAccountEndpointFailure(e) => { - VpnError::UpdateAccountEndpointFailure { - details: e.message, - message_id: e.message_id, - code_reference_id: e.code_reference_id, + AccountCommandError::General(err) => Self::InternalError { details: err }, + AccountCommandError::Internal(err) => Self::InternalError { details: err }, + AccountCommandError::NoAccountStored => Self::NoAccountStored, + AccountCommandError::NoDeviceStored => Self::NoDeviceIdentity, + AccountCommandError::StoreAccount(e) => Self::StoreAccount(e.into()), + AccountCommandError::SyncAccount(e) => Self::SyncAccount(e.into()), + AccountCommandError::SyncDevice(e) => Self::SyncDevice(e.into()), + AccountCommandError::RegisterDevice(e) => Self::RegisterDevice(e.into()), + AccountCommandError::RequestZkNym(e) => Self::RequestZkNym(e.into()), + AccountCommandError::RequestZkNymBundle { successes, failed } => { + Self::RequestZkNymBundle { + successes: successes.into_iter().map(|e| e.into()).collect(), + failed: failed.into_iter().map(|e| e.into()).collect(), } } + AccountCommandError::ForgetAccount(e) => Self::ForgetAccount(e.into()), } } } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs index 3f3db1d4b0..31766d735c 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs @@ -55,7 +55,7 @@ mod state_machine; mod uniffi_custom_impls; mod uniffi_lib_types; -use std::{env, path::PathBuf, sync::Arc, time::Duration}; +use std::{env, path::PathBuf, sync::Arc}; use account::AccountControllerHandle; use lazy_static::lazy_static; @@ -349,17 +349,16 @@ async fn start_vpn_inner(config: VPNConfig) -> Result<(), VpnError> { // in the config. let enable_credentials_mode = is_credential_mode_enabled(config.credential_mode).await?; - // TODO: we do a pre-connect check here. This mirrors the logic in the daemon. - // We want to move this check into the state machine so that it happens during the connecting - // state instead. This would allow us more flexibility in waiting for the account to be ready - // and handle errors in a unified manner. - // This can take a surprisingly long time, if we need to go through all steps of registering - // the device and requesting zknym ticketbooks. - let timeout = Duration::from_secs(120); - account::wait_for_account_ready_to_connect(enable_credentials_mode, timeout).await?; + let account_controller_tx = account::get_command_sender().await?; // Once we have established that the account is ready, we can start the state machine. - state_machine::init_state_machine(config, network_env, enable_credentials_mode).await + state_machine::init_state_machine( + config, + network_env, + enable_credentials_mode, + account_controller_tx, + ) + .await } async fn is_credential_mode_enabled(credential_mode: Option) -> Result { diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/state_machine.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/state_machine.rs index f69321bedf..0f418c39ea 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/state_machine.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/state_machine.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_vpn_account_controller::AccountControllerCommander; use nym_vpn_network_config::Network; use tokio::{sync::mpsc, task::JoinHandle}; use tokio_util::sync::CancellationToken; @@ -20,12 +21,18 @@ pub(super) async fn init_state_machine( config: VPNConfig, network_env: Network, enable_credentials_mode: bool, + account_controller_tx: AccountControllerCommander, ) -> Result<(), VpnError> { let mut guard = STATE_MACHINE_HANDLE.lock().await; if guard.is_none() { - let state_machine_handle = - start_state_machine(config, network_env, enable_credentials_mode).await?; + let state_machine_handle = start_state_machine( + config, + network_env, + enable_credentials_mode, + account_controller_tx, + ) + .await?; state_machine_handle.send_command(TunnelCommand::Connect); *guard = Some(state_machine_handle); Ok(()) @@ -40,6 +47,7 @@ pub(super) async fn start_state_machine( config: VPNConfig, network_env: Network, enable_credentials_mode: bool, + account_controller_tx: AccountControllerCommander, ) -> Result { let tunnel_type = if config.enable_two_hop { TunnelType::Wireguard @@ -113,6 +121,7 @@ pub(super) async fn start_state_machine( event_sender, nym_config, tunnel_settings, + account_controller_tx, #[cfg(any(target_os = "ios", target_os = "android"))] config.tun_provider, shutdown_token.child_token(), diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_custom_impls.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_custom_impls.rs index bc2c763821..5ffc1f1d8e 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_custom_impls.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_custom_impls.rs @@ -15,10 +15,7 @@ use nym_sdk::UserAgent as NymUserAgent; use time::OffsetDateTime; use url::Url; -use crate::{ - platform::error::{RequestZkNymError, RequestZkNymSuccess, VpnError}, - NodeIdentity, Recipient, UniffiCustomTypeConverter, -}; +use crate::{platform::error::VpnError, NodeIdentity, Recipient, UniffiCustomTypeConverter}; uniffi::custom_type!(Ipv4Addr, String); uniffi::custom_type!(Ipv6Addr, String); @@ -807,10 +804,10 @@ impl From for Re pub enum RequestZkNymResult { InProgress, Done { - successes: Vec, - failures: Vec, + successes: Vec, + failures: Vec, }, - Error(RequestZkNymError), + Error(super::uniffi_lib_types::RequestZkNymError), } impl From for RequestZkNymResult { diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs index 6eb7a963fc..ab5b6a3533 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs @@ -5,16 +5,22 @@ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; +use nym_vpn_api_client::response::NymErrorResponse; use nym_vpn_lib_types::{ ActionAfterDisconnect as CoreActionAfterDisconnect, BandwidthEvent as CoreBandwidthEvent, ConnectionData as CoreConnectionData, ConnectionEvent as CoreConnectionEvent, ConnectionStatisticsEvent as CoreConnectionStatisticsEvent, - ErrorStateReason as CoreErrorStateReason, Gateway as CoreGateway, - MixnetConnectionData as CoreMixnetConnectionData, MixnetEvent as CoreMixnetEvent, - NymAddress as CoreNymAddress, SphinxPacketRates as CoreSphinxPacketRates, - TunnelConnectionData as CoreTunnelConnectionData, TunnelEvent as CoreTunnelEvent, - TunnelState as CoreTunnelState, WireguardConnectionData as CoreWireguardConnectionData, - WireguardNode as CoreWireguardNode, + ErrorStateReason as CoreErrorStateReason, ForgetAccountError as CoreForgetAccountError, + Gateway as CoreGateway, MixnetConnectionData as CoreMixnetConnectionData, + MixnetEvent as CoreMixnetEvent, NymAddress as CoreNymAddress, + RegisterDeviceError as CoreRegisterDeviceError, RequestZkNymError as CoreRequestZkNymError, + RequestZkNymErrorReason as CoreRequestZkNymErrorReason, + RequestZkNymSuccess as CoreRequestZkNymSuccess, SphinxPacketRates as CoreSphinxPacketRates, + StoreAccountError as CoreStoreAccountError, SyncAccountError as CoreSyncAccountError, + SyncDeviceError as CoreSyncDeviceError, TunnelConnectionData as CoreTunnelConnectionData, + TunnelEvent as CoreTunnelEvent, TunnelState as CoreTunnelState, + VpnApiErrorResponse as CoreVpnApiErrorResponse, + WireguardConnectionData as CoreWireguardConnectionData, WireguardNode as CoreWireguardNode, }; use time::OffsetDateTime; @@ -226,6 +232,218 @@ pub enum ErrorStateReason { BadBandwidthIncrease, DuplicateTunFd, Internal, + + Account(String), + NoAccountStored, + NoDeviceStored, + StoreAccountError(StoreAccountError), + SyncAccount(SyncAccountError), + SyncDevice(SyncDeviceError), + RegisterDevice(RegisterDeviceError), + RequestZkNym(RequestZkNymError), + RequestZkNymBundle { + successes: Vec, + failed: Vec, + }, + ForgetAccount(ForgetAccountError), +} + +#[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq, Eq)] +pub enum StoreAccountError { + #[error("storage: {0}")] + Storage(String), + #[error("vpn api endpoint failure: {0}")] + GetAccountEndpointFailure(VpnApiErrorResponse), + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl From for StoreAccountError { + fn from(value: CoreStoreAccountError) -> Self { + match value { + CoreStoreAccountError::Storage(err) => Self::Storage(err), + CoreStoreAccountError::GetAccountEndpointFailure(failure) => { + Self::GetAccountEndpointFailure(failure.into()) + } + CoreStoreAccountError::UnexpectedResponse(response) => { + Self::UnexpectedResponse(response) + } + } + } +} + +#[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] +pub enum SyncAccountError { + #[error("vpn api endpoint failure: {0}")] + ErrorResponse(VpnApiErrorResponse), + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl From for SyncAccountError { + fn from(value: CoreSyncAccountError) -> Self { + match value { + CoreSyncAccountError::SyncAccountEndpointFailure(failure) => { + Self::ErrorResponse(failure.into()) + } + CoreSyncAccountError::UnexpectedResponse(response) => { + Self::UnexpectedResponse(response) + } + } + } +} + +#[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] +pub enum SyncDeviceError { + #[error("vpn api endpoint failure: {0}")] + ErrorResponse(VpnApiErrorResponse), + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl From for SyncDeviceError { + fn from(value: CoreSyncDeviceError) -> Self { + match value { + CoreSyncDeviceError::SyncDeviceEndpointFailure(failure) => { + Self::ErrorResponse(failure.into()) + } + CoreSyncDeviceError::UnexpectedResponse(response) => Self::UnexpectedResponse(response), + } + } +} + +#[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] +pub enum RegisterDeviceError { + #[error("vpn api endpoint failure: {0}")] + ErrorResponse(VpnApiErrorResponse), + #[error("unexpected response: {0}")] + UnexpectedResponse(String), +} + +impl From for RegisterDeviceError { + fn from(value: CoreRegisterDeviceError) -> Self { + match value { + CoreRegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { + Self::ErrorResponse(failure.into()) + } + CoreRegisterDeviceError::UnexpectedResponse(response) => { + Self::UnexpectedResponse(response) + } + } + } +} + +#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)] +pub struct RequestZkNymSuccess { + pub id: String, +} + +impl From for RequestZkNymSuccess { + fn from(success: CoreRequestZkNymSuccess) -> Self { + Self { id: success.id } + } +} + +#[derive(uniffi::Error, thiserror::Error, Clone, Debug, PartialEq, Eq)] +pub enum RequestZkNymError { + #[error(transparent)] + VpnApi(VpnApiErrorResponse), + #[error("nym-vpn-api: unexpected error response: {0}")] + UnexpectedVpnApiResponse(String), + #[error("storage error: {0}")] + Storage(String), + #[error("{0}")] + Internal(String), +} + +impl From for RequestZkNymError { + fn from(error: CoreRequestZkNymErrorReason) -> Self { + match error { + CoreRequestZkNymErrorReason::VpnApi(err) => Self::VpnApi(err.into()), + CoreRequestZkNymErrorReason::UnexpectedVpnApiResponse(response) => { + Self::UnexpectedVpnApiResponse(response) + } + CoreRequestZkNymErrorReason::Storage(err) => Self::Storage(err), + CoreRequestZkNymErrorReason::Internal(err) => Self::Internal(err), + } + } +} + +impl From for RequestZkNymError { + fn from(error: CoreRequestZkNymError) -> Self { + CoreRequestZkNymErrorReason::from(error).into() + } +} + +#[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq, Eq)] +pub enum ForgetAccountError { + #[error("registration is in progress")] + RegistrationInProgress, + #[error("failed to remove device from nym vpn api: {0}")] + UpdateDeviceErrorResponse(VpnApiErrorResponse), + #[error("unexpected response: {0}")] + UnexpectedResponse(String), + #[error("failed to remove account: {0}")] + RemoveAccount(String), + #[error("failed to remove device keys: {0}")] + RemoveDeviceKeys(String), + #[error("failed to reset credential storage: {0}")] + ResetCredentialStorage(String), + #[error("failed to remove account files: {0}")] + RemoveAccountFiles(String), + #[error("failed to init device keys: {0}")] + InitDeviceKeys(String), +} + +impl From for ForgetAccountError { + fn from(value: CoreForgetAccountError) -> Self { + match value { + CoreForgetAccountError::RegistrationInProgress => Self::RegistrationInProgress, + CoreForgetAccountError::UpdateDeviceErrorResponse(failure) => { + Self::UpdateDeviceErrorResponse(failure.into()) + } + CoreForgetAccountError::UnexpectedResponse(response) => { + Self::UnexpectedResponse(response) + } + CoreForgetAccountError::RemoveAccount(err) => Self::RemoveAccount(err), + CoreForgetAccountError::RemoveDeviceKeys(err) => Self::RemoveDeviceKeys(err), + CoreForgetAccountError::ResetCredentialStorage(err) => { + Self::ResetCredentialStorage(err) + } + CoreForgetAccountError::RemoveAccountFiles(err) => Self::RemoveAccountFiles(err), + CoreForgetAccountError::InitDeviceKeys(err) => Self::InitDeviceKeys(err), + } + } +} + +#[derive(uniffi::Record, thiserror::Error, Debug, Clone, PartialEq, Eq)] +#[error( + "nym-vpn-api: message: {message}, message_id: {message_id:?}, code_reference_id: {code_reference_id:?}" +)] +pub struct VpnApiErrorResponse { + pub message: String, + pub message_id: Option, + pub code_reference_id: Option, +} + +impl From for VpnApiErrorResponse { + fn from(value: CoreVpnApiErrorResponse) -> Self { + Self { + message: value.message, + message_id: value.message_id, + code_reference_id: value.code_reference_id, + } + } +} + +impl From for VpnApiErrorResponse { + fn from(value: NymErrorResponse) -> Self { + Self { + message: value.message, + message_id: value.message_id, + code_reference_id: value.code_reference_id, + } + } } impl From for ErrorStateReason { @@ -242,6 +460,25 @@ impl From for ErrorStateReason { CoreErrorStateReason::BadBandwidthIncrease => Self::BadBandwidthIncrease, CoreErrorStateReason::DuplicateTunFd => Self::DuplicateTunFd, CoreErrorStateReason::Internal => Self::Internal, + + CoreErrorStateReason::Account(err) => Self::Account(err), + CoreErrorStateReason::NoAccountStored => Self::NoAccountStored, + CoreErrorStateReason::NoDeviceStored => Self::NoDeviceStored, + CoreErrorStateReason::StoreAccount(err) => Self::StoreAccountError(err.into()), + CoreErrorStateReason::SyncAccount(err) => Self::SyncAccount(err.into()), + CoreErrorStateReason::SyncDevice(err) => Self::SyncDevice(err.into()), + CoreErrorStateReason::RegisterDevice(err) => Self::RegisterDevice(err.into()), + CoreErrorStateReason::RequestZkNym(err) => Self::RequestZkNym(err.into()), + CoreErrorStateReason::RequestZkNymBundle { successes, failed } => { + Self::RequestZkNymBundle { + successes: successes + .into_iter() + .map(RequestZkNymSuccess::from) + .collect(), + failed: failed.into_iter().map(RequestZkNymError::from).collect(), + } + } + CoreErrorStateReason::ForgetAccount(err) => Self::ForgetAccount(err.into()), } } } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs new file mode 100644 index 0000000000..a28da851af --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs @@ -0,0 +1,63 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_vpn_account_controller::AccountControllerCommander; +use nym_vpn_lib_types::AccountCommandError; +use tokio_util::sync::CancellationToken; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("timeout")] + Cancelled, + + #[error("account error: {0}")] + Account(#[from] AccountCommandError), +} + +async fn wait_for_sync_inner( + account_controller_tx: AccountControllerCommander, +) -> Result<(), AccountCommandError> { + account_controller_tx + .ensure_update_account() + .await + .map(|_| ())?; + account_controller_tx + .ensure_update_device() + .await + .map(|_| ()) +} + +pub async fn wait_for_sync( + account_controller_tx: AccountControllerCommander, + cancel_token: CancellationToken, +) -> Result<(), Error> { + cancel_token + .run_until_cancelled(wait_for_sync_inner(account_controller_tx)) + .await + .ok_or(Error::Cancelled)? + .map_err(Error::Account) +} + +pub async fn wait_for_device_register( + account_controller_tx: AccountControllerCommander, + cancel_token: CancellationToken, +) -> Result<(), Error> { + cancel_token + .run_until_cancelled(account_controller_tx.ensure_register_device()) + .await + .ok_or(Error::Cancelled)? + .map_err(Error::Account) +} + +// Waiting for credentials to be ready can take a while if it's from scratch, in the order of 30 +// seconds at least. +pub async fn wait_for_credentials_ready( + account_controller_tx: AccountControllerCommander, + cancel_token: CancellationToken, +) -> Result<(), Error> { + cancel_token + .run_until_cancelled(account_controller_tx.ensure_available_zk_nyms()) + .await + .ok_or(Error::Cancelled)? + .map_err(Error::Account) +} diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs index 3cdcf714c5..eb5a1f11d1 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +mod account; #[cfg(target_os = "linux")] mod default_interface; #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] @@ -22,6 +23,7 @@ mod wintun; use std::sync::Arc; use std::{net::IpAddr, path::PathBuf}; +use nym_vpn_account_controller::AccountControllerCommander; use tokio::{sync::mpsc, task::JoinHandle}; use tokio_util::sync::CancellationToken; @@ -288,6 +290,7 @@ pub struct SharedState { tun_provider: Arc, #[cfg(target_os = "android")] tun_provider: Arc, + account_command_tx: AccountControllerCommander, } #[derive(Debug, Clone)] @@ -313,6 +316,7 @@ impl TunnelStateMachine { event_sender: mpsc::UnboundedSender, nym_config: NymConfig, tunnel_settings: TunnelSettings, + account_command_tx: AccountControllerCommander, #[cfg(target_os = "ios")] tun_provider: Arc, #[cfg(target_os = "android")] tun_provider: Arc, shutdown_token: CancellationToken, @@ -363,6 +367,7 @@ impl TunnelStateMachine { status_listener_handle: None, #[cfg(any(target_os = "ios", target_os = "android"))] tun_provider, + account_command_tx, }; let tunnel_state_machine = Self { @@ -480,10 +485,13 @@ pub enum Error { #[error("tunnel error: {}", _0)] Tunnel(#[from] tunnel::Error), + + #[error("account error: {0}")] + Account(#[from] account::Error), } impl Error { - fn error_state_reason(&self) -> Option { + fn error_state_reason(self) -> Option { Some(match self { #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] Self::CreateRouteHandler(_) | Self::AddRoutes(_) => ErrorStateReason::Routing, @@ -513,12 +521,14 @@ impl Error { #[cfg(target_os = "linux")] Self::GetDefaultInterface(_) => ErrorStateReason::Internal, + + Self::Account(err) => err.error_state_reason()?, }) } } impl tunnel::Error { - fn error_state_reason(&self) -> Option { + fn error_state_reason(self) -> Option { match self { Self::SelectGateways(e) => match e { GatewayDirectoryError::SameEntryAndExitGatewayFromCountry { .. } => { @@ -549,4 +559,13 @@ impl tunnel::Error { } } +impl account::Error { + fn error_state_reason(self) -> Option { + match self { + Self::Account(e) => Some(e.into()), + Self::Cancelled => None, + } + } +} + pub type Result = std::result::Result; diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs index 10a5620285..dd4f3792d1 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs @@ -56,6 +56,7 @@ impl ConnectingState { shared_state.tun_provider.clone(), shared_state.nym_config.clone(), shared_state.tunnel_settings.clone(), + shared_state.account_command_tx.clone(), ); ( @@ -103,6 +104,18 @@ impl TunnelStateHandler for ConnectingState { TunnelMonitorEvent::InitializingClient => { NextTunnelState::SameState(self) } + TunnelMonitorEvent::SyncingAccount => { + NextTunnelState::SameState(self) + } + TunnelMonitorEvent::RegisteringDevice => { + NextTunnelState::SameState(self) + } + TunnelMonitorEvent::RequestingZkNyms => { + NextTunnelState::SameState(self) + } + TunnelMonitorEvent::SelectingGateways => { + NextTunnelState::SameState(self) + } TunnelMonitorEvent::EstablishingTunnel(conn_data) => { NextTunnelState::NewState((self, PrivateTunnelState::Connecting { connection_data: Some(*conn_data) })) } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs index ea886a3810..535400678f 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs @@ -13,6 +13,7 @@ use super::wintun::{self, WintunAdapterConfig}; #[cfg(any(target_os = "ios", target_os = "android"))] use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; use nym_gateway_directory::GatewayMinPerformance; +use nym_vpn_account_controller::AccountControllerCommander; use time::OffsetDateTime; use tokio::{sync::mpsc, task::JoinHandle}; use tokio_util::sync::CancellationToken; @@ -50,7 +51,7 @@ use crate::tunnel_provider; use crate::tunnel_provider::android::AndroidTunProvider; #[cfg(target_os = "ios")] use crate::tunnel_provider::ios::OSTunProvider; -use crate::tunnel_state_machine::WireguardMultihopMode; +use crate::tunnel_state_machine::{account, WireguardMultihopMode}; /// Default MTU for mixnet tun device. const DEFAULT_TUN_MTU: u16 = if cfg!(any(target_os = "ios", target_os = "android")) { @@ -102,6 +103,18 @@ pub enum TunnelMonitorEvent { /// Initializing mixnet client InitializingClient, + /// Syncronizing account with vpn-api + SyncingAccount, + + /// Registering device with vpn-api + RegisteringDevice, + + /// Requesting and downloading zknym credentials from vpn-api + RequestingZkNyms, + + /// Selecting gateways + SelectingGateways, + /// Selected gateways SelectedGateways(Box), @@ -148,6 +161,7 @@ pub struct TunnelMonitor { tun_provider: Arc, nym_config: NymConfig, tunnel_settings: TunnelSettings, + account_controller_tx: AccountControllerCommander, cancel_token: CancellationToken, } @@ -167,6 +181,7 @@ impl TunnelMonitor { #[cfg(target_os = "android")] tun_provider: Arc, nym_config: NymConfig, tunnel_settings: TunnelSettings, + account_controller_tx: AccountControllerCommander, ) -> TunnelMonitorHandle { let cancel_token = CancellationToken::new(); let tunnel_monitor = Self { @@ -180,6 +195,7 @@ impl TunnelMonitor { tun_provider, nym_config, tunnel_settings, + account_controller_tx, cancel_token: cancel_token.clone(), }; let join_handle = tokio::spawn(tunnel_monitor.run(retry_attempt, selected_gateways)); @@ -225,6 +241,10 @@ impl TunnelMonitor { self.send_event(TunnelMonitorEvent::InitializingClient); + self.setup_account().await?; + + self.send_event(TunnelMonitorEvent::SelectingGateways); + let gateway_performance_options = self.tunnel_settings.gateway_performance_options; let gateway_min_performance = GatewayMinPerformance::from_percentage_values( gateway_performance_options @@ -385,6 +405,33 @@ impl TunnelMonitor { } } + async fn setup_account(&mut self) -> Result<()> { + self.send_event(TunnelMonitorEvent::SyncingAccount); + account::wait_for_sync( + self.account_controller_tx.clone(), + self.cancel_token.clone(), + ) + .await?; + + self.send_event(TunnelMonitorEvent::RegisteringDevice); + account::wait_for_device_register( + self.account_controller_tx.clone(), + self.cancel_token.clone(), + ) + .await?; + + if self.tunnel_settings.enable_credentials_mode { + self.send_event(TunnelMonitorEvent::RequestingZkNyms); + account::wait_for_credentials_ready( + self.account_controller_tx.clone(), + self.cancel_token.clone(), + ) + .await?; + } + + Ok(()) + } + async fn start_mixnet_tunnel( &mut self, connected_mixnet: ConnectedMixnet, diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs new file mode 100644 index 0000000000..4a9a27854f --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs @@ -0,0 +1,168 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_vpn_lib_types::{ + ForgetAccountError, RegisterDeviceError, RequestZkNymErrorReason, RequestZkNymSuccess, + StoreAccountError, SyncAccountError, SyncDeviceError, VpnApiErrorResponse, +}; + +use crate::{ + conversions::ConversionError, ForgetAccountError as ProtoForgetAccountError, + GeneralAccountError as ProtoGeneralAccountError, + RegisterDeviceError as ProtoRegisterDeviceError, RequestZkNymError as ProtoRequestZkNymError, + RequestZkNymSuccess as ProtoRequestZkNymSuccess, StoreAccountError as ProtoStoreAccountError, + SyncAccountError as ProtoSyncAccountError, SyncDeviceError as ProtoSyncDeviceError, + VpnApiErrorResponse as ProtoVpnApiErrorResponse, +}; + +impl From for String { + fn from(value: ProtoGeneralAccountError) -> Self { + value.error_message + } +} + +impl TryFrom for StoreAccountError { + type Error = ConversionError; + + fn try_from(value: ProtoStoreAccountError) -> Result { + let error_detail = value + .error_detail + .ok_or(ConversionError::NoValueSet("StoreAccountError"))?; + Ok(match error_detail { + crate::store_account_error::ErrorDetail::StorageError(err) => Self::Storage(err), + crate::store_account_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { + Self::GetAccountEndpointFailure(vpn_api_endpoint_failure.into()) + } + crate::store_account_error::ErrorDetail::UnexpectedResponse(err) => { + Self::UnexpectedResponse(err) + } + }) + } +} + +impl TryFrom for SyncAccountError { + type Error = ConversionError; + + fn try_from(value: ProtoSyncAccountError) -> Result { + let error_detail = value + .error_detail + .ok_or(ConversionError::NoValueSet("SyncAccountError"))?; + Ok(match error_detail { + crate::sync_account_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { + Self::SyncAccountEndpointFailure(vpn_api_endpoint_failure.into()) + } + crate::sync_account_error::ErrorDetail::UnexpectedResponse(err) => { + Self::UnexpectedResponse(err) + } + }) + } +} + +impl TryFrom for SyncDeviceError { + type Error = ConversionError; + + fn try_from(value: ProtoSyncDeviceError) -> Result { + let error_detail = value + .error_detail + .ok_or(ConversionError::NoValueSet("SyncDeviceError"))?; + Ok(match error_detail { + crate::sync_device_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { + Self::SyncDeviceEndpointFailure(vpn_api_endpoint_failure.into()) + } + crate::sync_device_error::ErrorDetail::UnexpectedResponse(err) => { + Self::UnexpectedResponse(err) + } + }) + } +} + +impl TryFrom for RegisterDeviceError { + type Error = ConversionError; + + fn try_from(value: ProtoRegisterDeviceError) -> Result { + let error_detail = value + .error_detail + .ok_or(ConversionError::NoValueSet("RegisterDeviceError"))?; + Ok(match error_detail { + crate::register_device_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { + Self::RegisterDeviceEndpointFailure(vpn_api_endpoint_failure.into()) + } + crate::register_device_error::ErrorDetail::UnexpectedResponse(err) => { + Self::UnexpectedResponse(err) + } + }) + } +} + +impl From for RequestZkNymSuccess { + fn from(value: ProtoRequestZkNymSuccess) -> Self { + Self { id: value.id } + } +} + +impl TryFrom for RequestZkNymErrorReason { + type Error = ConversionError; + + fn try_from(value: ProtoRequestZkNymError) -> Result { + let error_outcome = value + .outcome + .ok_or(ConversionError::NoValueSet("RequestZkNymError.error"))?; + + Ok(match error_outcome { + crate::request_zk_nym_error::Outcome::VpnApi(vpn_api_endpoint_failure) => { + Self::VpnApi(vpn_api_endpoint_failure.into()) + } + crate::request_zk_nym_error::Outcome::UnexpectedVpnApiResponse(message) => { + Self::UnexpectedVpnApiResponse(message) + } + crate::request_zk_nym_error::Outcome::Storage(message) => Self::Storage(message), + crate::request_zk_nym_error::Outcome::Internal(message) => Self::Internal(message), + }) + } +} + +impl TryFrom for ForgetAccountError { + type Error = ConversionError; + + fn try_from(value: ProtoForgetAccountError) -> Result { + let error_detail = value + .error_detail + .ok_or(ConversionError::NoValueSet("ForgetAccountError"))?; + Ok(match error_detail { + crate::forget_account_error::ErrorDetail::RegistrationInProgress(_) => { + Self::RegistrationInProgress + } + crate::forget_account_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { + Self::UpdateDeviceErrorResponse(vpn_api_endpoint_failure.into()) + } + crate::forget_account_error::ErrorDetail::UnexpectedResponse(err) => { + Self::UnexpectedResponse(err) + } + crate::forget_account_error::ErrorDetail::RemoveAccount(err) => { + Self::RemoveAccount(err) + } + crate::forget_account_error::ErrorDetail::RemoveDeviceKeys(err) => { + Self::RemoveDeviceKeys(err) + } + crate::forget_account_error::ErrorDetail::ResetCredentialStore(err) => { + Self::ResetCredentialStorage(err) + } + crate::forget_account_error::ErrorDetail::RemoveAccountFiles(err) => { + Self::RemoveAccountFiles(err) + } + crate::forget_account_error::ErrorDetail::InitDeviceKeys(err) => { + Self::InitDeviceKeys(err) + } + }) + } +} + +impl From for VpnApiErrorResponse { + fn from(value: ProtoVpnApiErrorResponse) -> Self { + Self { + message: value.message, + message_id: value.message_id, + code_reference_id: value.code_reference_id, + } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/mod.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/mod.rs index 3e96401d45..875b6c690d 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/mod.rs @@ -3,6 +3,7 @@ use super::ConversionError; +pub mod account; pub mod network_config; pub mod tunnel_event; pub mod tunnel_state; diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs index f928e5bd40..f06617ec0e 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs @@ -18,10 +18,12 @@ use crate::{ Wireguard as ProtoWireguardConnectionDataVariant, }, tunnel_state::{ - ActionAfterDisconnect as ProtoActionAfterDisconnect, Connected as ProtoConnected, + error::ErrorStateReason as ProtoErrorStateReason, + ActionAfterDisconnect as ProtoActionAfterDisconnect, + BaseErrorStateReason as ProtoBaseErrorStateReason, Connected as ProtoConnected, Connecting as ProtoConnecting, Disconnected as ProtoDisconnected, - Disconnecting as ProtoDisconnecting, Error as ProtoError, - ErrorStateReason as ProtoErrorStateReason, Offline as ProtoOffline, State as ProtoState, + Disconnecting as ProtoDisconnecting, Error as ProtoError, Offline as ProtoOffline, + State as ProtoState, }, Address as ProtoAddress, ConnectionData as ProtoConnectionData, Gateway as ProtoGateway, MixnetConnectionData as ProtoMixnetConnectionData, @@ -40,20 +42,75 @@ impl From for ActionAfterDisconnect { } } -impl From for ErrorStateReason { - fn from(value: ProtoErrorStateReason) -> Self { +impl TryFrom for ErrorStateReason { + type Error = ConversionError; + + fn try_from(value: ProtoErrorStateReason) -> Result { + Ok(match value { + ProtoErrorStateReason::BaseReason(reason) => { + let proto_base_reason = ProtoBaseErrorStateReason::try_from(reason) + .map_err(|e| ConversionError::Decode("BaseErrorStateReason.base_reason", e))?; + + Self::from(proto_base_reason) + } + ProtoErrorStateReason::GeneralAccount(general_account_error) => { + Self::Account(general_account_error.into()) + } + ProtoErrorStateReason::StoreAccount(store_account_error) => { + Self::StoreAccount(store_account_error.try_into()?) + } + ProtoErrorStateReason::SyncAccount(sync_account_error) => { + Self::SyncAccount(sync_account_error.try_into()?) + } + ProtoErrorStateReason::SyncDevice(sync_device_error) => { + Self::SyncDevice(sync_device_error.try_into()?) + } + ProtoErrorStateReason::RegisterDevice(register_device_error) => { + Self::RegisterDevice(register_device_error.try_into()?) + } + ProtoErrorStateReason::RequestZkNym(request_zk_nym_general_error) => { + Self::RequestZkNym(request_zk_nym_general_error.try_into()?) + } + ProtoErrorStateReason::RequestZkNymBundle(request_zk_nym_bundle) => { + let failures = request_zk_nym_bundle + .failures + .into_iter() + .map(|outcome| outcome.try_into()) + .collect::, _>>()?; + Self::RequestZkNymBundle { + successes: request_zk_nym_bundle + .successes + .into_iter() + .map(Into::into) + .collect(), + failed: failures, + } + } + ProtoErrorStateReason::ForgetAccount(forget_account_error) => { + Self::ForgetAccount(forget_account_error.try_into()?) + } + }) + } +} + +impl From for ErrorStateReason { + fn from(value: ProtoBaseErrorStateReason) -> Self { match value { - ProtoErrorStateReason::Firewall => Self::Firewall, - ProtoErrorStateReason::Routing => Self::Routing, - ProtoErrorStateReason::Dns => Self::Dns, - ProtoErrorStateReason::TunDevice => Self::TunDevice, - ProtoErrorStateReason::TunnelProvider => Self::TunnelProvider, - ProtoErrorStateReason::SameEntryAndExitGateway => Self::SameEntryAndExitGateway, - ProtoErrorStateReason::InvalidEntryGatewayCountry => Self::InvalidEntryGatewayCountry, - ProtoErrorStateReason::InvalidExitGatewayCountry => Self::InvalidExitGatewayCountry, - ProtoErrorStateReason::BadBandwidthIncrease => Self::BadBandwidthIncrease, - ProtoErrorStateReason::DuplicateTunFd => Self::DuplicateTunFd, - ProtoErrorStateReason::Internal => Self::Internal, + ProtoBaseErrorStateReason::Firewall => Self::Firewall, + ProtoBaseErrorStateReason::Routing => Self::Routing, + ProtoBaseErrorStateReason::Dns => Self::Dns, + ProtoBaseErrorStateReason::TunDevice => Self::TunDevice, + ProtoBaseErrorStateReason::TunnelProvider => Self::TunnelProvider, + ProtoBaseErrorStateReason::SameEntryAndExitGateway => Self::SameEntryAndExitGateway, + ProtoBaseErrorStateReason::InvalidEntryGatewayCountry => { + Self::InvalidEntryGatewayCountry + } + ProtoBaseErrorStateReason::InvalidExitGatewayCountry => Self::InvalidExitGatewayCountry, + ProtoBaseErrorStateReason::BadBandwidthIncrease => Self::BadBandwidthIncrease, + ProtoBaseErrorStateReason::DuplicateTunFd => Self::DuplicateTunFd, + ProtoBaseErrorStateReason::Internal => Self::Internal, + ProtoBaseErrorStateReason::NoAccountStored => Self::NoAccountStored, + ProtoBaseErrorStateReason::NoDeviceStored => Self::NoDeviceStored, } } } @@ -88,10 +145,12 @@ impl TryFrom for TunnelState { Self::Connected { connection_data } } - ProtoState::Error(ProtoError { reason }) => { - let reason = ProtoErrorStateReason::try_from(reason) - .map_err(|e| ConversionError::Decode("TunnelState.after_disconnect", e)) - .map(ErrorStateReason::from)?; + ProtoState::Error(ProtoError { error_state_reason }) => { + let reason = error_state_reason + .ok_or(ConversionError::NoValueSet("TunnelState.error")) + .map(ProtoErrorStateReason::from) + .and_then(ErrorStateReason::try_from)?; + Self::Error(reason) } ProtoState::Offline(ProtoOffline { reconnect }) => Self::Offline { reconnect }, diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs index d44ba44438..0c400e85f3 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs @@ -1,533 +1,49 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -impl From> for crate::AccountIdentity { - fn from(identity: Option) -> Self { - Self { - account_identity: identity, - } - } -} - -impl From for crate::IsReadyToConnectResponse { - fn from(ready: nym_vpn_account_controller::ReadyToConnect) -> Self { - use crate::is_ready_to_connect_response::IsReadyToConnectResponseType; - use nym_vpn_account_controller::ReadyToConnect; - let kind = match ready { - ReadyToConnect::Ready => IsReadyToConnectResponseType::Ready, - ReadyToConnect::NoMnemonicStored => IsReadyToConnectResponseType::NoAccountStored, - ReadyToConnect::AccountNotSynced => IsReadyToConnectResponseType::AccountNotSynced, - ReadyToConnect::AccountNotRegistered => { - IsReadyToConnectResponseType::AccountNotRegistered - } - ReadyToConnect::AccountNotActive => IsReadyToConnectResponseType::AccountNotActive, - ReadyToConnect::NoActiveSubscription => { - IsReadyToConnectResponseType::NoActiveSubscription - } - ReadyToConnect::DeviceNotRegistered => { - IsReadyToConnectResponseType::DeviceNotRegistered - } - ReadyToConnect::DeviceNotActive => IsReadyToConnectResponseType::DeviceNotActive, - } as i32; - Self { kind } - } -} - -impl From for crate::MnemonicState { - fn from(mnemonic: nym_vpn_account_controller::shared_state::MnemonicState) -> Self { - match mnemonic { - nym_vpn_account_controller::shared_state::MnemonicState::Stored { .. } => { - crate::MnemonicState::Stored - } - nym_vpn_account_controller::shared_state::MnemonicState::NotStored => { - crate::MnemonicState::NotStored - } - } - } -} - -impl From - for crate::AccountRegistered -{ - fn from( - account_registered: nym_vpn_account_controller::shared_state::AccountRegistered, - ) -> Self { - match account_registered { - nym_vpn_account_controller::shared_state::AccountRegistered::Registered => { - crate::AccountRegistered::AccountRegistered - } - nym_vpn_account_controller::shared_state::AccountRegistered::NotRegistered => { - crate::AccountRegistered::AccountNotRegistered - } - } - } -} +use nym_vpn_lib_types::{RequestZkNymError, RequestZkNymErrorReason, RequestZkNymSuccess}; -impl From for crate::AccountState { - fn from(account: nym_vpn_account_controller::shared_state::AccountState) -> Self { - match account { - nym_vpn_account_controller::shared_state::AccountState::Inactive => { - crate::AccountState::Inactive - } - nym_vpn_account_controller::shared_state::AccountState::Active => { - crate::AccountState::Active - } - nym_vpn_account_controller::shared_state::AccountState::DeleteMe => { - crate::AccountState::DeleteMe - } - } - } -} +use crate::{ + AccountIdentity as ProtoAccountIdentity, RequestZkNymError as ProtoRequestZkNymError, + RequestZkNymSuccess as ProtoRequestZkNymSuccess, +}; -impl From - for crate::SubscriptionState -{ - fn from(subscription: nym_vpn_account_controller::shared_state::SubscriptionState) -> Self { - match subscription { - nym_vpn_account_controller::shared_state::SubscriptionState::NotActive => { - crate::SubscriptionState::NotRegistered - } - nym_vpn_account_controller::shared_state::SubscriptionState::Pending => { - crate::SubscriptionState::Pending - } - nym_vpn_account_controller::shared_state::SubscriptionState::Active => { - crate::SubscriptionState::Active - } - nym_vpn_account_controller::shared_state::SubscriptionState::Complete => { - crate::SubscriptionState::Complete - } - } - } -} - -impl From for crate::DeviceSummary { - fn from(device_summary: nym_vpn_account_controller::shared_state::DeviceSummary) -> Self { - Self { - active: device_summary.active, - max: device_summary.max, - remaining: device_summary.remaining, - } - } -} - -impl From for crate::AccountSummary { - fn from(account_summary: nym_vpn_account_controller::shared_state::AccountSummary) -> Self { +impl From> for ProtoAccountIdentity { + fn from(identity: Option) -> Self { Self { - account: crate::AccountState::from(account_summary.account) as i32, - subscription: crate::SubscriptionState::from(account_summary.subscription) as i32, - device_summary: Some(crate::DeviceSummary::from(account_summary.device_summary)), - } - } -} - -impl From for crate::DeviceState { - fn from(device: nym_vpn_account_controller::shared_state::DeviceState) -> Self { - match device { - nym_vpn_account_controller::shared_state::DeviceState::NotRegistered => { - crate::DeviceState::NotRegistered - } - nym_vpn_account_controller::shared_state::DeviceState::Inactive => { - crate::DeviceState::Inactive - } - nym_vpn_account_controller::shared_state::DeviceState::Active => { - crate::DeviceState::Active - } - nym_vpn_account_controller::shared_state::DeviceState::DeleteMe => { - crate::DeviceState::DeleteMe - } + account_identity: identity, } } } -impl From - for crate::RegisterDeviceResult -{ - fn from( - device_registration: nym_vpn_account_controller::shared_state::RegisterDeviceResult, - ) -> Self { - match device_registration { - nym_vpn_account_controller::shared_state::RegisterDeviceResult::InProgress => Self { - kind: crate::register_device_result::RegisterDeviceResultType::InProgress as i32, - ..Default::default() - }, - nym_vpn_account_controller::shared_state::RegisterDeviceResult::Success => Self { - kind: crate::register_device_result::RegisterDeviceResultType::Success as i32, - ..Default::default() - }, - nym_vpn_account_controller::shared_state::RegisterDeviceResult::Failed(err) => match err - { - nym_vpn_account_controller::RegisterDeviceError::RegisterDeviceEndpointFailure( - err, - ) => Self { - kind: crate::register_device_result::RegisterDeviceResultType::Failed as i32, - message: Some(err.message), - message_id: err.message_id, - }, - nym_vpn_account_controller::RegisterDeviceError::General(err) => Self { - kind: crate::register_device_result::RegisterDeviceResultType::Failed as i32, - message: Some(err), - message_id: None, - }, - }, - } +impl From for ProtoRequestZkNymSuccess { + fn from(value: RequestZkNymSuccess) -> Self { + Self { id: value.id } } } -impl From for crate::RequestZkNymSuccess { - fn from(request_success: nym_vpn_account_controller::RequestZkNymSuccess) -> Self { - Self { - id: request_success.id.to_string(), - } - } -} - -impl From for crate::RequestZkNymError { - fn from(error: nym_vpn_account_controller::RequestZkNymError) -> Self { - match error { - nym_vpn_account_controller::RequestZkNymError::GetZkNymsAvailableForDownloadEndpointFailure { - source, - } => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::GetZkNymsAvailableForDownloadEndpointFailure as i32, - id: None, - ticketbook_type: None, - message: Some(source.message.clone()), - message_id: source.message_id.clone(), - }, - nym_vpn_account_controller::RequestZkNymError::CreateEcashKeyPair(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::CreateEcashKeyPair as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::ConstructWithdrawalRequest(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::ConstructWithdrawalRequest - as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::RequestZkNymEndpointFailure { - source, - ticket_type, - } => Self { - kind: - crate::request_zk_nym_error::RequestZkNymErrorType::RequestZkNymEndpointFailure - as i32, - id: None, - ticketbook_type: Some(ticket_type), - message: Some(source.message.clone()), - message_id: source.message_id.clone(), - }, - nym_vpn_account_controller::RequestZkNymError::InvalidTicketTypeInResponse(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::InvalidTicketTypeInResponse - as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::TicketTypeMismatch => { - Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::TicketTypeMismatch - as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - } - } - nym_vpn_account_controller::RequestZkNymError::PollZkNymEndpointFailure { - source, - } => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::PollZkNymEndpointFailure - as i32, - id: None, - ticketbook_type: None, - message: Some(source.message.clone()), - message_id: source.message_id.clone(), - }, - nym_vpn_account_controller::RequestZkNymError::PollingTaskError => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::PollingTaskError as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::PollingTimeout { id } => { - Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::PollingTimeout as i32, - id: Some(id.clone()), - ticketbook_type: None, - message: None, - message_id: None, - } +impl From for ProtoRequestZkNymError { + fn from(error: RequestZkNymErrorReason) -> Self { + let outcome = match error { + RequestZkNymErrorReason::VpnApi(vpn_api_endpoint_failure) => Some( + crate::request_zk_nym_error::Outcome::VpnApi(vpn_api_endpoint_failure.into()), + ), + RequestZkNymErrorReason::UnexpectedVpnApiResponse(err) => { + Some(crate::request_zk_nym_error::Outcome::UnexpectedVpnApiResponse(err)) } - nym_vpn_account_controller::RequestZkNymError::FinishedWithError { - id, - ticket_type, - status, - } => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::FinishedWithError as i32, - id: Some(id.clone()), - ticketbook_type: Some(ticket_type), - message: Some(status.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::MissingBlindedShares => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::MissingBlindedShares as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::ResponseHasInvalidMasterVerificationKey(err) => { - Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::ResponseHasInvalidMasterVerificationKey as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - } - }, - nym_vpn_account_controller::RequestZkNymError::EpochIdMismatch => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::EpochIdMismatch as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::ExpirationDateMismatch => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::ExpirationDateMismatch as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::GetPartialVerificationKeysEndpointFailure { source, .. } => { - Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::GetPartialVerificationKeysEndpointFailure as i32, - id: None, - ticketbook_type: None, - message: Some(source.message.clone()), - message_id: source.message_id.clone(), - } - }, - nym_vpn_account_controller::RequestZkNymError::NoMasterVerificationKeyInStorage => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::NoMasterVerificationKeyInStorage as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::NoCoinIndexSignaturesInStorage => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::NoCoinIndexSignaturesInStorage as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::NoExpirationDateSignaturesInStorage => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::NoExpirationDateSignaturesInStorage as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::InvalidVerificationKey(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::InvalidVerificationKey as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::DeserializeBlindedSignature(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::DeserializeBlindedSignature as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::DecodedKeysMissingIndex => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::DecodedKeysMissingIndex as i32, - id: None, - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::ImportZkNym { ticket_type, error } => { - Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::Import as i32, - id: None, - ticketbook_type: Some(ticket_type), - message: Some(error.to_string()), - message_id: None, - } + RequestZkNymErrorReason::Storage(err) => { + Some(crate::request_zk_nym_error::Outcome::Storage(err)) } - nym_vpn_account_controller::RequestZkNymError::AggregateWallets(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::AggregateWallets as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::ConfirmZkNymDownloadEndpointFailure { source, id } => { - Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::ConfirmZkNymDownloadEndpointFailure as i32, - id: Some(id.clone()), - ticketbook_type: None, - message: Some(source.message.clone()), - message_id: source.message_id.clone(), - } - }, - nym_vpn_account_controller::RequestZkNymError::MissingPendingRequest(id) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::MissingPendingRequest as i32, - id: Some(id.clone()), - ticketbook_type: None, - message: None, - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::RemovePendingRequest { id, error } => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::RemovePendingRequest as i32, - id: Some(id.clone()), - ticketbook_type: None, - message: Some(error.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::CredentialStorage(credential_error) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::CredentialStorage as i32, - id: None, - ticketbook_type: None, - message: Some(credential_error.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::Internal(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::Internal as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - nym_vpn_account_controller::RequestZkNymError::UnexpectedErrorResponse(err) => Self { - kind: crate::request_zk_nym_error::RequestZkNymErrorType::UnexpectedErrorResponse as i32, - id: None, - ticketbook_type: None, - message: Some(err.to_string()), - message_id: None, - }, - } - } -} - -impl From - for crate::RequestZkNymResult -{ - fn from(zk_nym_request: nym_vpn_account_controller::shared_state::RequestZkNymResult) -> Self { - match zk_nym_request { - nym_vpn_account_controller::shared_state::RequestZkNymResult::InProgress => { - crate::RequestZkNymResult { - kind: crate::request_zk_nym_result::RequestZkNymResultType::InProgress as i32, - successes: Default::default(), - failures: Default::default(), - } + RequestZkNymErrorReason::Internal(err) => { + Some(crate::request_zk_nym_error::Outcome::Internal(err)) } - nym_vpn_account_controller::shared_state::RequestZkNymResult::Done { - successes, - failures, - } => crate::RequestZkNymResult { - kind: crate::request_zk_nym_result::RequestZkNymResultType::Done as i32, - successes: successes - .into_iter() - .map(crate::RequestZkNymSuccess::from) - .collect(), - failures: failures - .into_iter() - .map(crate::RequestZkNymError::from) - .collect(), - }, - nym_vpn_account_controller::shared_state::RequestZkNymResult::Error(e) => { - crate::RequestZkNymResult { - kind: crate::request_zk_nym_result::RequestZkNymResultType::Error as i32, - successes: Default::default(), - failures: vec![crate::RequestZkNymError::from(e)], - } - } - } - } -} - -impl From for crate::AccountStateSummary { - fn from(state: nym_vpn_account_controller::AccountStateSummary) -> Self { - Self { - mnemonic: state - .mnemonic - .map(crate::MnemonicState::from) - .map(|m| m as i32), - account_registered: state - .account_registered - .map(crate::AccountRegistered::from) - .map(|m| m as i32), - account_summary: state.account_summary.map(crate::AccountSummary::from), - device: state.device.map(crate::DeviceState::from).map(|m| m as i32), - register_device_result: state - .register_device_result - .map(crate::RegisterDeviceResult::from), - request_zk_nym_result: state - .request_zk_nym_result - .map(crate::RequestZkNymResult::from), - } - } -} - -impl From for crate::AccountUsage { - fn from(usage: nym_vpn_api_client::response::NymVpnUsage) -> Self { - Self { - created_on_utc: usage.created_on_utc, - last_updated_utc: usage.last_updated_utc, - id: usage.id, - subscription_id: usage.subscription_id, - valid_until_utc: usage.valid_until_utc, - valid_from_utc: usage.valid_from_utc, - bandwidth_allowance_gb: usage.bandwidth_allowance_gb, - bandwidth_used_gb: usage.bandwidth_used_gb, - } - } -} - -impl From> for crate::AccountUsages { - fn from(usage: Vec) -> Self { - Self { - account_usages: usage.into_iter().map(crate::AccountUsage::from).collect(), - } + }; + Self { outcome } } } -impl From for crate::DeviceStatus { - fn from(value: nym_vpn_api_client::response::NymVpnDeviceStatus) -> Self { - match value { - nym_vpn_api_client::response::NymVpnDeviceStatus::Active => Self::Active, - nym_vpn_api_client::response::NymVpnDeviceStatus::Inactive => Self::Inactive, - nym_vpn_api_client::response::NymVpnDeviceStatus::DeleteMe => Self::DeleteMe, - } - } -} - -impl From for crate::Device { - fn from(device: nym_vpn_api_client::response::NymVpnDevice) -> Self { - Self { - created_on_utc: device.created_on_utc, - last_updated_utc: device.last_updated_utc, - device_identity_key: device.device_identity_key, - status: crate::DeviceStatus::from(device.status) as i32, - } - } -} - -impl From> for crate::Devices { - fn from(devices: Vec) -> Self { - Self { - devices: devices.into_iter().map(crate::Device::from).collect(), - } +impl From for ProtoRequestZkNymError { + fn from(error: RequestZkNymError) -> Self { + RequestZkNymErrorReason::from(error).into() } } diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs new file mode 100644 index 0000000000..415d5c3721 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs @@ -0,0 +1,178 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_vpn_account_controller::{ + shared_state::{ + AccountRegistered, AccountState, AccountSummary, DeviceState, DeviceSummary, MnemonicState, + RegisterDeviceResult, RequestZkNymResult, SubscriptionState, + }, + AccountStateSummary, +}; +use nym_vpn_lib_types::RegisterDeviceError; + +use crate::{ + get_account_state_response::{ + account_state_summary::{ + account_summary::{ + AccountState as ProtoAccountState, DeviceSummary as ProtoDeviceSummary, + SubscriptionState as ProtoSubscriptionState, + }, + AccountRegistered as ProtoAccountRegistered, AccountSummary as ProtoAccountSummary, + DeviceState as ProtoDeviceState, MnemonicState as ProtoMnemonicState, + }, + AccountStateSummary as ProtoAccountStateSummary, + }, + RegisterDeviceResult as ProtoRegisterDeviceResult, RequestZkNymError as ProtoRequestZkNymError, + RequestZkNymResult as ProtoRequestZkNymResult, RequestZkNymSuccess as ProtoRequestZkNymSuccess, +}; + +impl From for ProtoMnemonicState { + fn from(mnemonic: MnemonicState) -> Self { + match mnemonic { + MnemonicState::Stored { .. } => Self::Stored, + MnemonicState::NotStored => Self::NotStored, + } + } +} + +impl From for ProtoAccountRegistered { + fn from(account_registered: AccountRegistered) -> Self { + match account_registered { + AccountRegistered::Registered => Self::AccountRegistered, + AccountRegistered::NotRegistered => Self::AccountNotRegistered, + } + } +} + +impl From for ProtoAccountState { + fn from(account: AccountState) -> Self { + match account { + AccountState::Inactive => Self::Inactive, + AccountState::Active => Self::Active, + AccountState::DeleteMe => Self::DeleteMe, + } + } +} + +impl From for ProtoSubscriptionState { + fn from(subscription: SubscriptionState) -> Self { + match subscription { + SubscriptionState::NotActive => Self::NotRegistered, + SubscriptionState::Pending => Self::Pending, + SubscriptionState::Active => Self::Active, + SubscriptionState::Complete => Self::Complete, + } + } +} + +impl From for ProtoDeviceSummary { + fn from(device_summary: DeviceSummary) -> Self { + Self { + active: device_summary.active, + max: device_summary.max, + remaining: device_summary.remaining, + } + } +} + +impl From for ProtoAccountSummary { + fn from(account_summary: AccountSummary) -> Self { + Self { + account: ProtoAccountState::from(account_summary.account) as i32, + subscription: ProtoSubscriptionState::from(account_summary.subscription) as i32, + device_summary: Some(ProtoDeviceSummary::from(account_summary.device_summary)), + } + } +} + +impl From for ProtoDeviceState { + fn from(device: DeviceState) -> Self { + match device { + DeviceState::NotRegistered => Self::NotRegistered, + DeviceState::Inactive => Self::Inactive, + DeviceState::Active => Self::Active, + DeviceState::DeleteMe => Self::DeleteMe, + } + } +} + +impl From for ProtoRegisterDeviceResult { + fn from(device_registration: RegisterDeviceResult) -> Self { + match device_registration { + RegisterDeviceResult::InProgress => Self { + kind: crate::register_device_result::RegisterDeviceResultType::InProgress as i32, + ..Default::default() + }, + RegisterDeviceResult::Success => Self { + kind: crate::register_device_result::RegisterDeviceResultType::Success as i32, + ..Default::default() + }, + RegisterDeviceResult::Failed(err) => match err { + RegisterDeviceError::RegisterDeviceEndpointFailure(err) => Self { + kind: crate::register_device_result::RegisterDeviceResultType::Failed as i32, + message: Some(err.message), + message_id: err.message_id, + }, + RegisterDeviceError::UnexpectedResponse(err) => Self { + kind: crate::register_device_result::RegisterDeviceResultType::Failed as i32, + message: Some(err), + message_id: None, + }, + }, + } + } +} + +impl From for ProtoRequestZkNymResult { + fn from(zk_nym_request: RequestZkNymResult) -> Self { + match zk_nym_request { + RequestZkNymResult::InProgress => Self { + kind: crate::request_zk_nym_result::RequestZkNymResultType::InProgress as i32, + successes: Default::default(), + failures: Default::default(), + }, + RequestZkNymResult::Done { + successes, + failures, + } => Self { + kind: crate::request_zk_nym_result::RequestZkNymResultType::Done as i32, + successes: successes + .into_iter() + .map(ProtoRequestZkNymSuccess::from) + .collect(), + failures: failures + .into_iter() + .map(ProtoRequestZkNymError::from) + .collect(), + }, + RequestZkNymResult::Error(e) => Self { + kind: crate::request_zk_nym_result::RequestZkNymResultType::Error as i32, + successes: Default::default(), + failures: vec![ProtoRequestZkNymError::from(e)], + }, + } + } +} + +impl From for ProtoAccountStateSummary { + fn from(state: AccountStateSummary) -> Self { + Self { + mnemonic: state + .mnemonic + .map(ProtoMnemonicState::from) + .map(|m| m as i32), + account_registered: state + .account_registered + .map(ProtoAccountRegistered::from) + .map(|m| m as i32), + account_summary: state.account_summary.map(ProtoAccountSummary::from), + device: state.device.map(ProtoDeviceState::from).map(|m| m as i32), + register_device_result: state + .register_device_result + .map(ProtoRegisterDeviceResult::from), + request_zk_nym_result: state + .request_zk_nym_result + .map(ProtoRequestZkNymResult::from), + } + } +} diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/mod.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/mod.rs index a87f429c60..e0b509de12 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/mod.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod account; +pub mod account_shared_state; pub mod network_config; pub mod tunnel_event; pub mod tunnel_state; +pub mod vpn_api_client; pub mod vpnd; impl From for crate::Url { diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs index 1f1dd3c9c4..c625ff5a2c 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_vpn_lib_types::{ - ActionAfterDisconnect, ConnectionData, ErrorStateReason, Gateway, MixnetConnectionData, - TunnelConnectionData, TunnelState, WireguardConnectionData, WireguardNode, + ActionAfterDisconnect, ConnectionData, ErrorStateReason, ForgetAccountError, Gateway, + MixnetConnectionData, RegisterDeviceError, StoreAccountError, SyncAccountError, + SyncDeviceError, TunnelConnectionData, TunnelState, VpnApiErrorResponse, + WireguardConnectionData, WireguardNode, }; use crate::{ @@ -12,14 +14,22 @@ use crate::{ Wireguard as ProtoWireguardConnectionDataVariant, }, tunnel_state::{ - ActionAfterDisconnect as ProtoActionAfterDisconnect, Connected as ProtoConnected, + error::ErrorStateReason as ProtoErrorStateReason, + ActionAfterDisconnect as ProtoActionAfterDisconnect, + BaseErrorStateReason as ProtoBaseErrorStateReason, Connected as ProtoConnected, Connecting as ProtoConnecting, Disconnected as ProtoDisconnected, - Disconnecting as ProtoDisconnecting, Error as ProtoError, - ErrorStateReason as ProtoErrorStateReason, Offline as ProtoOffline, State as ProtoState, + Disconnecting as ProtoDisconnecting, Error as ProtoError, Offline as ProtoOffline, + State as ProtoState, }, - Address as ProtoAddress, ConnectionData as ProtoConnectionData, Gateway as ProtoGateway, + Address as ProtoAddress, ConnectionData as ProtoConnectionData, + ForgetAccountError as ProtoForgetAccountError, Gateway as ProtoGateway, + GeneralAccountError as ProtoGeneralAccountError, MixnetConnectionData as ProtoMixnetConnectionData, - TunnelConnectionData as ProtoTunnelConnectionData, TunnelState as ProtoTunnelState, + RegisterDeviceError as ProtoRegisterDeviceError, RequestZkNymBundle as ProtoRequestZkNymBundle, + RequestZkNymError as ProtoRequestZkNymError, RequestZkNymSuccess as ProtoRequestZkNymSuccess, + StoreAccountError as ProtoStoreAccountError, SyncAccountError as ProtoSyncAccountError, + SyncDeviceError as ProtoSyncDeviceError, TunnelConnectionData as ProtoTunnelConnectionData, + TunnelState as ProtoTunnelState, VpnApiErrorResponse as ProtoVpnApiErrorResponse, WireguardConnectionData as ProtoWireguardConnectionData, WireguardNode as ProtoWireguardNode, }; @@ -37,17 +47,216 @@ impl From for ProtoActionAfterDisconnect { impl From for ProtoErrorStateReason { fn from(value: ErrorStateReason) -> Self { match value { - ErrorStateReason::Firewall => Self::Firewall, - ErrorStateReason::Routing => Self::Routing, - ErrorStateReason::Dns => Self::Dns, - ErrorStateReason::TunDevice => Self::TunDevice, - ErrorStateReason::TunnelProvider => Self::TunnelProvider, - ErrorStateReason::SameEntryAndExitGateway => Self::SameEntryAndExitGateway, - ErrorStateReason::InvalidEntryGatewayCountry => Self::InvalidEntryGatewayCountry, - ErrorStateReason::InvalidExitGatewayCountry => Self::InvalidExitGatewayCountry, - ErrorStateReason::BadBandwidthIncrease => Self::BadBandwidthIncrease, - ErrorStateReason::DuplicateTunFd => Self::DuplicateTunFd, - ErrorStateReason::Internal => Self::Internal, + ErrorStateReason::Firewall => { + Self::BaseReason(ProtoBaseErrorStateReason::Firewall as i32) + } + ErrorStateReason::Routing => { + Self::BaseReason(ProtoBaseErrorStateReason::Routing as i32) + } + ErrorStateReason::Dns => Self::BaseReason(ProtoBaseErrorStateReason::Dns as i32), + ErrorStateReason::TunDevice => { + Self::BaseReason(ProtoBaseErrorStateReason::TunDevice as i32) + } + ErrorStateReason::TunnelProvider => { + Self::BaseReason(ProtoBaseErrorStateReason::TunnelProvider as i32) + } + ErrorStateReason::SameEntryAndExitGateway => { + Self::BaseReason(ProtoBaseErrorStateReason::SameEntryAndExitGateway as i32) + } + ErrorStateReason::InvalidEntryGatewayCountry => { + Self::BaseReason(ProtoBaseErrorStateReason::InvalidEntryGatewayCountry as i32) + } + ErrorStateReason::InvalidExitGatewayCountry => { + Self::BaseReason(ProtoBaseErrorStateReason::InvalidExitGatewayCountry as i32) + } + ErrorStateReason::BadBandwidthIncrease => { + Self::BaseReason(ProtoBaseErrorStateReason::BadBandwidthIncrease as i32) + } + ErrorStateReason::DuplicateTunFd => { + Self::BaseReason(ProtoBaseErrorStateReason::DuplicateTunFd as i32) + } + ErrorStateReason::Internal => { + Self::BaseReason(ProtoBaseErrorStateReason::Internal as i32) + } + ErrorStateReason::Account(reason) => Self::GeneralAccount(reason.into()), + ErrorStateReason::NoAccountStored => { + Self::BaseReason(ProtoBaseErrorStateReason::NoAccountStored as i32) + } + ErrorStateReason::NoDeviceStored => { + Self::BaseReason(ProtoBaseErrorStateReason::NoDeviceStored as i32) + } + ErrorStateReason::StoreAccount(store_account_error) => { + Self::StoreAccount(store_account_error.into()) + } + ErrorStateReason::SyncAccount(sync_account_error) => { + Self::SyncAccount(sync_account_error.into()) + } + ErrorStateReason::SyncDevice(sync_device_error) => { + Self::SyncDevice(sync_device_error.into()) + } + ErrorStateReason::RegisterDevice(register_device_error) => { + Self::RegisterDevice(register_device_error.into()) + } + ErrorStateReason::RequestZkNym(request_zk_nym_error) => { + Self::RequestZkNym(request_zk_nym_error.into()) + } + ErrorStateReason::RequestZkNymBundle { successes, failed } => { + Self::RequestZkNymBundle(ProtoRequestZkNymBundle { + successes: successes + .into_iter() + .map(ProtoRequestZkNymSuccess::from) + .collect(), + failures: failed + .into_iter() + .map(ProtoRequestZkNymError::from) + .collect(), + }) + } + ErrorStateReason::ForgetAccount(forget_account_error) => { + Self::ForgetAccount(forget_account_error.into()) + } + } + } +} + +impl From for ProtoGeneralAccountError { + fn from(value: String) -> Self { + Self { + error_message: value, + } + } +} + +impl From for ProtoStoreAccountError { + fn from(value: StoreAccountError) -> Self { + match value { + StoreAccountError::Storage(err) => ProtoStoreAccountError { + error_detail: Some(crate::store_account_error::ErrorDetail::StorageError(err)), + }, + StoreAccountError::GetAccountEndpointFailure(vpn_api_endpoint_failure) => { + ProtoStoreAccountError { + error_detail: Some(crate::store_account_error::ErrorDetail::ErrorResponse( + vpn_api_endpoint_failure.into(), + )), + } + } + StoreAccountError::UnexpectedResponse(err) => ProtoStoreAccountError { + error_detail: Some(crate::store_account_error::ErrorDetail::UnexpectedResponse( + err, + )), + }, + } + } +} + +impl From for ProtoSyncAccountError { + fn from(value: SyncAccountError) -> Self { + match value { + SyncAccountError::SyncAccountEndpointFailure(vpn_api_endpoint_failure) => { + ProtoSyncAccountError { + error_detail: Some(crate::sync_account_error::ErrorDetail::ErrorResponse( + vpn_api_endpoint_failure.into(), + )), + } + } + SyncAccountError::UnexpectedResponse(err) => ProtoSyncAccountError { + error_detail: Some(crate::sync_account_error::ErrorDetail::UnexpectedResponse( + err, + )), + }, + } + } +} + +impl From for ProtoSyncDeviceError { + fn from(value: SyncDeviceError) -> Self { + match value { + SyncDeviceError::SyncDeviceEndpointFailure(vpn_api_endpoint_failure) => { + ProtoSyncDeviceError { + error_detail: Some(crate::sync_device_error::ErrorDetail::ErrorResponse( + vpn_api_endpoint_failure.into(), + )), + } + } + SyncDeviceError::UnexpectedResponse(err) => ProtoSyncDeviceError { + error_detail: Some(crate::sync_device_error::ErrorDetail::UnexpectedResponse( + err, + )), + }, + } + } +} + +impl From for ProtoRegisterDeviceError { + fn from(value: RegisterDeviceError) -> Self { + match value { + RegisterDeviceError::RegisterDeviceEndpointFailure(vpn_api_endpoint_failure) => { + ProtoRegisterDeviceError { + error_detail: Some(crate::register_device_error::ErrorDetail::ErrorResponse( + vpn_api_endpoint_failure.into(), + )), + } + } + RegisterDeviceError::UnexpectedResponse(err) => ProtoRegisterDeviceError { + error_detail: Some( + crate::register_device_error::ErrorDetail::UnexpectedResponse(err), + ), + }, + } + } +} + +impl From for ProtoForgetAccountError { + fn from(value: ForgetAccountError) -> Self { + match value { + ForgetAccountError::RegistrationInProgress => Self { + error_detail: Some( + crate::forget_account_error::ErrorDetail::RegistrationInProgress(true), + ), + }, + ForgetAccountError::UpdateDeviceErrorResponse(vpn_api_endpoint_failure) => Self { + error_detail: Some(crate::forget_account_error::ErrorDetail::ErrorResponse( + vpn_api_endpoint_failure.into(), + )), + }, + ForgetAccountError::UnexpectedResponse(err) => Self { + error_detail: Some( + crate::forget_account_error::ErrorDetail::UnexpectedResponse(err), + ), + }, + ForgetAccountError::RemoveAccount(err) => Self { + error_detail: Some(crate::forget_account_error::ErrorDetail::RemoveAccount(err)), + }, + ForgetAccountError::RemoveDeviceKeys(err) => Self { + error_detail: Some(crate::forget_account_error::ErrorDetail::RemoveDeviceKeys( + err, + )), + }, + ForgetAccountError::ResetCredentialStorage(err) => Self { + error_detail: Some( + crate::forget_account_error::ErrorDetail::ResetCredentialStore(err), + ), + }, + ForgetAccountError::RemoveAccountFiles(err) => Self { + error_detail: Some( + crate::forget_account_error::ErrorDetail::RemoveAccountFiles(err), + ), + }, + ForgetAccountError::InitDeviceKeys(err) => Self { + error_detail: Some(crate::forget_account_error::ErrorDetail::InitDeviceKeys( + err, + )), + }, + } + } +} + +impl From for ProtoVpnApiErrorResponse { + fn from(value: VpnApiErrorResponse) -> Self { + Self { + message: value.message, + message_id: value.message_id, + code_reference_id: value.code_reference_id, } } } @@ -71,7 +280,7 @@ impl From for ProtoTunnelState { } TunnelState::Offline { reconnect } => ProtoState::Offline(ProtoOffline { reconnect }), TunnelState::Error(reason) => ProtoState::Error(ProtoError { - reason: ProtoErrorStateReason::from(reason) as i32, + error_state_reason: Some(ProtoErrorStateReason::from(reason)), }), }; diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/vpn_api_client.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/vpn_api_client.rs new file mode 100644 index 0000000000..3b17ca7da4 --- /dev/null +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/vpn_api_client.rs @@ -0,0 +1,65 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_vpn_api_client::response::{NymVpnDevice, NymVpnDeviceStatus, NymVpnUsage}; + +use crate::{ + get_account_usage_response::{ + AccountUsage as ProtoAccountUsage, AccountUsages as ProtoAccountUsages, + }, + get_devices_response::{ + device::DeviceStatus as ProtoDeviceStatus, Device as ProtoDevice, Devices as ProtoDevices, + }, +}; + +impl From for ProtoAccountUsage { + fn from(usage: NymVpnUsage) -> Self { + Self { + created_on_utc: usage.created_on_utc, + last_updated_utc: usage.last_updated_utc, + id: usage.id, + subscription_id: usage.subscription_id, + valid_until_utc: usage.valid_until_utc, + valid_from_utc: usage.valid_from_utc, + bandwidth_allowance_gb: usage.bandwidth_allowance_gb, + bandwidth_used_gb: usage.bandwidth_used_gb, + } + } +} + +impl From> for ProtoAccountUsages { + fn from(usage: Vec) -> Self { + Self { + account_usages: usage.into_iter().map(ProtoAccountUsage::from).collect(), + } + } +} + +impl From for ProtoDeviceStatus { + fn from(value: NymVpnDeviceStatus) -> Self { + match value { + NymVpnDeviceStatus::Active => Self::Active, + NymVpnDeviceStatus::Inactive => Self::Inactive, + NymVpnDeviceStatus::DeleteMe => Self::DeleteMe, + } + } +} + +impl From for ProtoDevice { + fn from(device: NymVpnDevice) -> Self { + Self { + created_on_utc: device.created_on_utc, + last_updated_utc: device.last_updated_utc, + device_identity_key: device.device_identity_key, + status: ProtoDeviceStatus::from(device.status) as i32, + } + } +} + +impl From> for ProtoDevices { + fn from(devices: Vec) -> Self { + Self { + devices: devices.into_iter().map(ProtoDevice::from).collect(), + } + } +} diff --git a/nym-vpn-core/crates/nym-vpnc/src/cli.rs b/nym-vpn-core/crates/nym-vpnc/src/cli.rs index 98f541bd84..ddee016994 100644 --- a/nym-vpn-core/crates/nym-vpnc/src/cli.rs +++ b/nym-vpn-core/crates/nym-vpnc/src/cli.rs @@ -125,9 +125,6 @@ pub enum Internal { /// Get the account usage from the nym-vpn-api. GetAccountUsage, - /// Evaluate the current state of the device and determine if it is ready to connect. - IsReadyToConnect, - /// Manually reset the device identity. A seed can be provided as a way to generate a stable /// identity for testing. ResetDeviceIdentity(ResetDeviceIdentityArgs), diff --git a/nym-vpn-core/crates/nym-vpnc/src/main.rs b/nym-vpn-core/crates/nym-vpnc/src/main.rs index f8ce8a7cd0..62cc41efca 100644 --- a/nym-vpn-core/crates/nym-vpnc/src/main.rs +++ b/nym-vpn-core/crates/nym-vpnc/src/main.rs @@ -84,7 +84,6 @@ async fn main() -> Result<()> { Internal::GetFeatureFlags => get_feature_flags(opts.client_type).await?, Internal::SyncAccountState => refresh_account_state(opts.client_type).await?, Internal::GetAccountUsage => get_account_usage(opts.client_type).await?, - Internal::IsReadyToConnect => is_ready_to_connect(opts.client_type).await?, Internal::ResetDeviceIdentity(ref args) => { reset_device_identity(opts.client_type, args).await? } @@ -192,13 +191,6 @@ fn handle_connect_failure(error: nym_vpn_proto::ConnectRequestError) -> Result<( let kind = nym_vpn_proto::connect_request_error::ConnectRequestErrorType::try_from(error.kind) .context("failed to parse connect request error kind")?; println!("Connect command failed: {} (id={kind:?})", error.message); - for zk_nym_error in error.zk_nym_error { - println!( - " zk nym error ({}): {}", - zk_nym_error.ticketbook_type(), - zk_nym_error.message(), - ); - } Ok(()) } @@ -415,13 +407,6 @@ async fn get_account_state(client_type: ClientType) -> Result<()> { Ok(()) } -async fn is_ready_to_connect(client_type: ClientType) -> Result<()> { - let mut client = vpnd_client::get_client(&client_type).await?; - let response = client.is_ready_to_connect(()).await?.into_inner(); - println!("{:#?}", response); - Ok(()) -} - async fn reset_device_identity( client_type: ClientType, args: &cli::ResetDeviceIdentityArgs, diff --git a/nym-vpn-core/crates/nym-vpnd/src/command_interface/connection_handler.rs b/nym-vpn-core/crates/nym-vpnd/src/command_interface/connection_handler.rs index 9be1b3b583..6700377884 100644 --- a/nym-vpn-core/crates/nym-vpnd/src/command_interface/connection_handler.rs +++ b/nym-vpn-core/crates/nym-vpnd/src/command_interface/connection_handler.rs @@ -4,7 +4,7 @@ use tokio::sync::{mpsc::UnboundedSender, oneshot, watch}; use zeroize::Zeroizing; -use nym_vpn_account_controller::{AccountStateSummary, AvailableTicketbooks, ReadyToConnect}; +use nym_vpn_account_controller::{AccountStateSummary, AvailableTicketbooks}; use nym_vpn_api_client::{ response::{NymVpnDevice, NymVpnUsage}, types::GatewayMinPerformance, @@ -197,13 +197,6 @@ impl CommandInterfaceConnectionHandler { .await } - pub(crate) async fn handle_is_ready_to_connect( - &self, - ) -> Result, VpnCommandSendError> { - self.send_and_wait(VpnServiceCommand::IsReadyToConnect, ()) - .await - } - pub(crate) async fn handle_reset_device_identity( &self, seed: Option<[u8; 32]>, diff --git a/nym-vpn-core/crates/nym-vpnd/src/command_interface/listener.rs b/nym-vpn-core/crates/nym-vpnd/src/command_interface/listener.rs index 79493b1240..5f42df688f 100644 --- a/nym-vpn-core/crates/nym-vpnd/src/command_interface/listener.rs +++ b/nym-vpn-core/crates/nym-vpnd/src/command_interface/listener.rs @@ -17,11 +17,10 @@ use nym_vpn_proto::{ ConfirmZkNymDownloadedRequest, ConfirmZkNymDownloadedResponse, ConnectRequest, ConnectResponse, DisconnectResponse, ForgetAccountResponse, GetAccountIdentityResponse, GetAccountLinksRequest, GetAccountLinksResponse, GetAccountStateResponse, GetAccountUsageResponse, - GetActiveDevicesResponse, GetAvailableTicketsResponse, GetDeviceIdentityResponse, - GetDeviceZkNymsResponse, GetDevicesResponse, GetFeatureFlagsResponse, - GetSystemMessagesResponse, GetZkNymByIdRequest, GetZkNymByIdResponse, - GetZkNymsAvailableForDownloadResponse, InfoResponse, IsAccountStoredResponse, - IsReadyToConnectResponse, ListCountriesRequest, ListCountriesResponse, ListGatewaysRequest, + GetAvailableTicketsResponse, GetDeviceIdentityResponse, GetDeviceZkNymsResponse, + GetDevicesResponse, GetFeatureFlagsResponse, GetSystemMessagesResponse, GetZkNymByIdRequest, + GetZkNymByIdResponse, GetZkNymsAvailableForDownloadResponse, InfoResponse, + IsAccountStoredResponse, ListCountriesRequest, ListCountriesResponse, ListGatewaysRequest, ListGatewaysResponse, RefreshAccountStateResponse, RegisterDeviceResponse, RequestZkNymResponse, ResetDeviceIdentityRequest, ResetDeviceIdentityResponse, SetNetworkRequest, SetNetworkResponse, StoreAccountRequest, StoreAccountResponse, TunnelState, @@ -532,7 +531,7 @@ impl NymVpnd for CommandInterface { let response = match result { Ok(state) => GetAccountStateResponse { result: Some(nym_vpn_proto::get_account_state_response::Result::Account( - nym_vpn_proto::AccountStateSummary::from(state), + nym_vpn_proto::get_account_state_response::AccountStateSummary::from(state), )), }, Err(err) => { @@ -574,7 +573,7 @@ impl NymVpnd for CommandInterface { Ok(usage) => GetAccountUsageResponse { result: Some( nym_vpn_proto::get_account_usage_response::Result::AccountUsages( - nym_vpn_proto::AccountUsages::from(usage), + nym_vpn_proto::get_account_usage_response::AccountUsages::from(usage), ), ), }, @@ -588,28 +587,6 @@ impl NymVpnd for CommandInterface { Ok(tonic::Response::new(response)) } - async fn is_ready_to_connect( - &self, - _request: tonic::Request<()>, - ) -> Result, tonic::Status> { - let result = CommandInterfaceConnectionHandler::new(self.vpn_command_tx.clone()) - .handle_is_ready_to_connect() - .await?; - - let response = match result { - Ok(ready) => IsReadyToConnectResponse::from(ready), - Err(err) => { - // TODO: consider proper error handling for AccountError in this context - tracing::error!("Failed to check if ready to connect: {:?}", err); - return Err(tonic::Status::internal( - "Failed to check if ready to connect", - )); - } - }; - - tracing::debug!("Returning is ready to connect response"); - Ok(tonic::Response::new(response)) - } async fn reset_device_identity( &self, request: tonic::Request, @@ -691,7 +668,7 @@ impl NymVpnd for CommandInterface { .await? .map(|devices| GetDevicesResponse { result: Some(nym_vpn_proto::get_devices_response::Result::Devices( - nym_vpn_proto::Devices::from(devices), + nym_vpn_proto::get_devices_response::Devices::from(devices), )), }) .unwrap_or_else(|err| GetDevicesResponse { @@ -705,17 +682,17 @@ impl NymVpnd for CommandInterface { async fn get_active_devices( &self, _request: tonic::Request<()>, - ) -> Result, tonic::Status> { + ) -> Result, tonic::Status> { let response = CommandInterfaceConnectionHandler::new(self.vpn_command_tx.clone()) .handle_get_active_devices() .await? - .map(|devices| GetActiveDevicesResponse { - result: Some(nym_vpn_proto::get_active_devices_response::Result::Devices( - nym_vpn_proto::Devices::from(devices), + .map(|devices| GetDevicesResponse { + result: Some(nym_vpn_proto::get_devices_response::Result::Devices( + nym_vpn_proto::get_devices_response::Devices::from(devices), )), }) - .unwrap_or_else(|err| GetActiveDevicesResponse { - result: Some(nym_vpn_proto::get_active_devices_response::Result::Error( + .unwrap_or_else(|err| GetDevicesResponse { + result: Some(nym_vpn_proto::get_devices_response::Result::Error( nym_vpn_proto::AccountError::from(err), )), }); diff --git a/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/error.rs b/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/error.rs index 8b512b5b73..cb09d8e52b 100644 --- a/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/error.rs +++ b/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/error.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::service::{AccountNotReady, SetNetworkError, VpnServiceConnectError}; +use crate::service::{SetNetworkError, VpnServiceConnectError}; impl From for nym_vpn_proto::ConnectRequestError { fn from(err: VpnServiceConnectError) -> Self { @@ -11,102 +11,12 @@ impl From for nym_vpn_proto::ConnectRequestError { kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::Internal as i32, message: err.to_string(), - message_id: None, - zk_nym_error: Vec::new(), } } - VpnServiceConnectError::Account(not_ready_to_connect) => { - nym_vpn_proto::ConnectRequestError::from(not_ready_to_connect) - } VpnServiceConnectError::Cancel => nym_vpn_proto::ConnectRequestError { kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::Internal as i32, message: err.to_string(), - message_id: None, - zk_nym_error: Vec::new(), - }, - } - } -} - -impl From for nym_vpn_proto::ConnectRequestError { - fn from(error: AccountNotReady) -> Self { - match error { - AccountNotReady::UpdateAccount { - message, - message_id, - code_reference_id: _, - } => Self { - kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::UpdateAccount - as i32, - message: message.clone(), - message_id: message_id.clone(), - zk_nym_error: Vec::new(), - }, - AccountNotReady::UpdateDevice { - message, - message_id, - code_reference_id: _, - } => Self { - kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::UpdateDevice - as i32, - message: message.clone(), - message_id: message_id.clone(), - zk_nym_error: Vec::new(), - }, - AccountNotReady::RegisterDevice { - message, - message_id, - code_reference_id: _, - } => Self { - kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::RegisterDevice - as i32, - message: message.clone(), - message_id: message_id.clone(), - zk_nym_error: Vec::new(), - }, - AccountNotReady::RequestZkNym { ref failed } => { - let zk_nym_error = failed - .clone() - .into_iter() - .map(nym_vpn_proto::RequestZkNymError::from) - .collect(); - Self { - kind: - nym_vpn_proto::connect_request_error::ConnectRequestErrorType::RequestZkNym - as i32, - message: error.to_string(), - message_id: None, - zk_nym_error, - } - } - AccountNotReady::NoAccountStored => Self { - kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::NoAccountStored - as i32, - message: error.to_string(), - message_id: None, - zk_nym_error: Vec::new(), - }, - AccountNotReady::NoDeviceStored => Self { - kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::NoDeviceStored - as i32, - message: error.to_string(), - message_id: None, - zk_nym_error: Vec::new(), - }, - AccountNotReady::General(_) => Self { - kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::Internal - as i32, - message: error.to_string(), - message_id: None, - zk_nym_error: Vec::new(), - }, - AccountNotReady::Internal(_) => Self { - kind: nym_vpn_proto::connect_request_error::ConnectRequestErrorType::Internal - as i32, - message: error.to_string(), - message_id: None, - zk_nym_error: Vec::new(), }, } } diff --git a/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/info_response.rs b/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/info_response.rs index 5c44402690..59e5834a63 100644 --- a/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/info_response.rs +++ b/nym-vpn-core/crates/nym-vpnd/src/command_interface/protobuf/info_response.rs @@ -32,8 +32,8 @@ impl From for nym_vpn_proto::InfoResponse { pub(crate) fn into_proto_available_tickets( ticketbooks: nym_vpn_account_controller::AvailableTicketbooks, -) -> nym_vpn_proto::AvailableTickets { - nym_vpn_proto::AvailableTickets { +) -> nym_vpn_proto::get_available_tickets_response::AvailableTickets { + nym_vpn_proto::get_available_tickets_response::AvailableTickets { mixnet_entry_tickets: ticketbooks.remaining_tickets(TicketType::V1MixnetEntry), mixnet_entry_data: ticketbooks.remaining_data(TicketType::V1MixnetEntry), mixnet_entry_data_si: ticketbooks.remaining_data_si(TicketType::V1MixnetEntry), diff --git a/nym-vpn-core/crates/nym-vpnd/src/service/error.rs b/nym-vpn-core/crates/nym-vpnd/src/service/error.rs index c519f59547..71afa0e064 100644 --- a/nym-vpn-core/crates/nym-vpnd/src/service/error.rs +++ b/nym-vpn-core/crates/nym-vpnd/src/service/error.rs @@ -1,8 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_vpn_account_controller::AccountCommandError; use nym_vpn_lib::tunnel_state_machine::Error as TunnelStateMachineError; +use nym_vpn_lib_types::AccountCommandError; use tokio::sync::{mpsc::error::SendError, oneshot::error::RecvError}; use tracing::error; @@ -14,104 +14,10 @@ pub enum VpnServiceConnectError { #[error("internal error: {0}")] Internal(String), - #[error("failed to connect: {0}")] - Account(#[from] AccountNotReady), - #[error("connection attempt cancelled")] Cancel, } -#[derive(Clone, Debug, thiserror::Error)] -pub enum AccountNotReady { - #[error("update account failed: {message}")] - UpdateAccount { - message: String, - message_id: Option, - code_reference_id: Option, - }, - - #[error("update device failed: {message}")] - UpdateDevice { - message: String, - message_id: Option, - code_reference_id: Option, - }, - - #[error("register device failed: {message}")] - RegisterDevice { - message: String, - message_id: Option, - code_reference_id: Option, - }, - - #[error("no account stored")] - NoAccountStored, - - #[error("no device identity stored")] - NoDeviceStored, - - // There are usually multiple independent zknym requests at a time - #[error("failed to request zk-nym(s)")] - RequestZkNym { - failed: Vec, - }, - - #[error("general error: {0}")] - General(String), - - #[error("internal error: {0}")] - Internal(String), -} - -impl From for AccountNotReady { - fn from(err: AccountCommandError) -> Self { - match err { - AccountCommandError::SyncAccountEndpointFailure(e) => AccountNotReady::UpdateAccount { - message: e.message, - message_id: e.message_id, - code_reference_id: e.code_reference_id, - }, - AccountCommandError::SyncDeviceEndpointFailure(e) => AccountNotReady::UpdateDevice { - message: e.message, - message_id: e.message_id, - code_reference_id: e.code_reference_id, - }, - AccountCommandError::RegisterDeviceEndpointFailure(e) => { - AccountNotReady::RegisterDevice { - message: e.message, - message_id: e.message_id, - code_reference_id: e.code_reference_id, - } - } - AccountCommandError::RequestZkNym { - successes: _, - failed, - } => AccountNotReady::RequestZkNym { failed }, - AccountCommandError::RequestZkNymGeneral(e) => { - AccountNotReady::RequestZkNym { failed: vec![e] } - } - AccountCommandError::NoAccountStored => AccountNotReady::NoAccountStored, - AccountCommandError::NoDeviceStored => AccountNotReady::NoDeviceStored, - AccountCommandError::RemoveAccount(e) => AccountNotReady::General(e), - AccountCommandError::RemoveDeviceIdentity(e) => AccountNotReady::General(e), - AccountCommandError::ResetCredentialStorage(e) => AccountNotReady::General(e), - AccountCommandError::RemoveAccountFiles(e) => AccountNotReady::General(e), - AccountCommandError::InitDeviceKeys(e) => AccountNotReady::General(e), - AccountCommandError::General(err) => AccountNotReady::General(err), - AccountCommandError::Internal(err) => AccountNotReady::Internal(err), - AccountCommandError::UnregisterDeviceApiClientFailure(err) => { - AccountNotReady::Internal(err) - } - AccountCommandError::RegistrationInProgress => { - AccountNotReady::Internal(err.to_string()) - } - AccountCommandError::GetAccountEndpointFailure(err) => { - AccountNotReady::Internal(err.to_string()) - } - } - } -} - // Failure to initiate the disconnect #[derive(Clone, Debug, thiserror::Error)] pub enum VpnServiceDisconnectError { @@ -196,9 +102,7 @@ pub enum AccountError { }, #[error(transparent)] - AccountCommandError { - source: nym_vpn_account_controller::AccountCommandError, - }, + AccountCommandError { source: AccountCommandError }, #[error("account not configured")] AccountManagementNotConfigured, diff --git a/nym-vpn-core/crates/nym-vpnd/src/service/mod.rs b/nym-vpn-core/crates/nym-vpnd/src/service/mod.rs index 7935fb9f01..97b65e3783 100644 --- a/nym-vpn-core/crates/nym-vpnd/src/service/mod.rs +++ b/nym-vpn-core/crates/nym-vpnd/src/service/mod.rs @@ -10,8 +10,7 @@ pub(crate) use config::{ DEFAULT_GLOBAL_CONFIG_FILE, DEFAULT_LOG_FILE, }; pub(crate) use error::{ - AccountError, AccountNotReady, SetNetworkError, VpnServiceConnectError, - VpnServiceDisconnectError, + AccountError, SetNetworkError, VpnServiceConnectError, VpnServiceDisconnectError, }; pub(crate) use vpn_service::{ ConnectArgs, ConnectOptions, NymVpnService, VpnServiceCommand, VpnServiceInfo, diff --git a/nym-vpn-core/crates/nym-vpnd/src/service/vpn_service.rs b/nym-vpn-core/crates/nym-vpnd/src/service/vpn_service.rs index aca38a3cb8..3894ec2c54 100644 --- a/nym-vpn-core/crates/nym-vpnd/src/service/vpn_service.rs +++ b/nym-vpn-core/crates/nym-vpnd/src/service/vpn_service.rs @@ -4,7 +4,6 @@ use std::{net::IpAddr, path::PathBuf, sync::Arc}; use bip39::Mnemonic; -use futures::FutureExt; use nym_vpn_network_config::{ FeatureFlags, Network, NymNetwork, NymVpnNetwork, ParsedAccountLinks, SystemMessages, }; @@ -17,8 +16,8 @@ use tokio::{ use tokio_util::sync::CancellationToken; use nym_vpn_account_controller::{ - AccountCommand, AccountCommandError, AccountController, AccountControllerCommander, - AccountStateSummary, AvailableTicketbooks, ReadyToConnect, SharedAccountState, + AccountCommand, AccountController, AccountControllerCommander, AccountStateSummary, + AvailableTicketbooks, SharedAccountState, }; use nym_vpn_api_client::{ response::{NymVpnDevice, NymVpnUsage}, @@ -35,7 +34,7 @@ use nym_vpn_lib::{ use nym_vpn_lib_types::{TunnelEvent, TunnelState, TunnelType}; use zeroize::Zeroizing; -use crate::{config::GlobalConfigFile, service::AccountNotReady}; +use crate::config::GlobalConfigFile; use super::{ config::{ConfigSetupError, NetworkEnvironments, NymVpnServiceConfig, DEFAULT_CONFIG_FILE}, @@ -76,7 +75,6 @@ pub enum VpnServiceCommand { ), RefreshAccountState(oneshot::Sender>, ()), GetAccountUsage(oneshot::Sender, AccountError>>, ()), - IsReadyToConnect(oneshot::Sender>, ()), ResetDeviceIdentity(oneshot::Sender>, Option), GetDeviceIdentity(oneshot::Sender>, ()), RegisterDevice(oneshot::Sender>, ()), @@ -280,6 +278,7 @@ impl NymVpnService { event_sender, nym_config, tunnel_settings, + account_command_tx.clone(), shutdown_token.child_token(), ) .await @@ -415,10 +414,6 @@ where let result = self.handle_get_usage().await; let _ = tx.send(result); } - VpnServiceCommand::IsReadyToConnect(tx, ()) => { - let result = Ok(self.handle_is_ready_to_connect().await); - let _ = tx.send(result); - } VpnServiceCommand::ResetDeviceIdentity(tx, seed) => { let result = self.handle_reset_device_identity(seed).await; let _ = tx.send(result); @@ -498,38 +493,6 @@ where Ok(config) } - async fn wait_for_ready_to_connect( - &self, - credentials_mode: bool, - ) -> Result<(), AccountCommandError> { - self.account_command_tx.ensure_update_account().await?; - self.account_command_tx.ensure_update_device().await?; - self.account_command_tx.ensure_register_device().await?; - if credentials_mode { - self.account_command_tx.ensure_available_zk_nyms().await?; - } - Ok(()) - } - - async fn wait_for_ready_to_connect_until_cancelled( - &self, - enable_credentials_mode: bool, - ) -> Result<(), VpnServiceConnectError> { - let wait_for_ready_to_connect_fut = self - .wait_for_ready_to_connect(enable_credentials_mode) - .then(|n| async move { - n.inspect_err(|err| { - tracing::error!("Failed to wait for ready to connect: {:?}", err); - }) - }); - self.shutdown_token - .run_until_cancelled(wait_for_ready_to_connect_fut) - .await - .ok_or(VpnServiceConnectError::Cancel)? - .map_err(AccountNotReady::from)?; - Ok(()) - } - async fn handle_connect( &mut self, connect_args: ConnectArgs, @@ -550,12 +513,6 @@ where options.enable_credentials_mode = options.enable_credentials_mode || enable_credentials_mode; - // Before attempting to connect, ensure that the account is ready with the account synced, - // the device registered, and possibly zknym ticketbooks available in local credential - // storage. - self.wait_for_ready_to_connect_until_cancelled(options.enable_credentials_mode) - .await?; - tracing::info!( "Using entry point: {}", entry @@ -788,10 +745,6 @@ where .map_err(|source| AccountError::AccountCommandError { source }) } - async fn handle_is_ready_to_connect(&self) -> ReadyToConnect { - self.shared_account_state.is_ready_to_connect().await - } - async fn handle_reset_device_identity( &mut self, seed: Option<[u8; 32]>, diff --git a/proto/nym/account.proto b/proto/nym/account.proto new file mode 100644 index 0000000000..01030d5674 --- /dev/null +++ b/proto/nym/account.proto @@ -0,0 +1,370 @@ +syntax = "proto3"; + +package nym.vpn; + +// --- Error types + +message AccountError { + enum AccountErrorType { + STORE_ACCOUNT_ERROR_UNSPECIFIED = 0; + + // The provided mnemonic was not able to be parsed + INVALID_MNEMONIC = 1; + + // General error from the storage backend + STORAGE = 2; + + // Unable to proceed while connected + IS_CONNECTED = 3; + } + + AccountErrorType kind = 1; + + // Detailed error message for logging and debugging + string message = 2; + + // Optional additional details + map details = 3; +} + +message GeneralAccountError { + string error_message = 1; +} + +message StoreAccountError { + oneof error_detail { + string storage_error = 1; + VpnApiErrorResponse error_response = 2; + string unexpected_response = 3; + } +} + +message SyncAccountError { + oneof error_detail { + VpnApiErrorResponse error_response = 1; + string unexpected_response = 2; + } +} + +message SyncDeviceError { + oneof error_detail { + VpnApiErrorResponse error_response = 1; + string unexpected_response = 2; + } +} + +message RegisterDeviceError { + oneof error_detail { + VpnApiErrorResponse error_response = 1; + string unexpected_response = 2; + } +} + +message ForgetAccountError { + oneof error_detail { + bool registration_in_progress = 1; + VpnApiErrorResponse error_response = 2; + string unexpected_response = 3; + string remove_account = 4; + string remove_device_keys = 5; + string reset_credential_store = 6; + string remove_account_files = 7; + string init_device_keys = 8; + } +} + +message VpnApiErrorResponse { + string message = 1; + optional string message_id = 2; + optional string code_reference_id = 3; +} + +message RequestZkNymSuccess { + string id = 1; +} + +message RequestZkNymError { + oneof outcome { + VpnApiErrorResponse vpn_api = 1; + string unexpected_vpn_api_response = 2; + string storage = 3; + string internal = 4; + } +} + +message RequestZkNymBundle { + repeated RequestZkNymSuccess successes = 1; + repeated RequestZkNymError failures = 2; +} + +// --- Common types --- + +message AccountIdentity { + optional string account_identity = 1; +} + +// --- Requests and responses --- + +message StoreAccountRequest { + string mnemonic = 1; + uint32 nonce = 2; +} + +message StoreAccountResponse { + bool success = 1; + AccountError error = 2; +} + +message IsAccountStoredResponse { + oneof resp { + bool is_stored = 1; + AccountError error = 2; + } +} + +message ForgetAccountResponse { + bool success = 1; + AccountError error = 2; +} + +message GetAccountIdentityResponse { + oneof id { + AccountIdentity account_identity = 1; + AccountError error = 2; + } +} + +message GetAccountStateResponse { + message AccountStateSummary { + enum MnemonicState { + MNEMONIC_STATE_UNSPECIFIED = 0; + MNEMONIC_STATE_NOT_STORED = 1; + MNEMONIC_STATE_STORED = 2; + } + + enum AccountRegistered { + ACCOUNT_REGISTERED_UNSPECIFIED = 0; + ACCOUNT_REGISTERED = 1; + ACCOUNT_NOT_REGISTERED = 2; + } + + message AccountSummary { + enum AccountState { + ACCOUNT_STATE_UNSPECIFIED = 0; + ACCOUNT_STATE_NOT_REGISTERED = 1; + ACCOUNT_STATE_INACTIVE = 2; + ACCOUNT_STATE_ACTIVE = 3; + ACCOUNT_STATE_DELETE_ME = 4; + } + + enum SubscriptionState { + SUBSCRIPTION_STATE_UNSPECIFIED = 0; + SUBSCRIPTION_STATE_NOT_REGISTERED = 1; + SUBSCRIPTION_STATE_PENDING = 2; + SUBSCRIPTION_STATE_ACTIVE = 3; + SUBSCRIPTION_STATE_COMPLETE = 4; + } + + message DeviceSummary { + uint64 active = 1; + uint64 max = 2; + uint64 remaining = 3; + } + + AccountState account = 1; + SubscriptionState subscription = 2; + DeviceSummary device_summary = 3; + } + + enum DeviceState { + DEVICE_STATE_UNSPECIFIED = 0; + DEVICE_STATE_NOT_REGISTERED = 1; + DEVICE_STATE_INACTIVE = 2; + DEVICE_STATE_ACTIVE = 3; + DEVICE_STATE_DELETE_ME = 4; + } + + optional MnemonicState mnemonic = 1; + optional AccountRegistered account_registered = 2; + optional AccountSummary account_summary = 3; + optional DeviceState device = 4; + optional RegisterDeviceResult register_device_result = 5; + // NOTE: don't reuse tag 6 + // bool pending_zk_nym = 6; + optional RequestZkNymResult request_zk_nym_result = 7; + } + + oneof result { + AccountStateSummary account = 1; + AccountError error = 2; + } +} + +message RegisterDeviceResult { + enum RegisterDeviceResultType { + REGISTER_DEVICE_RESULT_UNSPECIFIED = 0; + IN_PROGRESS = 1; + SUCCESS = 2; + FAILED = 3; + } + + RegisterDeviceResultType kind = 1; + optional string message = 2; + optional string message_id = 3; +} + +// The status of a zk-nym request. A request can have multiple successes +// and failures, depending on how many ticket types were requested. +message RequestZkNymResult { + enum RequestZkNymResultType { + REQUEST_ZK_NYM_RESULT_UNSPECIFIED = 0; + IN_PROGRESS = 1; + DONE = 4; + ERROR = 5; + } + + RequestZkNymResultType kind = 1; + repeated RequestZkNymSuccess successes = 2; + repeated RequestZkNymError failures = 3; +} + +message RefreshAccountStateResponse {} + +message GetAccountUsageResponse { + message AccountUsages { + repeated AccountUsage account_usages = 1; + } + + message AccountUsage { + string created_on_utc = 1; + string last_updated_utc = 2; + string id = 3; + string subscription_id = 4; + string valid_until_utc = 5; + string valid_from_utc = 6; + double bandwidth_allowance_gb = 7; + double bandwidth_used_gb = 8; + } + + oneof result { + AccountUsages account_usages = 1; + AccountError error = 2; + } +} + +message ResetDeviceIdentityRequest { + // 32 byte seed, [u8; 32] + optional bytes seed = 1; +} + +message ResetDeviceIdentityResponse { + bool success = 1; + AccountError error = 2; +} + +message GetDeviceIdentityResponse { + oneof id { + string device_identity = 1; + AccountError error = 2; + } +} + +message RegisterDeviceResponse { + string json = 1; + AccountError error = 2; +} + +message GetDevicesResponse { + message Device { + enum DeviceStatus { + DEVICE_STATUS_UNSPECIFIED = 0; + ACTIVE = 1; + INACTIVE = 2; + DELETE_ME = 3; + } + + string created_on_utc = 1; + string last_updated_utc = 2; + string device_identity_key = 3; + DeviceStatus status = 4; + } + + message Devices { + repeated Device devices = 1; + } + + oneof result { + Devices devices = 1; + AccountError error = 2; + } +} + +message RequestZkNymResponse { + string json = 1; + AccountError error = 2; +} + +message GetDeviceZkNymsResponse { + string json = 1; + AccountError error = 2; +} + +message GetZkNymsAvailableForDownloadResponse { + string json = 1; + AccountError error = 2; +} + +message GetZkNymByIdRequest { + string id = 1; +} + +message GetZkNymByIdResponse { + string json = 1; + AccountError error = 2; +} + +message ConfirmZkNymDownloadedRequest { + string id = 1; +} + +message ConfirmZkNymDownloadedResponse { + AccountError error = 2; +} + +message GetAvailableTicketsResponse { + message AvailableTickets { + // Remaining number of mixnet entry tickets + uint64 mixnet_entry_tickets = 1; + // Remaining mixnet entry data in bytes + uint64 mixnet_entry_data = 2; + // Remaining mixnet entry data formatted to a string using SI units + string mixnet_entry_data_si = 3; + + // Remaining number of mixnet exit tickets + uint64 mixnet_exit_tickets = 4; + // Remaining mixnet exit data in bytes + uint64 mixnet_exit_data = 5; + // Remaining mixnet exit data formatted to a string using SI units + string mixnet_exit_data_si = 6; + + // Remaining number of vpn entry tickets + uint64 vpn_entry_tickets = 7; + // Remaining vpn entry data in bytes + uint64 vpn_entry_data = 8; + // Remaining vpn entry data formatted to a string using SI units + string vpn_entry_data_si = 9; + + // Remaining number of vpn exit tickets + uint64 vpn_exit_tickets = 10; + // Remaining vpn exit data in bytes + uint64 vpn_exit_data = 11; + // Remaining vpn exit data formatted to a string using SI units + string vpn_exit_data_si = 12; + } + + oneof resp { + AvailableTickets available_tickets = 1; + AccountError error = 2; + } +} + diff --git a/proto/nym/vpn.proto b/proto/nym/vpn.proto index b7a4a39e44..7ea895684c 100644 --- a/proto/nym/vpn.proto +++ b/proto/nym/vpn.proto @@ -5,6 +5,8 @@ package nym.vpn; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; +import "account.proto"; + // Represents the identity of a gateway message Gateway { string id = 1; @@ -202,38 +204,12 @@ message ConnectRequestError { // Unspecified internal error INTERNAL = 1; - - // General error - GENERAL = 2; - - // No account recovery phrase stored - NO_ACCOUNT_STORED = 3; - - // No device keys stored - NO_DEVICE_STORED = 4; - - // Update account failed - UPDATE_ACCOUNT = 5; - - // Update device failed - UPDATE_DEVICE = 6; - - // Device registration failed - REGISTER_DEVICE = 7; - - // Requesting zk-nym failed - REQUEST_ZK_NYM = 8; } ConnectRequestErrorType kind = 1; // Internal message for logging and debugging string message = 2; - - optional string message_id = 3; - - // When we have an error request zk-nyms, this is the set of zk-nym failures - repeated RequestZkNymError zk_nym_error = 4; } message ConnectRequest { @@ -254,7 +230,6 @@ message ConnectRequest { } message ConnectResponse { - // TODO: consider simplifying by removing the bool bool success = 1; ConnectRequestError error = 2; } @@ -342,39 +317,6 @@ message ListCountriesResponse { repeated Location countries = 1; } -message StoreAccountRequest { - string mnemonic = 1; - uint32 nonce = 2; -} - -message StoreAccountResponse { - bool success = 1; - AccountError error = 2; -} - -message IsAccountStoredResponse { - oneof resp { - bool is_stored = 1; - AccountError error = 2; - } -} - -message ForgetAccountResponse { - bool success = 1; - AccountError error = 2; -} - -message AccountIdentity { - optional string account_identity = 1; -} - -message GetAccountIdentityResponse { - oneof id { - AccountIdentity account_identity = 1; - AccountError error = 2; - } -} - message GetAccountLinksRequest { string locale = 1; } @@ -386,418 +328,6 @@ message GetAccountLinksResponse { } } -enum AccountRegistered { - ACCOUNT_REGISTERED_UNSPECIFIED = 0; - ACCOUNT_REGISTERED = 1; - ACCOUNT_NOT_REGISTERED = 2; -} - -enum MnemonicState { - MNEMONIC_STATE_UNSPECIFIED = 0; - MNEMONIC_STATE_NOT_STORED = 1; - MNEMONIC_STATE_STORED = 2; -} - -enum AccountState { - ACCOUNT_STATE_UNSPECIFIED = 0; - ACCOUNT_STATE_NOT_REGISTERED = 1; - ACCOUNT_STATE_INACTIVE = 2; - ACCOUNT_STATE_ACTIVE = 3; - ACCOUNT_STATE_DELETE_ME = 4; -} - -enum SubscriptionState { - SUBSCRIPTION_STATE_UNSPECIFIED = 0; - SUBSCRIPTION_STATE_NOT_REGISTERED = 1; - SUBSCRIPTION_STATE_PENDING = 2; - SUBSCRIPTION_STATE_ACTIVE = 3; - SUBSCRIPTION_STATE_COMPLETE = 4; -} - -message DeviceSummary { - uint64 active = 1; - uint64 max = 2; - uint64 remaining = 3; -} - -message AccountSummary { - AccountState account = 1; - SubscriptionState subscription = 2; - DeviceSummary device_summary = 3; -} - -enum DeviceState { - DEVICE_STATE_UNSPECIFIED = 0; - DEVICE_STATE_NOT_REGISTERED = 1; - DEVICE_STATE_INACTIVE = 2; - DEVICE_STATE_ACTIVE = 3; - DEVICE_STATE_DELETE_ME = 4; -} - -message RegisterDeviceResult { - enum RegisterDeviceResultType { - REGISTER_DEVICE_RESULT_UNSPECIFIED = 0; - IN_PROGRESS = 1; - SUCCESS = 2; - FAILED = 3; - } - - RegisterDeviceResultType kind = 1; - optional string message = 2; - optional string message_id = 3; -} - -// The status of a zk-nym request. A request can have multiple successes -// and failures, depending on how many ticket types were requested. -message RequestZkNymResult { - enum RequestZkNymResultType { - REQUEST_ZK_NYM_RESULT_UNSPECIFIED = 0; - IN_PROGRESS = 1; - DONE = 4; - ERROR = 5; - } - - RequestZkNymResultType kind = 1; - repeated RequestZkNymSuccess successes = 2; - repeated RequestZkNymError failures = 3; -} - -message RequestZkNymSuccess { - string id = 1; -} - -message RequestZkNymError { - enum RequestZkNymErrorType { - REQUEST_ZK_NYM_ERROR_TYPE_UNSPECIFIED = 0; - - // Unspecified internal error - INTERNAL = 1; - - // General error - GENERAL = 2; - - // Error returned from the nym-vpn-api endpoint for get the zk-nyms that - // are available for download or resume. - GET_ZK_NYMS_AVAILABLE_FOR_DOWNLOAD_ENDPOINT_FAILURE = 9; - - // Failed to create the ecash key pair - CREATE_ECASH_KEY_PAIR = 10; - - // Failed to construct the withdrawal request - CONSTRUCT_WITHDRAWAL_REQUEST = 11; - - // Error returned from the nym-vpn-api endpoint - REQUEST_ZK_NYM_ENDPOINT_FAILURE = 3; - - // The vpn-api response contained an invalid ticket type - INVALID_TICKET_TYPE_IN_RESPONSE = 12; - - // The vpn-api response contained a ticket type that didn't match the one - // we requested - TICKET_TYPE_MISMATCH = 13; - - // Error returned from the nym-vpn-api polling endpoint - POLL_ZK_NYM_ENDPOINT_FAILURE = 4; - - // The task polling for the result failed - POLLING_TASK_ERROR = 5; - - // Timeout polling for the result - POLLING_TIMEOUT = 6; - - // Request finished correctly, but the nym-vpn-api returned an error - // with the result. - FINISHED_WITH_ERROR = 7; - - // The vpn-api response is missing the blinded shares - MISSING_BLINDED_SHARES = 14; - - // The vpn-api response is missing the master verification key - RESPONSE_HAS_INVALID_MASTER_VERIFICATION_KEY = 15; - - // The vpn-api response has an inconsistent epoch id - EPOCH_ID_MISMATCH = 16; - - // The vpn-api response has an inconsistent expiration date - EXPIRATION_DATE_MISMATCH = 17; - - // Error returned from the nym-vpn-api endpoint for getting the partial - // verification keys - GET_PARTIAL_VERIFICATION_KEYS_ENDPOINT_FAILURE = 18; - - // Missing the master verification key in storage - NO_MASTER_VERIFICATION_KEY_IN_STORAGE = 19; - - // Missing coin index signatures in storage - NO_COIN_INDEX_SIGNATURES_IN_STORAGE = 20; - - // Missing expiration date signatures in storage - NO_EXPIRATION_DATE_SIGNATURES_IN_STORAGE = 21; - - // The verification key is invalid - INVALID_VERIFICATION_KEY = 22; - - // Failed to deserialize the blinded signature - DESERIALIZE_BLINDED_SIGNATURE = 23; - - // Missing index when decoding keys - DECODED_KEYS_MISSING_INDEX = 24; - - // Failed to import the ticketbook into local storage - IMPORT = 8; - - // Failed to aggregate the wallets - AGGREGATE_WALLETS = 25; - - // The error returned from the vpn-api endpoint when confirming the zk-nym - // download - CONFIRM_ZK_NYM_DOWNLOAD_ENDPOINT_FAILURE = 26; - - // Error importing the zknym due to the storage missing the pending request data - MISSING_PENDING_REQUEST = 27; - - // Error removing the pending request from storage - REMOVE_PENDING_REQUEST = 28; - - // General credential storage error - CREDENTIAL_STORAGE = 29; - - // The vpn-api response contained an unexpected error - UNEXPECTED_ERROR_RESPONSE = 30; - } - - RequestZkNymErrorType kind = 1; - optional string id = 2; - optional string ticketbook_type = 3; - optional string message = 4; - optional string message_id = 5; -} - -message AccountStateSummary { - optional MnemonicState mnemonic = 1; - optional AccountRegistered account_registered = 2; - optional AccountSummary account_summary = 3; - optional DeviceState device = 4; - optional RegisterDeviceResult register_device_result = 5; - // NOTE: don't reuse tag 6 - // bool pending_zk_nym = 6; - optional RequestZkNymResult request_zk_nym_result = 7; -} - -message GetAccountStateResponse { - oneof result { - AccountStateSummary account = 1; - AccountError error = 2; - } -} - -message RefreshAccountStateResponse {} - -message AccountUsages { - repeated AccountUsage account_usages = 1; -} - -message AccountUsage { - string created_on_utc = 1; - string last_updated_utc = 2; - string id = 3; - string subscription_id = 4; - string valid_until_utc = 5; - string valid_from_utc = 6; - double bandwidth_allowance_gb = 7; - double bandwidth_used_gb = 8; -} - -message GetAccountUsageResponse { - oneof result { - AccountUsages account_usages = 1; - AccountError error = 2; - } -} - -message ResetDeviceIdentityRequest { - // 32 byte seed, [u8; 32] - optional bytes seed = 1; -} - -message ResetDeviceIdentityResponse { - bool success = 1; - AccountError error = 2; -} - -message GetDeviceIdentityResponse { - oneof id { - string device_identity = 1; - AccountError error = 2; - } -} - -message RegisterDeviceResponse { - string json = 1; - AccountError error = 2; -} - -enum DeviceStatus { - DEVICE_STATUS_UNSPECIFIED = 0; - DEVICE_STATUS_ACTIVE = 1; - DEVICE_STATUS_INACTIVE = 2; - DEVICE_STATUS_DELETE_ME = 3; -} - -message Device { - string created_on_utc = 1; - string last_updated_utc = 2; - string device_identity_key = 3; - DeviceStatus status = 4; -} - -message Devices { - repeated Device devices = 1; -} - -message GetDevicesResponse { - oneof result { - Devices devices = 1; - AccountError error = 2; - } -} - -message GetActiveDevicesResponse { - oneof result { - Devices devices = 1; - AccountError error = 2; - } -} - -message RequestZkNymResponse { - string json = 1; - AccountError error = 2; -} - -message GetDeviceZkNymsResponse { - string json = 1; - AccountError error = 2; -} - -message GetZkNymsAvailableForDownloadResponse { - string json = 1; - AccountError error = 2; -} - -message GetZkNymByIdRequest { - string id = 1; -} - -message GetZkNymByIdResponse { - string json = 1; - AccountError error = 2; -} - -message ConfirmZkNymDownloadedRequest { - string id = 1; -} - -message ConfirmZkNymDownloadedResponse { - AccountError error = 2; -} - -message AvailableTickets { - // Remaining number of mixnet entry tickets - uint64 mixnet_entry_tickets = 1; - // Remaining mixnet entry data in bytes - uint64 mixnet_entry_data = 2; - // Remaining mixnet entry data formatted to a string using SI units - string mixnet_entry_data_si = 3; - - // Remaining number of mixnet exit tickets - uint64 mixnet_exit_tickets = 4; - // Remaining mixnet exit data in bytes - uint64 mixnet_exit_data = 5; - // Remaining mixnet exit data formatted to a string using SI units - string mixnet_exit_data_si = 6; - - // Remaining number of vpn entry tickets - uint64 vpn_entry_tickets = 7; - // Remaining vpn entry data in bytes - uint64 vpn_entry_data = 8; - // Remaining vpn entry data formatted to a string using SI units - string vpn_entry_data_si = 9; - - // Remaining number of vpn exit tickets - uint64 vpn_exit_tickets = 10; - // Remaining vpn exit data in bytes - uint64 vpn_exit_data = 11; - // Remaining vpn exit data formatted to a string using SI units - string vpn_exit_data_si = 12; -} - -message GetAvailableTicketsResponse { - oneof resp { - AvailableTickets available_tickets = 1; - AccountError error = 2; - } -} - -message IsReadyToConnectResponse { - enum IsReadyToConnectResponseType { - IS_READY_TO_CONNECT_RESPONSE_TYPE_UNSPECIFIED = 0; - - // We are ready to attempt to connect - READY = 1; - - // No account recovery phrase stored - NO_ACCOUNT_STORED = 2; - - // The account is not synced - ACCOUNT_NOT_SYNCED = 3; - - // The account is not registered - ACCOUNT_NOT_REGISTERED = 4; - - // The account is not active - ACCOUNT_NOT_ACTIVE = 5; - - // The account does not have an active subscription - NO_ACTIVE_SUBSCRIPTION = 6; - - // The device is not registered - DEVICE_NOT_REGISTERED = 7; - - // The device is not active - // NOTE: in the future we will try to re-active an inactive device on - // connect - DEVICE_NOT_ACTIVE = 8; - - // The device has reached the maximum number of devices - MAX_DEVICES_REACHED = 9; - } - - IsReadyToConnectResponseType kind = 1; -} - -message AccountError { - enum AccountErrorType { - STORE_ACCOUNT_ERROR_UNSPECIFIED = 0; - - // The provided mnemonic was not able to be parsed - INVALID_MNEMONIC = 1; - - // General error from the storage backend - STORAGE = 2; - - // Unable to proceed while connected - IS_CONNECTED = 3; - } - - AccountErrorType kind = 1; - - // Detailed error message for logging and debugging - string message = 2; - - // Optional additional details - map details = 3; -} - message ConnectionData { Gateway entry_gateway = 1; Gateway exit_gateway = 2; @@ -831,7 +361,7 @@ message TunnelConnectionData { message Wireguard { WireguardConnectionData data = 2; } - + oneof state { Mixnet mixnet = 1; Wireguard wireguard = 2; @@ -839,18 +369,20 @@ message TunnelConnectionData { } message TunnelState { - enum ErrorStateReason { - FIREWALL = 0; - ROUTING = 1; - DNS = 2; - TUN_DEVICE = 3; - TUNNEL_PROVIDER = 4; - SAME_ENTRY_AND_EXIT_GATEWAY = 5; - INVALID_ENTRY_GATEWAY_COUNTRY = 6; - INVALID_EXIT_GATEWAY_COUNTRY = 7; - BAD_BANDWIDTH_INCREASE = 8; - DUPLICATE_TUN_FD = 9; - INTERNAL = 10; + enum BaseErrorStateReason { + FIREWALL = 0; + ROUTING = 1; + DNS = 2; + TUN_DEVICE = 3; + TUNNEL_PROVIDER = 4; + SAME_ENTRY_AND_EXIT_GATEWAY = 5; + INVALID_ENTRY_GATEWAY_COUNTRY = 6; + INVALID_EXIT_GATEWAY_COUNTRY = 7; + BAD_BANDWIDTH_INCREASE = 8; + DUPLICATE_TUN_FD = 9; + INTERNAL = 10; + NO_ACCOUNT_STORED = 11; + NO_DEVICE_STORED = 12; } enum ActionAfterDisconnect { @@ -865,13 +397,23 @@ message TunnelState { optional ConnectionData connection_data = 1; } message Connected { - ConnectionData connection_data = 1; + ConnectionData connection_data = 1; } message Disconnecting { ActionAfterDisconnect after_disconnect = 1; } message Error { - ErrorStateReason reason = 1; + oneof error_state_reason { + BaseErrorStateReason base_reason = 1; + GeneralAccountError general_account = 2; + StoreAccountError store_account = 3; + SyncAccountError sync_account = 4; + SyncDeviceError sync_device = 5; + RegisterDeviceError register_device = 6; + RequestZkNymError request_zk_nym = 7; + RequestZkNymBundle request_zk_nym_bundle = 8; + ForgetAccountError forget_account = 9; + } } message Offline { bool reconnect = 1; @@ -1016,9 +558,6 @@ service NymVpnd { // Get the account usage from the nym-vpn-api rpc GetAccountUsage (google.protobuf.Empty) returns (GetAccountUsageResponse) {} - // Check if the local account state is ready to connect - rpc IsReadyToConnect (google.protobuf.Empty) returns (IsReadyToConnectResponse) {} - // Reset the device identity rpc ResetDeviceIdentity (ResetDeviceIdentityRequest) returns (ResetDeviceIdentityResponse) {} @@ -1032,7 +571,7 @@ service NymVpnd { rpc GetDevices (google.protobuf.Empty) returns (GetDevicesResponse) {} // Get the list of active devices associated with this account from the nym-vpn-api - rpc GetActiveDevices (google.protobuf.Empty) returns (GetActiveDevicesResponse) {} + rpc GetActiveDevices (google.protobuf.Empty) returns (GetDevicesResponse) {} // Request new zk-nyms (ticketbooks) from the nym-vpn-api rpc RequestZkNym (google.protobuf.Empty) returns (RequestZkNymResponse) {} From 7651ebb1890e66de4643b1e659c7d7658343f55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 7 Feb 2025 09:30:48 +0100 Subject: [PATCH 2/9] refine errors in uniffi --- .../nym-vpn-lib/src/platform/account.rs | 93 +++++++------------ .../nym-vpn-lib/src/platform/environment.rs | 13 ++- .../crates/nym-vpn-lib/src/platform/error.rs | 82 ++++++++++------ .../crates/nym-vpn-lib/src/platform/mod.rs | 14 ++- 4 files changed, 104 insertions(+), 98 deletions(-) diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs index e0dbd8d856..4825db6993 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs @@ -237,55 +237,46 @@ pub(super) async fn get_device_id() -> Result { .map_err(VpnError::from) } -// Raw API that does not interact with the account controller +// Raw API that directly accesses storage without going through the account controller. +// This API places the responsibility of ensuring the account controller is not running on +// the caller. +// +// WARN: This API was added mostly as a workaround for unblocking the iOS client, and is not a +// sustainable long term solution. pub(crate) mod raw { use std::path::Path; - use super::*; - use crate::platform::environment; use nym_sdk::mixnet::StoragePaths; - use nym_vpn_api_client::response::NymVpnAccountResponse; - use nym_vpn_api_client::types::{Device, DeviceStatus}; - use nym_vpn_api_client::VpnApiClient; + use nym_vpn_api_client::{ + response::NymVpnAccountResponse, + types::{Device, DeviceStatus}, + VpnApiClient, + }; + + use crate::{platform::environment, storage::VpnClientOnDiskStorage}; - async fn setup_account_storage( - path: &str, - ) -> Result { + use super::*; + + async fn setup_account_storage(path: &str) -> Result { assert_account_controller_not_running().await?; let path = PathBuf::from_str(path).map_err(|err| VpnError::InvalidAccountStoragePath { details: err.to_string(), })?; - Ok(crate::storage::VpnClientOnDiskStorage::new(path)) + Ok(VpnClientOnDiskStorage::new(path)) } pub(crate) async fn login_raw(mnemonic: &str, path: &str) -> Result<(), VpnError> { let mnemonic = parse_mnemonic(mnemonic).await?; get_account_by_mnemonic_raw(mnemonic.clone()).await?; let storage = setup_account_storage(path).await?; - storage - .store_mnemonic(mnemonic) - .await - .map_err(|err| VpnError::InternalError { - details: err.to_string(), - })?; - storage - .init_keys(None) - .await - .map_err(|err| VpnError::InternalError { - details: err.to_string(), - })?; - + storage.store_mnemonic(mnemonic).await?; + storage.init_keys(None).await?; Ok(()) } pub(crate) async fn is_account_mnemonic_stored_raw(path: &str) -> Result { let storage = setup_account_storage(path).await?; - storage - .is_mnemonic_stored() - .await - .map_err(|err| VpnError::InternalError { - details: err.to_string(), - }) + storage.is_mnemonic_stored().await.map_err(Into::into) } pub(crate) async fn get_account_id_raw(path: &str) -> Result { @@ -304,19 +295,13 @@ pub(crate) mod raw { .remove_mnemonic() .await .map(|_| true) - .map_err(|err| VpnError::InternalError { - details: err.to_string(), - }) + .map_err(Into::into) } async fn remove_credential_storage_raw>(path: P) -> Result<(), VpnError> { - let storage_paths = - StoragePaths::new_from_dir(&path).map_err(|err| VpnError::InternalError { - details: err.to_string(), - })?; - + let storage_paths = StoragePaths::new_from_dir(&path).map_err(VpnError::internal)?; std::fs::remove_file(storage_paths.credential_database_path).map_err(|err| { - VpnError::InternalError { + VpnError::Storage { details: err.to_string(), } }) @@ -325,11 +310,7 @@ pub(crate) mod raw { async fn create_vpn_api_client() -> Result { let network_env = environment::current_environment_details().await?; let user_agent = crate::util::construct_user_agent(); - VpnApiClient::new(network_env.vpn_api_url(), user_agent).map_err(|e| { - VpnError::InternalError { - details: e.to_string(), - } - }) + VpnApiClient::new(network_env.vpn_api_url(), user_agent).map_err(VpnError::internal) } async fn load_device(path: &str) -> Result { @@ -355,10 +336,10 @@ pub(crate) mod raw { vpn_api_client .update_device(&account, &device, DeviceStatus::DeleteMe) .await + .map(|_| ()) .map_err(|err| VpnError::UnregisterDevice { details: err.to_string(), - })?; - Ok(()) + }) } async fn get_account_by_mnemonic_raw( @@ -380,14 +361,11 @@ pub(crate) mod raw { details: err.to_string(), })?; - match unregister_device_raw(path).await { - Ok(_) => { - tracing::info!("Device has been unregistered"); - } - Err(error) => { - tracing::error!("Failed to unregister device: {error:?}"); - } - } + unregister_device_raw(path) + .await + .inspect(|_| tracing::info!("Device has been unregistered")) + .inspect_err(|err| tracing::error!("Failed to unregister device: {err:?}")) + .ok(); // First remove the files we own directly remove_account_mnemonic_raw(path).await?; @@ -396,7 +374,7 @@ pub(crate) mod raw { // Then remove the rest of the files, that we own indirectly nym_vpn_account_controller::util::remove_files_for_account(&path_buf).map_err(|err| { - VpnError::InternalError { + VpnError::Storage { details: err.to_string(), } })?; @@ -415,11 +393,6 @@ pub(crate) mod raw { pub(crate) async fn remove_device_identity_raw(path: &str) -> Result<(), VpnError> { let storage = setup_account_storage(path).await?; - storage - .remove_keys() - .await - .map_err(|err| VpnError::InternalError { - details: err.to_string(), - }) + storage.remove_keys().await.map_err(VpnError::internal) } } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/environment.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/environment.rs index 4e43926c05..2c73f8f81f 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/environment.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/environment.rs @@ -7,7 +7,7 @@ use super::{error::VpnError, NETWORK_ENVIRONMENT}; pub(crate) async fn init_environment(network_name: &str) -> Result<(), VpnError> { let network = nym_vpn_network_config::Network::fetch(network_name).map_err(|err| { - VpnError::InternalError { + VpnError::NetworkConnectionError { details: err.to_string(), } })?; @@ -67,9 +67,7 @@ pub(crate) async fn get_account_links(locale: &str) -> Result Result { + // If the account ID is not found, we are not logged in, so we don't need to pass it to the + // API. But we can still get the links that don't require an account ID. let account_id = super::account::raw::get_account_id_raw(path).await.ok(); + current_environment_details() .await .and_then(|network| { network .nym_vpn_network .try_into_parsed_links(locale, account_id.as_deref()) - .map_err(|err| VpnError::InternalError { - details: err.to_string(), - }) + .map_err(VpnError::internal) }) .map(AccountLinks::from) } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs index a370e868d0..bba026c8dc 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs @@ -5,13 +5,16 @@ use nym_vpn_lib_types::AccountCommandError; #[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] pub enum VpnError { - #[error("{details}")] + #[error("internal error:{details}")] InternalError { details: String }, - #[error("{details}")] + #[error("storage error: {details}")] + Storage { details: String }, + + #[error("network error: {details}")] NetworkConnectionError { details: String }, - #[error("{details}")] + #[error("API usage error: {details}")] InvalidStateError { details: String }, #[error("no account stored")] @@ -35,20 +38,34 @@ pub enum VpnError { #[error("failed to remove device from nym vpn api: {details}")] UnregisterDevice { details: String }, - #[error("failed to store account: {0}")] - StoreAccount(super::uniffi_lib_types::StoreAccountError), - - #[error("sync account failed: {0}")] - SyncAccount(super::uniffi_lib_types::SyncAccountError), + #[error("failed to store account: {details}")] + StoreAccount { + #[from] + details: super::uniffi_lib_types::StoreAccountError, + }, - #[error("sync device failed: {0}")] - SyncDevice(super::uniffi_lib_types::SyncDeviceError), + #[error("sync account failed: {details}")] + SyncAccount { + #[from] + details: super::uniffi_lib_types::SyncAccountError, + }, + #[error("sync device failed: {details}")] + SyncDevice { + #[from] + details: super::uniffi_lib_types::SyncDeviceError, + }, - #[error("device registration failed: {0}")] - RegisterDevice(super::uniffi_lib_types::RegisterDeviceError), + #[error("device registration failed: {details}")] + RegisterDevice { + #[from] + details: super::uniffi_lib_types::RegisterDeviceError, + }, #[error("failed to request zk nym")] - RequestZkNym(super::uniffi_lib_types::RequestZkNymError), + RequestZkNym { + #[from] + details: super::uniffi_lib_types::RequestZkNymError, + }, #[error("when requesting zk nym, some were reported as failed")] RequestZkNymBundle { @@ -56,8 +73,19 @@ pub enum VpnError { failed: Vec, }, - #[error("failed to forget account: {0}")] - ForgetAccount(super::uniffi_lib_types::ForgetAccountError), + #[error("failed to forget account: {details}")] + ForgetAccount { + #[from] + details: super::uniffi_lib_types::ForgetAccountError, + }, +} + +impl VpnError { + pub fn internal(details: impl ToString) -> Self { + Self::InternalError { + details: details.to_string(), + } + } } impl From for VpnError { @@ -67,18 +95,18 @@ impl From for VpnError { AccountCommandError::Internal(err) => Self::InternalError { details: err }, AccountCommandError::NoAccountStored => Self::NoAccountStored, AccountCommandError::NoDeviceStored => Self::NoDeviceIdentity, - AccountCommandError::StoreAccount(e) => Self::StoreAccount(e.into()), - AccountCommandError::SyncAccount(e) => Self::SyncAccount(e.into()), - AccountCommandError::SyncDevice(e) => Self::SyncDevice(e.into()), - AccountCommandError::RegisterDevice(e) => Self::RegisterDevice(e.into()), - AccountCommandError::RequestZkNym(e) => Self::RequestZkNym(e.into()), + AccountCommandError::StoreAccount(e) => Self::StoreAccount { details: e.into() }, + AccountCommandError::SyncAccount(e) => Self::SyncAccount { details: e.into() }, + AccountCommandError::SyncDevice(e) => Self::SyncDevice { details: e.into() }, + AccountCommandError::RegisterDevice(e) => Self::RegisterDevice { details: e.into() }, + AccountCommandError::RequestZkNym(e) => Self::RequestZkNym { details: e.into() }, AccountCommandError::RequestZkNymBundle { successes, failed } => { Self::RequestZkNymBundle { successes: successes.into_iter().map(|e| e.into()).collect(), failed: failed.into_iter().map(|e| e.into()).collect(), } } - AccountCommandError::ForgetAccount(e) => Self::ForgetAccount(e.into()), + AccountCommandError::ForgetAccount(e) => Self::ForgetAccount { details: e.into() }, } } } @@ -91,17 +119,17 @@ impl From for VpnError { } } -impl From for VpnError { - fn from(value: nym_gateway_directory::Error) -> Self { - Self::NetworkConnectionError { +impl From for VpnError { + fn from(value: nym_vpn_store::keys::persistence::OnDiskKeysError) -> Self { + Self::Storage { details: value.to_string(), } } } -impl From for VpnError { - fn from(value: nym_vpn_api_client::VpnApiClientError) -> Self { - Self::NetworkConnectionError { +impl From for VpnError { + fn from(value: nym_vpn_store::mnemonic::on_disk::OnDiskMnemonicStorageError) -> Self { + Self::Storage { details: value.to_string(), } } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs index 31766d735c..439468d33c 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs @@ -281,11 +281,14 @@ async fn get_gateway_countries( nym_vpn_api_url, min_gateway_performance, }; - GatewayClient::new(directory_config, user_agent.into())? + GatewayClient::new(directory_config, user_agent.into()) + .map_err(VpnError::internal)? .lookup_countries(gw_type.into()) .await .map(|countries| countries.into_iter().map(Location::from).collect()) - .map_err(VpnError::from) + .map_err(|err| VpnError::NetworkConnectionError { + details: err.to_string(), + }) } /// Get the list of gateways available of the given type. @@ -319,7 +322,8 @@ async fn get_gateways( nym_vpn_api_url, min_gateway_performance, }; - GatewayClient::new(directory_config, user_agent.into())? + GatewayClient::new(directory_config, user_agent.into()) + .map_err(VpnError::internal)? .lookup_gateways(gw_type.into()) .await .map(|gateways| { @@ -329,7 +333,9 @@ async fn get_gateways( .map(GatewayInfo::from) .collect() }) - .map_err(VpnError::from) + .map_err(|err| VpnError::NetworkConnectionError { + details: err.to_string(), + }) } /// Start the VPN by first establishing that the account is ready to connect, including requesting From 35e293023f324bc99e0e8ddf04e4815e39f49628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 10 Feb 2025 23:56:26 +0100 Subject: [PATCH 3/9] Replace function with TryFrom --- .../src/commands/register_device.rs | 4 ++-- .../src/commands/request_zknym/cached_data.rs | 4 ++-- .../src/commands/request_zknym/handler.rs | 4 ++-- .../src/commands/request_zknym/request.rs | 8 ++++---- .../src/commands/sync_account.rs | 4 ++-- .../src/commands/sync_device.rs | 4 ++-- .../nym-vpn-account-controller/src/controller.rs | 8 +++++--- .../crates/nym-vpn-account-controller/src/util.rs | 12 ------------ .../crates/nym-vpn-lib-types/src/account/mod.rs | 13 +++++++++++++ 9 files changed, 32 insertions(+), 29 deletions(-) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs index 94b09ecde4..fb199cb861 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs @@ -6,7 +6,7 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; -use nym_vpn_lib_types::RegisterDeviceError; +use nym_vpn_lib_types::{RegisterDeviceError, VpnApiErrorResponse}; use crate::{ shared_state::{DeviceState, RegisterDeviceResult}, @@ -102,7 +102,7 @@ pub(crate) async fn register_device( .register_device(account, device) .await .map_err(|err| { - crate::util::into_endpoint_failure(err) + VpnApiErrorResponse::try_from(err) .map(RegisterDeviceError::RegisterDeviceEndpointFailure) .unwrap_or_else(RegisterDeviceError::unexpected_response) })?; diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs index f36b7945ff..e0583c97dc 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/cached_data.rs @@ -5,7 +5,7 @@ use std::{collections::HashMap, sync::Arc}; use nym_credential_proxy_requests::api::v1::ticketbook::models::PartialVerificationKeysResponse; use nym_vpn_api_client::VpnApiClient; -use nym_vpn_lib_types::RequestZkNymError; +use nym_vpn_lib_types::{RequestZkNymError, VpnApiErrorResponse}; // Generic struct to store cached data during the request process, both between concurrent requests // for different types, and between requests for the same type. @@ -44,7 +44,7 @@ impl CachedData { .get_directory_zk_nyms_ticketbook_partial_verification_keys() .await .map_err(|err| { - crate::util::into_endpoint_failure(err) + VpnApiErrorResponse::try_from(err) .map(|source| { RequestZkNymError::GetPartialVerificationKeysEndpointFailure { response: source, diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs index 98d71a1c72..f5ef2f30a8 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs @@ -14,7 +14,7 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; -use nym_vpn_lib_types::{AccountCommandError, RequestZkNymError, RequestZkNymSuccess}; +use nym_vpn_lib_types::{AccountCommandError, RequestZkNymError, RequestZkNymSuccess, VpnApiErrorResponse}; use tokio::task::JoinSet; use crate::{ @@ -246,7 +246,7 @@ impl RequestZkNymCommandHandler { .await .map(|response| response.items.into_iter().map(|item| item.id).collect()) .map_err(|err| { - crate::util::into_endpoint_failure(err) + VpnApiErrorResponse::try_from(err) .map(|response| { RequestZkNymError::GetZkNymsAvailableForDownloadEndpointFailure { response } }) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs index 6063049440..ff3e892eab 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs @@ -20,7 +20,7 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; -use nym_vpn_lib_types::{RequestZkNymError, RequestZkNymSuccess}; +use nym_vpn_lib_types::{RequestZkNymError, RequestZkNymSuccess, VpnApiErrorResponse}; use time::Date; use crate::storage::{PendingCredentialRequest, VpnCredentialStorage}; @@ -158,7 +158,7 @@ impl RequestZkNymTask { ) .await .map_err(|err| { - crate::util::into_endpoint_failure(err) + VpnApiErrorResponse::try_from(err) .map(|response| RequestZkNymError::RequestZkNymEndpointFailure { response, ticket_type: request.ticketbook_type.to_string(), @@ -226,7 +226,7 @@ impl RequestZkNymTask { } } Err(error) => { - return Err(crate::util::into_endpoint_failure(error) + return Err(VpnApiErrorResponse::try_from(error) .map(|response| RequestZkNymError::PollZkNymEndpointFailure { response }) .unwrap_or_else(RequestZkNymError::unexpected_response)); } @@ -564,7 +564,7 @@ impl RequestZkNymTask { .confirm_zk_nym_download_by_id(&self.account, &self.device, id) .await .map_err(|err| { - crate::util::into_endpoint_failure(err) + VpnApiErrorResponse::try_from(err) .map( |response| RequestZkNymError::ConfirmZkNymDownloadEndpointFailure { response, diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs index 79621a31b7..8389a80dcc 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use nym_vpn_api_client::{response::NymVpnAccountSummaryResponse, types::VpnApiAccount}; -use nym_vpn_lib_types::SyncAccountError; +use nym_vpn_lib_types::{SyncAccountError, VpnApiErrorResponse}; use tracing::Level; use crate::shared_state::{AccountRegistered, AccountSummary, SharedAccountState}; @@ -104,7 +104,7 @@ async fn update_state( account_state .set_account_registered(AccountRegistered::NotRegistered) .await; - return Err(crate::util::into_endpoint_failure(err) + return Err(VpnApiErrorResponse::try_from(err) .map(SyncAccountError::SyncAccountEndpointFailure) .unwrap_or_else(SyncAccountError::unexpected_response)); } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs index ac73f2dc9f..1f5b5aca1d 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs @@ -7,7 +7,7 @@ use nym_vpn_api_client::{ response::NymVpnDevicesResponse, types::{Device, VpnApiAccount}, }; -use nym_vpn_lib_types::SyncDeviceError; +use nym_vpn_lib_types::{SyncDeviceError, VpnApiErrorResponse}; use tracing::Level; use crate::shared_state::{DeviceState, SharedAccountState}; @@ -104,7 +104,7 @@ async fn update_state( tracing::debug!("Updating device state"); let devices = vpn_api_client.get_devices(account).await.map_err(|err| { - crate::util::into_endpoint_failure(err) + VpnApiErrorResponse::try_from(err) .map(SyncDeviceError::SyncDeviceEndpointFailure) .unwrap_or_else(SyncDeviceError::unexpected_response) })?; diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs index a604f65f2b..8d3e82e874 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs @@ -8,7 +8,9 @@ use nym_vpn_api_client::{ response::{NymVpnAccountResponse, NymVpnDevice, NymVpnUsage}, types::{DeviceStatus, VpnApiAccount}, }; -use nym_vpn_lib_types::{AccountCommandError, ForgetAccountError, StoreAccountError}; +use nym_vpn_lib_types::{ + AccountCommandError, ForgetAccountError, StoreAccountError, VpnApiErrorResponse, +}; use nym_vpn_network_config::Network; use nym_vpn_store::{mnemonic::Mnemonic, VpnStorage}; use tokio::{ @@ -273,7 +275,7 @@ where .get_account(&account) .await .map_err(|e| { - crate::util::into_endpoint_failure(e) + VpnApiErrorResponse::try_from(e) .map(StoreAccountError::GetAccountEndpointFailure) .unwrap_or_else(|e| StoreAccountError::UnexpectedResponse(e.to_string())) .into() @@ -406,7 +408,7 @@ where .update_device(&account, &device, DeviceStatus::DeleteMe) .await .map_err(|err| { - crate::util::into_endpoint_failure(err) + VpnApiErrorResponse::try_from(err) .map(ForgetAccountError::UpdateDeviceErrorResponse) .unwrap_or_else(|err| ForgetAccountError::UnexpectedResponse(err.to_string())) .into() diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs index 7b75704f54..1cda20cb7a 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs @@ -85,15 +85,3 @@ pub fn remove_files_for_account(data_dir: &Path) -> Result<(), Error> { Ok(()) } - -pub fn into_endpoint_failure( - err: nym_vpn_api_client::VpnApiClientError, -) -> Result { - nym_vpn_api_client::response::NymErrorResponse::try_from(err).map(|res| { - nym_vpn_lib_types::VpnApiErrorResponse { - message: res.message, - message_id: res.message_id, - code_reference_id: res.code_reference_id, - } - }) -} diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs index 5fcaa44be8..036d22d7c0 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs @@ -86,3 +86,16 @@ pub struct VpnApiErrorResponse { pub message_id: Option, pub code_reference_id: Option, } + +#[cfg(feature = "nym-type-conversions")] +impl TryFrom for VpnApiErrorResponse { + type Error = nym_vpn_api_client::VpnApiClientError; + + fn try_from(err: nym_vpn_api_client::VpnApiClientError) -> Result { + nym_vpn_api_client::response::NymErrorResponse::try_from(err).map(|res| Self { + message: res.message, + message_id: res.message_id, + code_reference_id: res.code_reference_id, + }) + } +} From f67cbd672a978571fef24f72eb289a0f35274544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 11 Feb 2025 00:00:01 +0100 Subject: [PATCH 4/9] Rename to storage_cleanup --- .../crates/nym-vpn-account-controller/src/controller.rs | 2 +- nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs | 2 +- .../src/{util.rs => storage_cleanup.rs} | 0 nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs | 8 ++++---- 4 files changed, 6 insertions(+), 6 deletions(-) rename nym-vpn-core/crates/nym-vpn-account-controller/src/{util.rs => storage_cleanup.rs} (100%) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs index 8d3e82e874..9eb55b1bd7 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs @@ -346,7 +346,7 @@ where // Purge all files in the data directory that we are not explicitly deleting through it's // owner. Ideally we should strive for this to be removed. // If this fails, we still need to continue with the remaining steps - let remove_files_result = crate::util::remove_files_for_account(&self.data_dir) + let remove_files_result = crate::storage_cleanup::remove_files_for_account(&self.data_dir) .inspect_err(|err| { tracing::error!("Failed to remove files for account: {err:?}"); }); diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs index 6419772e10..cdecbc53d9 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/lib.rs @@ -8,7 +8,7 @@ // 3. request ticketbooks and top up the local credential store pub mod shared_state; -pub mod util; +pub mod storage_cleanup; mod commander; mod commands; diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/storage_cleanup.rs similarity index 100% rename from nym-vpn-core/crates/nym-vpn-account-controller/src/util.rs rename to nym-vpn-core/crates/nym-vpn-account-controller/src/storage_cleanup.rs diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs index 4825db6993..706a47da4e 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs @@ -373,11 +373,11 @@ pub(crate) mod raw { remove_credential_storage_raw(&path_buf).await?; // Then remove the rest of the files, that we own indirectly - nym_vpn_account_controller::util::remove_files_for_account(&path_buf).map_err(|err| { - VpnError::Storage { + nym_vpn_account_controller::storage_cleanup::remove_files_for_account(&path_buf).map_err( + |err| VpnError::Storage { details: err.to_string(), - } - })?; + }, + )?; Ok(()) } From 0df4413d34ea39be3a0acf744aa18540dfe1fcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 11 Feb 2025 00:20:09 +0100 Subject: [PATCH 5/9] rustfmt --- .../src/commands/request_zknym/handler.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs index f5ef2f30a8..b3cd7f2aa7 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs @@ -14,7 +14,9 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; -use nym_vpn_lib_types::{AccountCommandError, RequestZkNymError, RequestZkNymSuccess, VpnApiErrorResponse}; +use nym_vpn_lib_types::{ + AccountCommandError, RequestZkNymError, RequestZkNymSuccess, VpnApiErrorResponse, +}; use tokio::task::JoinSet; use crate::{ From abb7f352632c3b176d45a78bea1f079fac7ff792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 11 Feb 2025 09:31:46 +0100 Subject: [PATCH 6/9] Narrow errors returned to reduce scope of error state --- .../nym-vpn-account-controller/Cargo.toml | 2 +- .../src/commander.rs | 43 +++++----- .../src/commands/mod.rs | 78 ++++++++++++------- .../src/commands/register_device.rs | 8 +- .../src/commands/request_zknym/handler.rs | 10 +-- .../src/commands/sync_account.rs | 9 +-- .../src/commands/sync_device.rs | 5 +- .../src/controller.rs | 66 ++++------------ .../src/account/register_device.rs | 22 ++++++ .../src/account/request_zknym.rs | 14 ++++ .../src/account/sync_account.rs | 16 ++++ .../src/account/sync_device.rs | 22 ++++++ .../nym-vpn-lib-types/src/tunnel_state.rs | 61 ++++++--------- .../nym-vpn-lib/src/platform/account.rs | 20 +++-- .../src/platform/uniffi_lib_types.rs | 46 +++++++---- .../src/tunnel_state_machine/account.rs | 41 ++++++---- .../src/tunnel_state_machine/mod.rs | 5 +- .../tunnel_state_machine/tunnel_monitor.rs | 8 +- .../src/conversions/from_proto/account.rs | 34 +++++--- .../conversions/from_proto/tunnel_state.rs | 11 --- .../src/conversions/into_proto/account.rs | 6 ++ .../into_proto/account_shared_state.rs | 20 ++--- .../conversions/into_proto/tunnel_state.rs | 43 ++++++---- proto/nym/account.proto | 33 +++++--- proto/nym/vpn.proto | 15 ++-- 25 files changed, 370 insertions(+), 268 deletions(-) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml b/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml index e31d34f74b..7d6fb2de08 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml +++ b/nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml @@ -23,7 +23,7 @@ nym-http-api-client.workspace = true nym-sdk.workspace = true nym-validator-client.workspace = true nym-vpn-api-client = { path = "../nym-vpn-api-client" } -nym-vpn-lib-types = { path = "../nym-vpn-lib-types" } +nym-vpn-lib-types = { path = "../nym-vpn-lib-types", features = ["nym-type-conversions"] } nym-vpn-network-config = { path = "../nym-vpn-network-config" } nym-vpn-store = { path = "../nym-vpn-store" } nym-wg-gateway-client = { path = "../nym-wg-gateway-client" } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs index db21ff0115..2f6a0114c0 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_vpn_api_client::response::{NymVpnAccountSummaryResponse, NymVpnDevice, NymVpnUsage}; -use nym_vpn_lib_types::AccountCommandError; +use nym_vpn_lib_types::{ + AccountCommandError, RegisterDeviceError, RequestZkNymError, SyncAccountError, SyncDeviceError, +}; use nym_vpn_store::mnemonic::Mnemonic; use tokio::sync::mpsc::UnboundedSender; @@ -55,20 +57,20 @@ impl AccountControllerCommander { pub async fn sync_account_state( &self, - ) -> Result { + ) -> Result { let (tx, rx) = ReturnSender::new(); self.command_tx .send(AccountCommand::SyncAccountState(Some(tx))) - .map_err(AccountCommandError::internal)?; - rx.await.map_err(AccountCommandError::internal)? + .map_err(SyncAccountError::internal)?; + rx.await.map_err(SyncAccountError::internal)? } - pub async fn sync_device_state(&self) -> Result { + pub async fn sync_device_state(&self) -> Result { let (tx, rx) = ReturnSender::new(); self.command_tx .send(AccountCommand::SyncDeviceState(Some(tx))) - .map_err(AccountCommandError::internal)?; - rx.await.map_err(AccountCommandError::internal)? + .map_err(SyncDeviceError::internal)?; + rx.await.map_err(SyncDeviceError::internal)? } pub async fn get_usage(&self) -> Result, AccountCommandError> { @@ -87,12 +89,12 @@ impl AccountControllerCommander { rx.await.map_err(AccountCommandError::internal)? } - pub async fn register_device(&self) -> Result { + pub async fn register_device(&self) -> Result { let (tx, rx) = ReturnSender::new(); self.command_tx .send(AccountCommand::RegisterDevice(Some(tx))) - .map_err(AccountCommandError::internal)?; - rx.await.map_err(AccountCommandError::internal)? + .map_err(RegisterDeviceError::internal)?; + rx.await.map_err(RegisterDeviceError::internal)? } pub async fn get_devices(&self) -> Result, AccountCommandError> { @@ -119,12 +121,12 @@ impl AccountControllerCommander { rx.await.map_err(AccountCommandError::internal)? } - pub async fn request_zk_nyms(&self) -> Result { + pub async fn request_zk_nyms(&self) -> Result { let (tx, rx) = ReturnSender::new(); self.command_tx .send(AccountCommand::RequestZkNym(Some(tx))) - .map_err(AccountCommandError::internal)?; - rx.await.map_err(AccountCommandError::internal)? + .map_err(RequestZkNymError::internal)?; + rx.await.map_err(RequestZkNymError::internal)? } } @@ -134,7 +136,7 @@ impl AccountControllerCommander { impl AccountControllerCommander { pub async fn ensure_update_account( &self, - ) -> Result, AccountCommandError> { + ) -> Result, SyncAccountError> { tracing::debug!("Ensuring account is synced"); let state = self.shared_state.lock().await.clone(); match state.account_registered { @@ -144,7 +146,7 @@ impl AccountControllerCommander { self.sync_account_state().await.map(Some) } - pub async fn ensure_update_device(&self) -> Result { + pub async fn ensure_update_device(&self) -> Result { tracing::debug!("Ensuring device is synced"); let state = self.shared_state.lock().await.clone(); match state.device { @@ -157,7 +159,7 @@ impl AccountControllerCommander { self.sync_device_state().await } - pub async fn ensure_register_device(&self) -> Result<(), AccountCommandError> { + pub async fn ensure_register_device(&self) -> Result<(), RegisterDeviceError> { tracing::debug!("Ensuring device is registered"); let state = self.shared_state.lock().await.clone(); match state.device { @@ -170,11 +172,12 @@ impl AccountControllerCommander { self.register_device().await.map(|_device| ()) } - pub async fn ensure_available_zk_nyms(&self) -> Result<(), AccountCommandError> { + pub async fn ensure_available_zk_nyms(&self) -> Result<(), RequestZkNymError> { tracing::debug!("Ensuring available zk-nyms in the local credential store"); if self .get_available_tickets() - .await? + .await + .map_err(|err| RequestZkNymError::CredentialStorage(err.to_string()))? .is_all_ticket_types_above_threshold(0) { // If all ticket types are above zero, we're good to go. Additional ticketbooks will @@ -186,8 +189,8 @@ impl AccountControllerCommander { let results = self.request_zk_nyms().await?; // If any of them failed, return an error - if results.iter().any(Result::is_err) { - Err(AccountCommandError::from(results)) + if let Some(Err(err)) = results.into_iter().find(Result::is_err) { + Err(err.clone()) } else { Ok(()) } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs index 7511b52f0e..3a65c99177 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs @@ -6,7 +6,9 @@ pub(crate) mod request_zknym; pub(crate) mod sync_account; pub(crate) mod sync_device; -use nym_vpn_lib_types::AccountCommandError; +use nym_vpn_lib_types::{ + AccountCommandError, RegisterDeviceError, RequestZkNymError, SyncAccountError, SyncDeviceError, +}; use nym_vpn_store::mnemonic::Mnemonic; use request_zknym::RequestZkNymSummary; @@ -15,7 +17,7 @@ use std::{collections::HashMap, sync::Arc}; use nym_vpn_api_client::response::{NymVpnAccountSummaryResponse, NymVpnDevice, NymVpnUsage}; use tokio::sync::oneshot; -use crate::{shared_state::DeviceState, AvailableTicketbooks}; +use crate::{shared_state::DeviceState, AvailableTicketbooks, Error}; #[derive(Debug, Default)] pub(crate) struct RunningCommands { @@ -51,22 +53,24 @@ impl RunningCommands { } #[derive(Debug)] -pub struct ReturnSender { - sender: oneshot::Sender>, +pub struct ReturnSender { + sender: oneshot::Sender>, } -impl ReturnSender +impl ReturnSender where T: std::fmt::Debug, + E: std::fmt::Debug, { - pub fn new() -> (Self, oneshot::Receiver>) { + pub fn new() -> (Self, oneshot::Receiver>) { let (sender, receiver) = oneshot::channel(); (Self { sender }, receiver) } - pub fn send(self, response: Result) + pub fn send(self, response: Result) where T: Send, + E: Send, { self.sender .send(response) @@ -79,21 +83,21 @@ where #[derive(Debug, strum::Display)] pub enum AccountCommand { - StoreAccount(ReturnSender<()>, Mnemonic), - ForgetAccount(ReturnSender<()>), - SyncAccountState(Option>), - SyncDeviceState(Option>), - GetUsage(ReturnSender>), - GetDeviceIdentity(ReturnSender), - RegisterDevice(Option>), - GetDevices(ReturnSender>), - GetActiveDevices(ReturnSender>), - RequestZkNym(Option>), + StoreAccount(ReturnSender<(), AccountCommandError>, Mnemonic), + ForgetAccount(ReturnSender<(), AccountCommandError>), + SyncAccountState(Option>), + SyncDeviceState(Option>), + GetUsage(ReturnSender, AccountCommandError>), + GetDeviceIdentity(ReturnSender), + RegisterDevice(Option>), + GetDevices(ReturnSender, AccountCommandError>), + GetActiveDevices(ReturnSender, AccountCommandError>), + RequestZkNym(Option>), GetDeviceZkNym, GetZkNymsAvailableForDownload, GetZkNymById(String), ConfirmZkNymIdDownloaded(String), - GetAvailableTickets(ReturnSender), + GetAvailableTickets(ReturnSender), } impl AccountCommand { @@ -101,34 +105,48 @@ impl AccountCommand { self.to_string() } - pub fn return_error(self, error: AccountCommandError) { - tracing::warn!("Returning error: {:?}", error); + pub fn return_no_account(self, error: Error) { + tracing::warn!("No account found: {error}"); match self { AccountCommand::SyncAccountState(Some(tx)) => { - tx.send(Err(error)); + tx.send(Err(SyncAccountError::NoAccountStored)); } AccountCommand::SyncDeviceState(Some(tx)) => { - tx.send(Err(error)); + tx.send(Err(SyncDeviceError::NoAccountStored)); } AccountCommand::RegisterDevice(Some(tx)) => { - tx.send(Err(error)); + tx.send(Err(RegisterDeviceError::NoAccountStored)); } AccountCommand::RequestZkNym(Some(tx)) => { - tx.send(Err(error)); + tx.send(Err(RequestZkNymError::NoAccountStored)); + } + _ => {} + } + } + + pub fn return_no_device(self, error: Error) { + tracing::warn!("No device found: {error}"); + match self { + AccountCommand::SyncDeviceState(Some(tx)) => { + tx.send(Err(SyncDeviceError::NoDeviceStored)); + } + AccountCommand::RegisterDevice(Some(tx)) => { + tx.send(Err(RegisterDeviceError::NoDeviceStored)); + } + AccountCommand::RequestZkNym(Some(tx)) => { + tx.send(Err(RequestZkNymError::NoDeviceStored)); } _ => {} } } } -// WIP: Fix this clippy -#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub(crate) enum AccountCommandResult { - SyncAccountState(Result), - SyncDeviceState(Result), - RegisterDevice(Result), - RequestZkNym(Result), + SyncAccountState(Result), + SyncDeviceState(Result), + RegisterDevice(Result), + RequestZkNym(Result), } #[cfg(test)] diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs index fb199cb861..3f91cd39c6 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/register_device.rs @@ -13,7 +13,7 @@ use crate::{ SharedAccountState, }; -use super::{AccountCommandError, AccountCommandResult}; +use super::AccountCommandResult; pub(crate) struct RegisterDeviceCommandHandler { id: uuid::Uuid, @@ -55,14 +55,14 @@ impl RegisterDeviceCommandHandler { ret, err, )] - async fn register_device(self) -> Result { + async fn register_device(self) -> Result { tracing::debug!("Running register device command handler: {}", self.id); // Defensive check for something that should not be possible if let Some(RegisterDeviceResult::InProgress) = self.account_state.lock().await.register_device_result { - return Err(AccountCommandError::internal( + return Err(RegisterDeviceError::internal( "duplicate register device command", )); } @@ -86,7 +86,7 @@ impl RegisterDeviceCommandHandler { self.account_state .set_device_registration(RegisterDeviceResult::Failed(err.clone())) .await; - Err(AccountCommandError::RegisterDevice(err)) + Err(err) } } } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs index b3cd7f2aa7..d51d2c740b 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/handler.rs @@ -14,9 +14,7 @@ use nym_vpn_api_client::{ types::{Device, VpnApiAccount}, VpnApiClient, }; -use nym_vpn_lib_types::{ - AccountCommandError, RequestZkNymError, RequestZkNymSuccess, VpnApiErrorResponse, -}; +use nym_vpn_lib_types::{RequestZkNymError, RequestZkNymSuccess, VpnApiErrorResponse}; use tokio::task::JoinSet; use crate::{ @@ -116,12 +114,12 @@ impl RequestZkNymCommandHandler { ret, err, )] - async fn request_zk_nyms_outer(self) -> Result { + async fn request_zk_nyms_outer(self) -> Result { tracing::debug!("Running zk-nym request command handler: {}", self.id); // Defensive check for something that should not be possible if self.account_state.is_zk_nym_request_in_progress().await { - return Err(AccountCommandError::internal( + return Err(RequestZkNymError::internal( "duplicate zk-nym request command", )); } @@ -141,7 +139,7 @@ impl RequestZkNymCommandHandler { self.account_state .set_zk_nym_request(RequestZkNymResult::from(err.clone())) .await; - Err(AccountCommandError::from(err)) + Err(err) } } } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs index 8389a80dcc..66a30ee804 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_account.rs @@ -9,7 +9,7 @@ use tracing::Level; use crate::shared_state::{AccountRegistered, AccountSummary, SharedAccountState}; -use super::{AccountCommandError, AccountCommandResult}; +use super::AccountCommandResult; type PreviousAccountSummaryResponse = Arc>>; @@ -71,9 +71,7 @@ impl SyncStateCommandHandler { err, level = Level::DEBUG, )] - pub(crate) async fn run_inner( - self, - ) -> Result { + pub(crate) async fn run_inner(self) -> Result { tracing::debug!("Running sync account state command handler: {}", self.id); let update_result = update_state( &self.account, @@ -81,8 +79,7 @@ impl SyncStateCommandHandler { &self.vpn_api_client, &self.previous_account_summary_response, ) - .await - .map_err(AccountCommandError::SyncAccount); + .await; tracing::debug!("Current state: {:?}", self.account_state.lock().await); update_result diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs index 1f5b5aca1d..2bcf881904 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/sync_device.rs @@ -12,7 +12,7 @@ use tracing::Level; use crate::shared_state::{DeviceState, SharedAccountState}; -use super::{AccountCommandError, AccountCommandResult}; +use super::AccountCommandResult; type PreviousDevicesResponse = Arc>>; @@ -80,7 +80,7 @@ impl SyncDeviceStateCommandHandler { err, level = Level::DEBUG, )] - async fn run_inner(self) -> Result { + async fn run_inner(self) -> Result { tracing::debug!("Running sync device state command handler: {}", self.id); update_state( &self.account, @@ -90,7 +90,6 @@ impl SyncDeviceStateCommandHandler { &self.previous_devices_response, ) .await - .map_err(AccountCommandError::SyncDevice) } } diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs index 9eb55b1bd7..21d8e183cd 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs @@ -178,29 +178,18 @@ where } async fn handle_request_zk_nym(&mut self, command: AccountCommand) { - let account = self - .update_mnemonic_state() - .await - .map_err(|_err| AccountCommandError::NoAccountStored); - - let account = match account { + let account = match self.update_mnemonic_state().await { Ok(account) => account, Err(err) => { - command.return_error(err); + command.return_no_account(err); return; } }; - let device = self - .account_storage - .load_device_keys() - .await - .map_err(|_err| AccountCommandError::NoDeviceStored); - - let device = match device { + let device = match self.account_storage.load_device_keys().await { Ok(device) => device, Err(err) => { - command.return_error(err); + command.return_no_device(err); return; } }; @@ -416,15 +405,10 @@ where } async fn handle_sync_account_state(&mut self, command: AccountCommand) { - let account = self - .update_mnemonic_state() - .await - .map_err(|_err| AccountCommandError::NoAccountStored); - - let account = match account { + let account = match self.update_mnemonic_state().await { Ok(account) => account, Err(err) => { - command.return_error(err); + command.return_no_account(err); return; } }; @@ -437,29 +421,18 @@ where } async fn handle_sync_device_state(&mut self, command: AccountCommand) { - let account = self - .update_mnemonic_state() - .await - .map_err(|_err| AccountCommandError::NoAccountStored); - - let account = match account { + let account = match self.update_mnemonic_state().await { Ok(account) => account, Err(err) => { - command.return_error(err); + command.return_no_account(err); return; } }; - let device = self - .account_storage - .load_device_keys() - .await - .map_err(|_err| AccountCommandError::NoDeviceStored); - - let device = match device { + let device = match self.account_storage.load_device_keys().await { Ok(device) => device, Err(err) => { - command.return_error(err); + command.return_no_device(err); return; } }; @@ -500,29 +473,18 @@ where } async fn handle_register_device(&mut self, command: AccountCommand) { - let account = self - .update_mnemonic_state() - .await - .map_err(|_err| AccountCommandError::NoAccountStored); - - let account = match account { + let account = match self.update_mnemonic_state().await { Ok(account) => account, Err(err) => { - command.return_error(err); + command.return_no_account(err); return; } }; - let device = self - .account_storage - .load_device_keys() - .await - .map_err(|_err| AccountCommandError::NoDeviceStored); - - let device = match device { + let device = match self.account_storage.load_device_keys().await { Ok(device) => device, Err(err) => { - command.return_error(err); + command.return_no_device(err); return; } }; diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs index 5e1e330527..d70a36db6a 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/register_device.rs @@ -5,11 +5,20 @@ use super::VpnApiErrorResponse; #[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] pub enum RegisterDeviceError { + #[error("no account stored")] + NoAccountStored, + + #[error("no device stored")] + NoDeviceStored, + #[error("failed to register device: {0}")] RegisterDeviceEndpointFailure(VpnApiErrorResponse), #[error("unexpected response: {0}")] UnexpectedResponse(String), + + #[error("internal error: {0}")] + Internal(String), } impl RegisterDeviceError { @@ -17,28 +26,41 @@ impl RegisterDeviceError { RegisterDeviceError::UnexpectedResponse(message.to_string()) } + pub fn internal(message: impl ToString) -> Self { + RegisterDeviceError::Internal(message.to_string()) + } + pub fn message(&self) -> String { match self { + RegisterDeviceError::NoAccountStored => self.to_string(), + RegisterDeviceError::NoDeviceStored => self.to_string(), RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => failure.message.clone(), RegisterDeviceError::UnexpectedResponse(message) => message.clone(), + RegisterDeviceError::Internal(_) => self.to_string(), } } pub fn message_id(&self) -> Option { match self { + RegisterDeviceError::NoAccountStored => None, + RegisterDeviceError::NoDeviceStored => None, RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { failure.message_id.clone() } RegisterDeviceError::UnexpectedResponse(_) => None, + RegisterDeviceError::Internal(_) => None, } } pub fn code_reference_id(&self) -> Option { match self { + RegisterDeviceError::NoAccountStored => None, + RegisterDeviceError::NoDeviceStored => None, RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { failure.code_reference_id.clone() } RegisterDeviceError::UnexpectedResponse(_) => None, + RegisterDeviceError::Internal(_) => None, } } } diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs index 3ca472adb4..185d8c5ae1 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/request_zknym.rs @@ -5,6 +5,12 @@ use super::VpnApiErrorResponse; #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] pub enum RequestZkNymError { + #[error("no account stored")] + NoAccountStored, + + #[error("no device stored")] + NoDeviceStored, + #[error("failed to get zk-nyms available for download: {response}")] GetZkNymsAvailableForDownloadEndpointFailure { response: VpnApiErrorResponse }, @@ -158,6 +164,12 @@ impl RequestZkNymError { // Simplified version of the error enum suitable for app API #[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] pub enum RequestZkNymErrorReason { + #[error("no account stored")] + NoAccountStored, + + #[error("no device stored")] + NoDeviceStored, + #[error(transparent)] VpnApi(VpnApiErrorResponse), @@ -174,6 +186,8 @@ pub enum RequestZkNymErrorReason { impl From for RequestZkNymErrorReason { fn from(err: RequestZkNymError) -> Self { match err { + RequestZkNymError::NoAccountStored => Self::NoAccountStored, + RequestZkNymError::NoDeviceStored => Self::NoDeviceStored, RequestZkNymError::GetZkNymsAvailableForDownloadEndpointFailure { response } | RequestZkNymError::RequestZkNymEndpointFailure { response, diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs index e02e24bc40..1658d12071 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_account.rs @@ -5,11 +5,17 @@ use super::VpnApiErrorResponse; #[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)] pub enum SyncAccountError { + #[error("no account stored")] + NoAccountStored, + #[error("vpn api endpoint failure: {0}")] SyncAccountEndpointFailure(VpnApiErrorResponse), #[error("unexpected response: {0}")] UnexpectedResponse(String), + + #[error("internal error: {0}")] + Internal(String), } impl SyncAccountError { @@ -17,26 +23,36 @@ impl SyncAccountError { SyncAccountError::UnexpectedResponse(err.to_string()) } + pub fn internal(err: impl ToString) -> Self { + SyncAccountError::Internal(err.to_string()) + } + pub fn message(&self) -> String { match self { + SyncAccountError::NoAccountStored => self.to_string(), SyncAccountError::SyncAccountEndpointFailure(failure) => failure.message.clone(), SyncAccountError::UnexpectedResponse(response) => response.to_string(), + SyncAccountError::Internal(_) => self.to_string(), } } pub fn message_id(&self) -> Option { match self { + SyncAccountError::NoAccountStored => None, SyncAccountError::SyncAccountEndpointFailure(failure) => failure.message_id.clone(), SyncAccountError::UnexpectedResponse(_) => None, + SyncAccountError::Internal(_) => None, } } pub fn code_reference_id(&self) -> Option { match self { + SyncAccountError::NoAccountStored => None, SyncAccountError::SyncAccountEndpointFailure(failure) => { failure.code_reference_id.clone() } SyncAccountError::UnexpectedResponse(_) => None, + SyncAccountError::Internal(_) => None, } } } diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs index 4f0bc0eed6..49e0189ca3 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/sync_device.rs @@ -5,11 +5,20 @@ use super::VpnApiErrorResponse; #[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)] pub enum SyncDeviceError { + #[error("no account stored")] + NoAccountStored, + + #[error("no device stored")] + NoDeviceStored, + #[error("vpn api endpoint failure: {0}")] SyncDeviceEndpointFailure(VpnApiErrorResponse), #[error("unexpected response: {0}")] UnexpectedResponse(String), + + #[error("internal error: {0}")] + Internal(String), } impl SyncDeviceError { @@ -17,26 +26,39 @@ impl SyncDeviceError { SyncDeviceError::UnexpectedResponse(err.to_string()) } + pub fn internal(err: impl ToString) -> Self { + SyncDeviceError::Internal(err.to_string()) + } + pub fn message(&self) -> String { match self { + SyncDeviceError::NoAccountStored => self.to_string(), + SyncDeviceError::NoDeviceStored => self.to_string(), SyncDeviceError::SyncDeviceEndpointFailure(failure) => failure.message.clone(), SyncDeviceError::UnexpectedResponse(response) => response.to_string(), + SyncDeviceError::Internal(_) => self.to_string(), } } pub fn message_id(&self) -> Option { match self { + SyncDeviceError::NoAccountStored => None, + SyncDeviceError::NoDeviceStored => None, SyncDeviceError::SyncDeviceEndpointFailure(failure) => failure.message_id.clone(), SyncDeviceError::UnexpectedResponse(_) => None, + SyncDeviceError::Internal(_) => None, } } pub fn code_reference_id(&self) -> Option { match self { + SyncDeviceError::NoAccountStored => None, + SyncDeviceError::NoDeviceStored => None, SyncDeviceError::SyncDeviceEndpointFailure(failure) => { failure.code_reference_id.clone() } SyncDeviceError::UnexpectedResponse(_) => None, + SyncDeviceError::Internal(_) => None, } } } diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs index 68e67f61d9..758a278441 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/tunnel_state.rs @@ -3,12 +3,11 @@ use std::fmt; -use crate::{account::AccountCommandError, RequestZkNymErrorReason}; +use crate::{RequestZkNymError, RequestZkNymErrorReason}; use super::{ account::{ - forget_account::ForgetAccountError, register_device::RegisterDeviceError, - request_zknym::RequestZkNymSuccess, store_account::StoreAccountError, + register_device::RegisterDeviceError, request_zknym::RequestZkNymSuccess, sync_account::SyncAccountError, sync_device::SyncDeviceError, }, connection_data::{ConnectionData, TunnelConnectionData}, @@ -159,18 +158,6 @@ pub enum ErrorStateReason { /// Failure to duplicate tunnel file descriptor. DuplicateTunFd, - /// Account related error not specifically handled. - Account(String), - - /// Failure to perform the requested action due to no account stored. - NoAccountStored, - - /// Failure to perform the requested action due to no device stored. - NoDeviceStored, - - /// Failure to store account. - StoreAccount(StoreAccountError), - /// Failure to sync account with the VPN API. SyncAccount(SyncAccountError), @@ -189,32 +176,30 @@ pub enum ErrorStateReason { failed: Vec, }, - /// Failure to forget account. - ForgetAccount(ForgetAccountError), - /// Program errors that must not happen. Internal, } -impl From for ErrorStateReason { - fn from(value: AccountCommandError) -> Self { - match value { - AccountCommandError::General(err) => ErrorStateReason::Account(err), - AccountCommandError::Internal(err) => ErrorStateReason::Account(err), - AccountCommandError::NoAccountStored => ErrorStateReason::NoAccountStored, - AccountCommandError::NoDeviceStored => ErrorStateReason::NoDeviceStored, - AccountCommandError::StoreAccount(err) => ErrorStateReason::StoreAccount(err), - AccountCommandError::SyncAccount(err) => ErrorStateReason::SyncAccount(err), - AccountCommandError::SyncDevice(err) => ErrorStateReason::SyncDevice(err), - AccountCommandError::RegisterDevice(err) => ErrorStateReason::RegisterDevice(err), - AccountCommandError::RequestZkNym(err) => ErrorStateReason::RequestZkNym(err.into()), - AccountCommandError::RequestZkNymBundle { successes, failed } => { - ErrorStateReason::RequestZkNymBundle { - successes, - failed: failed.into_iter().map(Into::into).collect(), - } - } - AccountCommandError::ForgetAccount(err) => ErrorStateReason::ForgetAccount(err), - } +impl From for ErrorStateReason { + fn from(value: SyncAccountError) -> Self { + ErrorStateReason::SyncAccount(value) + } +} + +impl From for ErrorStateReason { + fn from(value: SyncDeviceError) -> Self { + ErrorStateReason::SyncDevice(value) + } +} + +impl From for ErrorStateReason { + fn from(value: RegisterDeviceError) -> Self { + ErrorStateReason::RegisterDevice(value) + } +} + +impl From for ErrorStateReason { + fn from(value: RequestZkNymError) -> Self { + ErrorStateReason::RequestZkNym(value.into()) } } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs index 706a47da4e..05fc68cd96 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs @@ -145,7 +145,9 @@ pub(super) async fn wait_for_update_account( .await? .ensure_update_account() .await - .map_err(VpnError::from) + .map_err(|err| VpnError::SyncAccount { + details: err.into(), + }) } pub(super) async fn wait_for_update_device() -> Result { @@ -153,7 +155,9 @@ pub(super) async fn wait_for_update_device() -> Result { .await? .ensure_update_device() .await - .map_err(VpnError::from) + .map_err(|err| VpnError::SyncDevice { + details: err.into(), + }) } pub(super) async fn wait_for_register_device() -> Result<(), VpnError> { @@ -161,7 +165,9 @@ pub(super) async fn wait_for_register_device() -> Result<(), VpnError> { .await? .ensure_register_device() .await - .map_err(VpnError::from) + .map_err(|err| VpnError::RegisterDevice { + details: err.into(), + }) } pub(super) async fn wait_for_available_zk_nyms() -> Result<(), VpnError> { @@ -169,7 +175,9 @@ pub(super) async fn wait_for_available_zk_nyms() -> Result<(), VpnError> { .await? .ensure_available_zk_nyms() .await - .map_err(VpnError::from) + .map_err(|err| VpnError::RequestZkNym { + details: err.into(), + }) } pub(super) async fn wait_for_account_ready_to_connect( @@ -197,7 +205,9 @@ pub(super) async fn update_account_state() -> Result<(), VpnError> { .await? .sync_account_state() .await - .map_err(VpnError::from) + .map_err(|err| VpnError::SyncAccount { + details: err.into(), + }) .map(|_| ()) } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs index ab5b6a3533..cdd99de1a2 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/uniffi_lib_types.rs @@ -231,12 +231,6 @@ pub enum ErrorStateReason { InvalidExitGatewayCountry, BadBandwidthIncrease, DuplicateTunFd, - Internal, - - Account(String), - NoAccountStored, - NoDeviceStored, - StoreAccountError(StoreAccountError), SyncAccount(SyncAccountError), SyncDevice(SyncDeviceError), RegisterDevice(RegisterDeviceError), @@ -245,7 +239,7 @@ pub enum ErrorStateReason { successes: Vec, failed: Vec, }, - ForgetAccount(ForgetAccountError), + Internal, } #[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq, Eq)] @@ -274,61 +268,85 @@ impl From for StoreAccountError { #[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] pub enum SyncAccountError { + #[error("no account stored")] + NoAccountStored, #[error("vpn api endpoint failure: {0}")] ErrorResponse(VpnApiErrorResponse), #[error("unexpected response: {0}")] UnexpectedResponse(String), + #[error("internal error: {0}")] + Internal(String), } impl From for SyncAccountError { fn from(value: CoreSyncAccountError) -> Self { match value { + CoreSyncAccountError::NoAccountStored => Self::NoAccountStored, CoreSyncAccountError::SyncAccountEndpointFailure(failure) => { Self::ErrorResponse(failure.into()) } CoreSyncAccountError::UnexpectedResponse(response) => { Self::UnexpectedResponse(response) } + CoreSyncAccountError::Internal(err) => Self::Internal(err), } } } #[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] pub enum SyncDeviceError { + #[error("no account stored")] + NoAccountStored, + #[error("no device stored")] + NoDeviceStored, #[error("vpn api endpoint failure: {0}")] ErrorResponse(VpnApiErrorResponse), #[error("unexpected response: {0}")] UnexpectedResponse(String), + #[error("internal error: {0}")] + Internal(String), } impl From for SyncDeviceError { fn from(value: CoreSyncDeviceError) -> Self { match value { + CoreSyncDeviceError::NoAccountStored => Self::NoAccountStored, + CoreSyncDeviceError::NoDeviceStored => Self::NoDeviceStored, CoreSyncDeviceError::SyncDeviceEndpointFailure(failure) => { Self::ErrorResponse(failure.into()) } CoreSyncDeviceError::UnexpectedResponse(response) => Self::UnexpectedResponse(response), + CoreSyncDeviceError::Internal(err) => Self::Internal(err), } } } #[derive(thiserror::Error, uniffi::Error, Debug, Clone, PartialEq)] pub enum RegisterDeviceError { + #[error("no account stored")] + NoAccountStored, + #[error("no device stored")] + NoDeviceStored, #[error("vpn api endpoint failure: {0}")] ErrorResponse(VpnApiErrorResponse), #[error("unexpected response: {0}")] UnexpectedResponse(String), + #[error("internal error: {0}")] + Internal(String), } impl From for RegisterDeviceError { fn from(value: CoreRegisterDeviceError) -> Self { match value { + CoreRegisterDeviceError::NoAccountStored => Self::NoAccountStored, + CoreRegisterDeviceError::NoDeviceStored => Self::NoDeviceStored, CoreRegisterDeviceError::RegisterDeviceEndpointFailure(failure) => { Self::ErrorResponse(failure.into()) } CoreRegisterDeviceError::UnexpectedResponse(response) => { Self::UnexpectedResponse(response) } + CoreRegisterDeviceError::Internal(err) => Self::Internal(err), } } } @@ -346,6 +364,10 @@ impl From for RequestZkNymSuccess { #[derive(uniffi::Error, thiserror::Error, Clone, Debug, PartialEq, Eq)] pub enum RequestZkNymError { + #[error("no account stored")] + NoAccountStored, + #[error("no device stored")] + NoDeviceStored, #[error(transparent)] VpnApi(VpnApiErrorResponse), #[error("nym-vpn-api: unexpected error response: {0}")] @@ -359,6 +381,8 @@ pub enum RequestZkNymError { impl From for RequestZkNymError { fn from(error: CoreRequestZkNymErrorReason) -> Self { match error { + CoreRequestZkNymErrorReason::NoAccountStored => Self::NoAccountStored, + CoreRequestZkNymErrorReason::NoDeviceStored => Self::NoDeviceStored, CoreRequestZkNymErrorReason::VpnApi(err) => Self::VpnApi(err.into()), CoreRequestZkNymErrorReason::UnexpectedVpnApiResponse(response) => { Self::UnexpectedVpnApiResponse(response) @@ -459,12 +483,6 @@ impl From for ErrorStateReason { CoreErrorStateReason::InvalidExitGatewayCountry => Self::InvalidExitGatewayCountry, CoreErrorStateReason::BadBandwidthIncrease => Self::BadBandwidthIncrease, CoreErrorStateReason::DuplicateTunFd => Self::DuplicateTunFd, - CoreErrorStateReason::Internal => Self::Internal, - - CoreErrorStateReason::Account(err) => Self::Account(err), - CoreErrorStateReason::NoAccountStored => Self::NoAccountStored, - CoreErrorStateReason::NoDeviceStored => Self::NoDeviceStored, - CoreErrorStateReason::StoreAccount(err) => Self::StoreAccountError(err.into()), CoreErrorStateReason::SyncAccount(err) => Self::SyncAccount(err.into()), CoreErrorStateReason::SyncDevice(err) => Self::SyncDevice(err.into()), CoreErrorStateReason::RegisterDevice(err) => Self::RegisterDevice(err.into()), @@ -478,7 +496,7 @@ impl From for ErrorStateReason { failed: failed.into_iter().map(RequestZkNymError::from).collect(), } } - CoreErrorStateReason::ForgetAccount(err) => Self::ForgetAccount(err.into()), + CoreErrorStateReason::Internal => Self::Internal, } } } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs index a28da851af..5447647654 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/account.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_vpn_account_controller::AccountControllerCommander; -use nym_vpn_lib_types::AccountCommandError; +use nym_vpn_lib_types::{ + RegisterDeviceError, RequestZkNymError, SyncAccountError, SyncDeviceError, +}; use tokio_util::sync::CancellationToken; #[derive(Debug, thiserror::Error)] @@ -11,31 +13,40 @@ pub enum Error { Cancelled, #[error("account error: {0}")] - Account(#[from] AccountCommandError), + SyncAccount(#[from] SyncAccountError), + + #[error("device error: {0}")] + SyncDevice(#[from] SyncDeviceError), + + #[error("register device error: {0}")] + RegisterDevice(#[from] RegisterDeviceError), + + #[error("request zk nym error: {0}")] + RequestZkNym(#[from] RequestZkNymError), } -async fn wait_for_sync_inner( +pub async fn wait_for_account_sync( account_controller_tx: AccountControllerCommander, -) -> Result<(), AccountCommandError> { - account_controller_tx - .ensure_update_account() - .await - .map(|_| ())?; - account_controller_tx - .ensure_update_device() + cancel_token: CancellationToken, +) -> Result<(), Error> { + cancel_token + .run_until_cancelled(account_controller_tx.ensure_update_account()) .await + .ok_or(Error::Cancelled)? + .map_err(Error::SyncAccount) .map(|_| ()) } -pub async fn wait_for_sync( +pub async fn wait_for_device_sync( account_controller_tx: AccountControllerCommander, cancel_token: CancellationToken, ) -> Result<(), Error> { cancel_token - .run_until_cancelled(wait_for_sync_inner(account_controller_tx)) + .run_until_cancelled(account_controller_tx.ensure_update_device()) .await .ok_or(Error::Cancelled)? - .map_err(Error::Account) + .map_err(Error::SyncDevice) + .map(|_| ()) } pub async fn wait_for_device_register( @@ -46,7 +57,7 @@ pub async fn wait_for_device_register( .run_until_cancelled(account_controller_tx.ensure_register_device()) .await .ok_or(Error::Cancelled)? - .map_err(Error::Account) + .map_err(Error::RegisterDevice) } // Waiting for credentials to be ready can take a while if it's from scratch, in the order of 30 @@ -59,5 +70,5 @@ pub async fn wait_for_credentials_ready( .run_until_cancelled(account_controller_tx.ensure_available_zk_nyms()) .await .ok_or(Error::Cancelled)? - .map_err(Error::Account) + .map_err(Error::RequestZkNym) } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs index eb5a1f11d1..f334ccdf32 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs @@ -562,7 +562,10 @@ impl tunnel::Error { impl account::Error { fn error_state_reason(self) -> Option { match self { - Self::Account(e) => Some(e.into()), + Self::SyncAccount(e) => Some(e.into()), + Self::SyncDevice(e) => Some(e.into()), + Self::RegisterDevice(e) => Some(e.into()), + Self::RequestZkNym(e) => Some(e.into()), Self::Cancelled => None, } } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs index 535400678f..93535d1657 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs @@ -407,7 +407,13 @@ impl TunnelMonitor { async fn setup_account(&mut self) -> Result<()> { self.send_event(TunnelMonitorEvent::SyncingAccount); - account::wait_for_sync( + account::wait_for_account_sync( + self.account_controller_tx.clone(), + self.cancel_token.clone(), + ) + .await?; + + account::wait_for_device_sync( self.account_controller_tx.clone(), self.cancel_token.clone(), ) diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs index 4a9a27854f..093815636b 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs @@ -25,9 +25,9 @@ impl TryFrom for StoreAccountError { type Error = ConversionError; fn try_from(value: ProtoStoreAccountError) -> Result { - let error_detail = value - .error_detail - .ok_or(ConversionError::NoValueSet("StoreAccountError"))?; + let error_detail = value.error_detail.ok_or(ConversionError::NoValueSet( + "StoreAccountError.error_detail", + ))?; Ok(match error_detail { crate::store_account_error::ErrorDetail::StorageError(err) => Self::Storage(err), crate::store_account_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { @@ -46,14 +46,16 @@ impl TryFrom for SyncAccountError { fn try_from(value: ProtoSyncAccountError) -> Result { let error_detail = value .error_detail - .ok_or(ConversionError::NoValueSet("SyncAccountError"))?; + .ok_or(ConversionError::NoValueSet("SyncAccountError.error_detail"))?; Ok(match error_detail { + crate::sync_account_error::ErrorDetail::NoAccountStored(_) => Self::NoAccountStored, crate::sync_account_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { Self::SyncAccountEndpointFailure(vpn_api_endpoint_failure.into()) } crate::sync_account_error::ErrorDetail::UnexpectedResponse(err) => { Self::UnexpectedResponse(err) } + crate::sync_account_error::ErrorDetail::Internal(err) => Self::Internal(err), }) } } @@ -64,14 +66,17 @@ impl TryFrom for SyncDeviceError { fn try_from(value: ProtoSyncDeviceError) -> Result { let error_detail = value .error_detail - .ok_or(ConversionError::NoValueSet("SyncDeviceError"))?; + .ok_or(ConversionError::NoValueSet("SyncDeviceError.error_detail"))?; Ok(match error_detail { + crate::sync_device_error::ErrorDetail::NoAccountStored(_) => Self::NoAccountStored, + crate::sync_device_error::ErrorDetail::NoDeviceStored(_) => Self::NoDeviceStored, crate::sync_device_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { Self::SyncDeviceEndpointFailure(vpn_api_endpoint_failure.into()) } crate::sync_device_error::ErrorDetail::UnexpectedResponse(err) => { Self::UnexpectedResponse(err) } + crate::sync_device_error::ErrorDetail::Internal(err) => Self::Internal(err), }) } } @@ -80,16 +85,19 @@ impl TryFrom for RegisterDeviceError { type Error = ConversionError; fn try_from(value: ProtoRegisterDeviceError) -> Result { - let error_detail = value - .error_detail - .ok_or(ConversionError::NoValueSet("RegisterDeviceError"))?; + let error_detail = value.error_detail.ok_or(ConversionError::NoValueSet( + "RegisterDeviceError.error_detail", + ))?; Ok(match error_detail { + crate::register_device_error::ErrorDetail::NoAccountStored(_) => Self::NoAccountStored, + crate::register_device_error::ErrorDetail::NoDeviceStored(_) => Self::NoDeviceStored, crate::register_device_error::ErrorDetail::ErrorResponse(vpn_api_endpoint_failure) => { Self::RegisterDeviceEndpointFailure(vpn_api_endpoint_failure.into()) } crate::register_device_error::ErrorDetail::UnexpectedResponse(err) => { Self::UnexpectedResponse(err) } + crate::register_device_error::ErrorDetail::Internal(err) => Self::Internal(err), }) } } @@ -106,9 +114,11 @@ impl TryFrom for RequestZkNymErrorReason { fn try_from(value: ProtoRequestZkNymError) -> Result { let error_outcome = value .outcome - .ok_or(ConversionError::NoValueSet("RequestZkNymError.error"))?; + .ok_or(ConversionError::NoValueSet("RequestZkNymError.outcome"))?; Ok(match error_outcome { + crate::request_zk_nym_error::Outcome::NoAccountStored(_) => Self::NoAccountStored, + crate::request_zk_nym_error::Outcome::NoDeviceStored(_) => Self::NoDeviceStored, crate::request_zk_nym_error::Outcome::VpnApi(vpn_api_endpoint_failure) => { Self::VpnApi(vpn_api_endpoint_failure.into()) } @@ -125,9 +135,9 @@ impl TryFrom for ForgetAccountError { type Error = ConversionError; fn try_from(value: ProtoForgetAccountError) -> Result { - let error_detail = value - .error_detail - .ok_or(ConversionError::NoValueSet("ForgetAccountError"))?; + let error_detail = value.error_detail.ok_or(ConversionError::NoValueSet( + "ForgetAccountError.error_detail", + ))?; Ok(match error_detail { crate::forget_account_error::ErrorDetail::RegistrationInProgress(_) => { Self::RegistrationInProgress diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs index f06617ec0e..8e68d236d2 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/tunnel_state.rs @@ -53,12 +53,6 @@ impl TryFrom for ErrorStateReason { Self::from(proto_base_reason) } - ProtoErrorStateReason::GeneralAccount(general_account_error) => { - Self::Account(general_account_error.into()) - } - ProtoErrorStateReason::StoreAccount(store_account_error) => { - Self::StoreAccount(store_account_error.try_into()?) - } ProtoErrorStateReason::SyncAccount(sync_account_error) => { Self::SyncAccount(sync_account_error.try_into()?) } @@ -86,9 +80,6 @@ impl TryFrom for ErrorStateReason { failed: failures, } } - ProtoErrorStateReason::ForgetAccount(forget_account_error) => { - Self::ForgetAccount(forget_account_error.try_into()?) - } }) } } @@ -109,8 +100,6 @@ impl From for ErrorStateReason { ProtoBaseErrorStateReason::BadBandwidthIncrease => Self::BadBandwidthIncrease, ProtoBaseErrorStateReason::DuplicateTunFd => Self::DuplicateTunFd, ProtoBaseErrorStateReason::Internal => Self::Internal, - ProtoBaseErrorStateReason::NoAccountStored => Self::NoAccountStored, - ProtoBaseErrorStateReason::NoDeviceStored => Self::NoDeviceStored, } } } diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs index 0c400e85f3..ded99f14d3 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account.rs @@ -25,6 +25,12 @@ impl From for ProtoRequestZkNymSuccess { impl From for ProtoRequestZkNymError { fn from(error: RequestZkNymErrorReason) -> Self { let outcome = match error { + RequestZkNymErrorReason::NoAccountStored => { + Some(crate::request_zk_nym_error::Outcome::NoAccountStored(true)) + } + RequestZkNymErrorReason::NoDeviceStored => { + Some(crate::request_zk_nym_error::Outcome::NoDeviceStored(true)) + } RequestZkNymErrorReason::VpnApi(vpn_api_endpoint_failure) => Some( crate::request_zk_nym_error::Outcome::VpnApi(vpn_api_endpoint_failure.into()), ), diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs index 415d5c3721..319d594407 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/account_shared_state.rs @@ -8,7 +8,6 @@ use nym_vpn_account_controller::{ }, AccountStateSummary, }; -use nym_vpn_lib_types::RegisterDeviceError; use crate::{ get_account_state_response::{ @@ -22,6 +21,7 @@ use crate::{ }, AccountStateSummary as ProtoAccountStateSummary, }, + RegisterDeviceError as ProtoRegisterDeviceError, RegisterDeviceResult as ProtoRegisterDeviceResult, RequestZkNymError as ProtoRequestZkNymError, RequestZkNymResult as ProtoRequestZkNymResult, RequestZkNymSuccess as ProtoRequestZkNymSuccess, }; @@ -101,23 +101,15 @@ impl From for ProtoRegisterDeviceResult { match device_registration { RegisterDeviceResult::InProgress => Self { kind: crate::register_device_result::RegisterDeviceResultType::InProgress as i32, - ..Default::default() + error: None, }, RegisterDeviceResult::Success => Self { kind: crate::register_device_result::RegisterDeviceResultType::Success as i32, - ..Default::default() + error: None, }, - RegisterDeviceResult::Failed(err) => match err { - RegisterDeviceError::RegisterDeviceEndpointFailure(err) => Self { - kind: crate::register_device_result::RegisterDeviceResultType::Failed as i32, - message: Some(err.message), - message_id: err.message_id, - }, - RegisterDeviceError::UnexpectedResponse(err) => Self { - kind: crate::register_device_result::RegisterDeviceResultType::Failed as i32, - message: Some(err), - message_id: None, - }, + RegisterDeviceResult::Failed(err) => Self { + kind: crate::register_device_result::RegisterDeviceResultType::Failed as i32, + error: Some(ProtoRegisterDeviceError::from(err)), }, } } diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs index c625ff5a2c..6937c6995a 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs @@ -78,16 +78,6 @@ impl From for ProtoErrorStateReason { ErrorStateReason::Internal => { Self::BaseReason(ProtoBaseErrorStateReason::Internal as i32) } - ErrorStateReason::Account(reason) => Self::GeneralAccount(reason.into()), - ErrorStateReason::NoAccountStored => { - Self::BaseReason(ProtoBaseErrorStateReason::NoAccountStored as i32) - } - ErrorStateReason::NoDeviceStored => { - Self::BaseReason(ProtoBaseErrorStateReason::NoDeviceStored as i32) - } - ErrorStateReason::StoreAccount(store_account_error) => { - Self::StoreAccount(store_account_error.into()) - } ErrorStateReason::SyncAccount(sync_account_error) => { Self::SyncAccount(sync_account_error.into()) } @@ -112,9 +102,6 @@ impl From for ProtoErrorStateReason { .collect(), }) } - ErrorStateReason::ForgetAccount(forget_account_error) => { - Self::ForgetAccount(forget_account_error.into()) - } } } } @@ -152,6 +139,11 @@ impl From for ProtoStoreAccountError { impl From for ProtoSyncAccountError { fn from(value: SyncAccountError) -> Self { match value { + SyncAccountError::NoAccountStored => ProtoSyncAccountError { + error_detail: Some(crate::sync_account_error::ErrorDetail::NoAccountStored( + true, + )), + }, SyncAccountError::SyncAccountEndpointFailure(vpn_api_endpoint_failure) => { ProtoSyncAccountError { error_detail: Some(crate::sync_account_error::ErrorDetail::ErrorResponse( @@ -164,6 +156,9 @@ impl From for ProtoSyncAccountError { err, )), }, + SyncAccountError::Internal(err) => ProtoSyncAccountError { + error_detail: Some(crate::sync_account_error::ErrorDetail::Internal(err)), + }, } } } @@ -171,6 +166,12 @@ impl From for ProtoSyncAccountError { impl From for ProtoSyncDeviceError { fn from(value: SyncDeviceError) -> Self { match value { + SyncDeviceError::NoAccountStored => ProtoSyncDeviceError { + error_detail: Some(crate::sync_device_error::ErrorDetail::NoAccountStored(true)), + }, + SyncDeviceError::NoDeviceStored => ProtoSyncDeviceError { + error_detail: Some(crate::sync_device_error::ErrorDetail::NoDeviceStored(true)), + }, SyncDeviceError::SyncDeviceEndpointFailure(vpn_api_endpoint_failure) => { ProtoSyncDeviceError { error_detail: Some(crate::sync_device_error::ErrorDetail::ErrorResponse( @@ -183,6 +184,9 @@ impl From for ProtoSyncDeviceError { err, )), }, + SyncDeviceError::Internal(err) => ProtoSyncDeviceError { + error_detail: Some(crate::sync_device_error::ErrorDetail::Internal(err)), + }, } } } @@ -190,6 +194,16 @@ impl From for ProtoSyncDeviceError { impl From for ProtoRegisterDeviceError { fn from(value: RegisterDeviceError) -> Self { match value { + RegisterDeviceError::NoAccountStored => ProtoRegisterDeviceError { + error_detail: Some(crate::register_device_error::ErrorDetail::NoAccountStored( + true, + )), + }, + RegisterDeviceError::NoDeviceStored => ProtoRegisterDeviceError { + error_detail: Some(crate::register_device_error::ErrorDetail::NoDeviceStored( + true, + )), + }, RegisterDeviceError::RegisterDeviceEndpointFailure(vpn_api_endpoint_failure) => { ProtoRegisterDeviceError { error_detail: Some(crate::register_device_error::ErrorDetail::ErrorResponse( @@ -202,6 +216,9 @@ impl From for ProtoRegisterDeviceError { crate::register_device_error::ErrorDetail::UnexpectedResponse(err), ), }, + RegisterDeviceError::Internal(err) => ProtoRegisterDeviceError { + error_detail: Some(crate::register_device_error::ErrorDetail::Internal(err)), + }, } } } diff --git a/proto/nym/account.proto b/proto/nym/account.proto index 01030d5674..8224d8f184 100644 --- a/proto/nym/account.proto +++ b/proto/nym/account.proto @@ -41,22 +41,30 @@ message StoreAccountError { message SyncAccountError { oneof error_detail { - VpnApiErrorResponse error_response = 1; - string unexpected_response = 2; + bool no_account_stored = 1; + VpnApiErrorResponse error_response = 2; + string unexpected_response = 3; + string internal = 4; } } message SyncDeviceError { oneof error_detail { - VpnApiErrorResponse error_response = 1; - string unexpected_response = 2; + bool no_account_stored = 1; + bool no_device_stored = 2; + VpnApiErrorResponse error_response = 3; + string unexpected_response = 4; + string internal = 5; } } message RegisterDeviceError { oneof error_detail { - VpnApiErrorResponse error_response = 1; - string unexpected_response = 2; + bool no_account_stored = 1; + bool no_device_stored = 2; + VpnApiErrorResponse error_response = 3; + string unexpected_response = 4; + string internal = 5; } } @@ -85,10 +93,12 @@ message RequestZkNymSuccess { message RequestZkNymError { oneof outcome { - VpnApiErrorResponse vpn_api = 1; - string unexpected_vpn_api_response = 2; - string storage = 3; - string internal = 4; + bool no_account_stored = 1; + bool no_device_stored = 2; + VpnApiErrorResponse vpn_api = 3; + string unexpected_vpn_api_response = 4; + string storage = 5; + string internal = 6; } } @@ -209,8 +219,7 @@ message RegisterDeviceResult { } RegisterDeviceResultType kind = 1; - optional string message = 2; - optional string message_id = 3; + optional RegisterDeviceError error = 2; } // The status of a zk-nym request. A request can have multiple successes diff --git a/proto/nym/vpn.proto b/proto/nym/vpn.proto index 7ea895684c..efc138fa54 100644 --- a/proto/nym/vpn.proto +++ b/proto/nym/vpn.proto @@ -381,8 +381,6 @@ message TunnelState { BAD_BANDWIDTH_INCREASE = 8; DUPLICATE_TUN_FD = 9; INTERNAL = 10; - NO_ACCOUNT_STORED = 11; - NO_DEVICE_STORED = 12; } enum ActionAfterDisconnect { @@ -405,14 +403,11 @@ message TunnelState { message Error { oneof error_state_reason { BaseErrorStateReason base_reason = 1; - GeneralAccountError general_account = 2; - StoreAccountError store_account = 3; - SyncAccountError sync_account = 4; - SyncDeviceError sync_device = 5; - RegisterDeviceError register_device = 6; - RequestZkNymError request_zk_nym = 7; - RequestZkNymBundle request_zk_nym_bundle = 8; - ForgetAccountError forget_account = 9; + SyncAccountError sync_account = 2; + SyncDeviceError sync_device = 3; + RegisterDeviceError register_device = 4; + RequestZkNymError request_zk_nym = 5; + RequestZkNymBundle request_zk_nym_bundle = 6; } } message Offline { From a90379ec1ea90951991029b6157b7600d39128a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 11 Feb 2025 14:14:59 +0100 Subject: [PATCH 7/9] Downgrade to log statements to debug --- .../src/commands/request_zknym/request.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs index ff3e892eab..67a5e82515 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/commands/request_zknym/request.rs @@ -116,7 +116,7 @@ impl RequestZkNymTask { &self, ticketbook_type: TicketType, ) -> Result { - tracing::info!("Constructing zk-nym request"); + tracing::debug!("Constructing zk-nym request"); let ecash_keypair = self .account @@ -146,7 +146,7 @@ impl RequestZkNymTask { &self, request: &ZkNymRequestData, ) -> Result { - tracing::info!("Requesting zk-nym ticketbook"); + tracing::debug!("Requesting zk-nym ticketbook"); self.vpn_api_client .request_zk_nym( &self.account, From 3e4747b1b42bbccf171375f306f65a6a42474ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 12 Feb 2025 09:47:01 +0100 Subject: [PATCH 8/9] Remove general error case --- .../src/controller.rs | 28 +++++++++++++------ .../nym-vpn-lib-types/src/account/mod.rs | 16 ++++------- .../crates/nym-vpn-lib/src/platform/error.rs | 9 +++++- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs index 21d8e183cd..23a3dbb06e 100644 --- a/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs +++ b/nym-vpn-core/crates/nym-vpn-account-controller/src/controller.rs @@ -451,12 +451,16 @@ where .account_storage .load_account() .await - .map_err(AccountCommandError::general)?; + .map_err(|err| AccountCommandError::Storage(err.to_string()))?; let usage = self .vpn_api_client .get_usage(&account) .await - .map_err(AccountCommandError::general)?; + .map_err(|err| { + VpnApiErrorResponse::try_from(err) + .map(AccountCommandError::from) + .unwrap_or_else(AccountCommandError::internal) + })?; tracing::info!("Usage: {:#?}", usage); Ok(usage.items) } @@ -507,13 +511,17 @@ where .account_storage .load_account() .await - .map_err(AccountCommandError::general)?; + .map_err(|err| AccountCommandError::Storage(err.to_string()))?; let devices = self .vpn_api_client .get_devices(&account) .await - .map_err(AccountCommandError::general)?; + .map_err(|err| { + VpnApiErrorResponse::try_from(err) + .map(AccountCommandError::from) + .unwrap_or_else(AccountCommandError::internal) + })?; tracing::info!("The account has the following devices associated to it:"); // TODO: pagination @@ -532,13 +540,17 @@ where .account_storage .load_account() .await - .map_err(AccountCommandError::general)?; + .map_err(|err| AccountCommandError::Storage(err.to_string()))?; let devices = self .vpn_api_client .get_active_devices(&account) .await - .map_err(AccountCommandError::general)?; + .map_err(|err| { + VpnApiErrorResponse::try_from(err) + .map(AccountCommandError::from) + .unwrap_or_else(AccountCommandError::internal) + })?; tracing::info!("The account has the following active devices associated to it:"); // TODO: pagination @@ -634,11 +646,11 @@ where guard .print_info() .await - .map_err(AccountCommandError::general)?; + .map_err(|err| AccountCommandError::Storage(err.to_string()))?; guard .get_available_ticketbooks() .await - .map_err(AccountCommandError::general) + .map_err(|err| AccountCommandError::Storage(err.to_string())) } fn queue_command(&self, command: AccountCommand) { diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs index 036d22d7c0..41a8e12a44 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs @@ -10,19 +10,19 @@ pub mod sync_device; #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] pub enum AccountCommandError { - // Catch all for any error not specifically handled - #[error("general error: {0}")] - General(String), - // Internal error that should not happen #[error("internal error: {0}")] Internal(String), - // Top level error that can happen for any command + #[error("storage error: {0}")] + Storage(String), + + #[error("vpn api error: {0}")] + VpnApi(#[from] VpnApiErrorResponse), + #[error("no account stored")] NoAccountStored, - // Top level error that can happen for any command #[error("no device stored")] NoDeviceStored, @@ -58,10 +58,6 @@ impl AccountCommandError { pub fn internal(message: impl ToString) -> Self { AccountCommandError::Internal(message.to_string()) } - - pub fn general(message: impl ToString) -> Self { - AccountCommandError::General(message.to_string()) - } } // Local alias for syntactic simplification diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs b/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs index bba026c8dc..8a92dd95e6 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/platform/error.rs @@ -26,6 +26,12 @@ pub enum VpnError { #[error("no device identity stored")] NoDeviceIdentity, + #[error("vpn-api error: {details}")] + VpnApi { + #[from] + details: super::uniffi_lib_types::VpnApiErrorResponse, + }, + #[error("timeout connecting to nym-vpn-api")] VpnApiTimeout, @@ -91,8 +97,9 @@ impl VpnError { impl From for VpnError { fn from(value: AccountCommandError) -> Self { match value { - AccountCommandError::General(err) => Self::InternalError { details: err }, AccountCommandError::Internal(err) => Self::InternalError { details: err }, + AccountCommandError::Storage(err) => Self::Storage { details: err }, + AccountCommandError::VpnApi(e) => Self::VpnApi { details: e.into() }, AccountCommandError::NoAccountStored => Self::NoAccountStored, AccountCommandError::NoDeviceStored => Self::NoDeviceIdentity, AccountCommandError::StoreAccount(e) => Self::StoreAccount { details: e.into() }, From 9226be030520181d5efff14af309dd0aecf789ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 12 Feb 2025 09:49:24 +0100 Subject: [PATCH 9/9] Remove unused proto message --- .../nym-vpn-proto/src/conversions/from_proto/account.rs | 7 ------- .../src/conversions/into_proto/tunnel_state.rs | 9 --------- proto/nym/account.proto | 4 ---- 3 files changed, 20 deletions(-) diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs index 093815636b..8dc624a4b5 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/from_proto/account.rs @@ -8,19 +8,12 @@ use nym_vpn_lib_types::{ use crate::{ conversions::ConversionError, ForgetAccountError as ProtoForgetAccountError, - GeneralAccountError as ProtoGeneralAccountError, RegisterDeviceError as ProtoRegisterDeviceError, RequestZkNymError as ProtoRequestZkNymError, RequestZkNymSuccess as ProtoRequestZkNymSuccess, StoreAccountError as ProtoStoreAccountError, SyncAccountError as ProtoSyncAccountError, SyncDeviceError as ProtoSyncDeviceError, VpnApiErrorResponse as ProtoVpnApiErrorResponse, }; -impl From for String { - fn from(value: ProtoGeneralAccountError) -> Self { - value.error_message - } -} - impl TryFrom for StoreAccountError { type Error = ConversionError; diff --git a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs index 6937c6995a..6e335890c1 100644 --- a/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs +++ b/nym-vpn-core/crates/nym-vpn-proto/src/conversions/into_proto/tunnel_state.rs @@ -23,7 +23,6 @@ use crate::{ }, Address as ProtoAddress, ConnectionData as ProtoConnectionData, ForgetAccountError as ProtoForgetAccountError, Gateway as ProtoGateway, - GeneralAccountError as ProtoGeneralAccountError, MixnetConnectionData as ProtoMixnetConnectionData, RegisterDeviceError as ProtoRegisterDeviceError, RequestZkNymBundle as ProtoRequestZkNymBundle, RequestZkNymError as ProtoRequestZkNymError, RequestZkNymSuccess as ProtoRequestZkNymSuccess, @@ -106,14 +105,6 @@ impl From for ProtoErrorStateReason { } } -impl From for ProtoGeneralAccountError { - fn from(value: String) -> Self { - Self { - error_message: value, - } - } -} - impl From for ProtoStoreAccountError { fn from(value: StoreAccountError) -> Self { match value { diff --git a/proto/nym/account.proto b/proto/nym/account.proto index 8224d8f184..f4f9796c92 100644 --- a/proto/nym/account.proto +++ b/proto/nym/account.proto @@ -27,10 +27,6 @@ message AccountError { map details = 3; } -message GeneralAccountError { - string error_message = 1; -} - message StoreAccountError { oneof error_detail { string storage_error = 1;