use crate::{Daemon, settings}; use mullvad_api::{ApiProxy, access_mode, proxy::ApiConnectionMode, rest}; use mullvad_types::{ access_method::{self, AccessMethod, AccessMethodSetting}, settings::Settings, }; #[derive(thiserror::Error, Debug)] pub enum Error { /// Can not add access method #[error("Cannot add custom access method")] Add, /// Can not find access method #[error("Cannot find custom access method {0}")] NoSuchMethod(access_method::Id), /// Some error occurred in the daemon's state of handling /// [`AccessMethodSetting`]s & [`ApiConnectionMode`]s #[error("Error occurred when handling connection settings & details")] ApiService(#[from] access_mode::Error), /// A REST request failed #[error("Reset request failed")] Rest(#[from] rest::Error), /// Something went wrong in the [`access_method`](mod@access_method) module. #[error("Access method error")] AccessMethod(#[from] access_method::Error), /// Access methods settings error #[error("Settings error")] Settings(#[from] settings::Error), } impl Daemon { /// Add a [`AccessMethod`] to the daemon's settings. /// /// If the daemon settings are successfully updated, the /// [`access_method::Id`] of the newly created [`AccessMethodSetting`] /// (which has been derived from the [`AccessMethod`]) is returned. pub async fn add_access_method( &mut self, name: String, enabled: bool, access_method: AccessMethod, ) -> Result { let access_method_setting = AccessMethodSetting::new(name, enabled, access_method); let id = access_method_setting.get_id(); self.settings .try_update(|settings| settings.api_access_methods.append(access_method_setting)) .await .map_err(crate::Error::SettingsError)?; Ok(id) } /// Remove a [`AccessMethodSetting`] from the daemon's saved settings. pub async fn remove_access_method( &mut self, access_method: access_method::Id, ) -> Result<(), Error> { self.settings .try_update(|settings| -> Result<(), Error> { settings.api_access_methods.remove(&access_method)?; Ok(()) }) .await?; Ok(()) } /// Select an [`AccessMethodSetting`] as the current API access method. /// /// If successful, the daemon will force a rotation of the active API access /// method, which means that subsequent API calls will use the new /// [`AccessMethodSetting`] as to reach the API endpoint. /// /// # Note /// /// If the selected [`AccessMethodSetting`] is disabled, it will be enabled /// and the Daemon's settings will be updated accordingly. If an /// [`AccessMethodSetting`] is enabled, it is eligible to be part of the /// automatic selection of access methods that the Daemon will perform at /// start up or if the current access method starts failing. pub async fn use_api_access_method( &mut self, access_method: access_method::Id, ) -> Result<(), Error> { self.settings .try_update(|settings| -> Result<(), Error> { settings.api_access_methods.update( |setting| setting.get_id() == access_method, |setting| setting.enable(), )?; Ok(()) }) .await?; self.access_mode_handler .use_access_method(access_method) .await?; Ok(()) } pub fn get_api_access_method( &mut self, access_method: access_method::Id, ) -> Result { self.settings .api_access_methods .iter() .find(|setting| setting.get_id() == access_method) .ok_or(Error::NoSuchMethod(access_method)) .cloned() } /// Updates a [`AccessMethodSetting`] by replacing the existing entry with /// the argument `access_method_update`. if an entry with a matching /// [`access_method::Id`] is found. pub async fn update_access_method( &mut self, access_method_update: AccessMethodSetting, ) -> Result<(), crate::Error> { self.settings .try_update( |settings: &mut Settings| -> Result<(), access_method::Error> { let target = access_method_update.get_id(); settings.api_access_methods.update( |access_method| access_method.get_id() == target, |method| *method = access_method_update, )?; Ok(()) }, ) .await .map_err(crate::Error::SettingsError)?; Ok(()) } /// Remove all custom [`AccessMethodSetting`]. pub async fn clear_custom_api_access_methods(&mut self) -> Result<(), Error> { self.settings .update(|settings: &mut Settings| { settings.api_access_methods.clear_custom(); }) .await?; Ok(()) } /// Return the [`AccessMethodSetting`] which is currently used to access the /// Mullvad API. pub async fn get_current_access_method(&self) -> Result { let current = self.access_mode_handler.get_current().await?; Ok(current.setting) } /// Test if the API is reachable via `proxy`. /// /// This function tests if [`AccessMethod`] can be used to reach the API. /// Its parameters are as low-level as possible to promot re-use between /// different kinds of testing contexts, such as testing /// [`AccessMethodSetting`]s or on the fly testing of /// [`talpid_types::net::proxy::CustomProxy`]s. #[cfg(not(target_os = "android"))] pub(crate) async fn test_access_method( proxy: talpid_types::net::AllowedEndpoint, access_method_selector: access_mode::AccessModeSelectorHandle, daemon_event_sender: crate::DaemonEventSender<( access_mode::AccessMethodEvent, futures::channel::oneshot::Sender<()>, )>, api_proxy: ApiProxy, ) -> Result { let reset = access_method_selector .get_current() .await .map(|connection_mode| connection_mode.endpoint)?; access_mode::AccessMethodEvent::Allow { endpoint: proxy } .send(daemon_event_sender.to_unbounded_sender()) .await?; let result = Self::perform_api_request(api_proxy).await; access_mode::AccessMethodEvent::Allow { endpoint: reset } .send(daemon_event_sender.to_unbounded_sender()) .await?; result } #[cfg(target_os = "android")] pub(crate) async fn test_access_method( _: talpid_types::net::AllowedEndpoint, _: access_mode::AccessModeSelectorHandle, _: crate::DaemonEventSender<( access_mode::AccessMethodEvent, futures::channel::oneshot::Sender<()>, )>, api_proxy: ApiProxy, ) -> Result { Self::perform_api_request(api_proxy).await } /// Create an [`ApiProxy`] which will perform all REST requests against one /// specific endpoint `connection_mode`. pub fn create_limited_api_proxy(&mut self, connection_mode: ApiConnectionMode) -> ApiProxy { let rest_handle = self .api_runtime .mullvad_rest_handle(connection_mode.into_provider()); ApiProxy::new(rest_handle) } /// Perform some REST request against the Mullvad API. /// /// * Returns `Ok(true)` if the API returned the expected result /// * Returns `Ok(false)` if the API returned an unexpected result /// * Returns `Err(..)` if the API could not be reached async fn perform_api_request(api_proxy: ApiProxy) -> Result { Ok(api_proxy.api_addrs_available().await?) } }