diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index 2ee7d9a4f0e1..50c242cefd3b 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -172,7 +172,8 @@ impl Bridge { Self::update_bridge_settings(&mut rpc, Some(location), None, None).await } SetCommands::CustomList { custom_list_name } => { - let list = rpc.get_custom_list(custom_list_name).await?; + let list = + super::custom_list::find_list_by_name(&mut rpc, &custom_list_name).await?; let location = Constraint::Only(LocationConstraint::CustomList { list_id: list.id }); Self::update_bridge_settings(&mut rpc, Some(location), None, None).await diff --git a/mullvad-cli/src/cmds/custom_lists.rs b/mullvad-cli/src/cmds/custom_list.rs similarity index 85% rename from mullvad-cli/src/cmds/custom_lists.rs rename to mullvad-cli/src/cmds/custom_list.rs index d30f29f62d20..d70a96a28aa7 100644 --- a/mullvad-cli/src/cmds/custom_lists.rs +++ b/mullvad-cli/src/cmds/custom_list.rs @@ -2,11 +2,10 @@ use super::{ relay::{find_relay_by_hostname, get_filtered_relays}, relay_constraints::LocationArgs, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Subcommand; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::{ - custom_list::CustomListLocationUpdate, relay_constraints::{Constraint, GeographicLocationConstraint}, relay_list::RelayList, }; @@ -86,7 +85,7 @@ impl CustomList { async fn list() -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let cache = rpc.get_relay_locations().await?; - for custom_list in rpc.list_custom_lists().await? { + for custom_list in rpc.get_settings().await?.custom_lists { Self::print_custom_list(&custom_list, &cache) } Ok(()) @@ -96,7 +95,7 @@ impl CustomList { /// If the list does not exist, print an error. async fn get(name: String) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let custom_list = rpc.get_custom_list(name).await?; + let custom_list = find_list_by_name(&mut rpc, &name).await?; let cache = rpc.get_relay_locations().await?; Self::print_custom_list_content(&custom_list, &cache); Ok(()) @@ -111,30 +110,47 @@ impl CustomList { async fn add_location(name: String, location_args: LocationArgs) -> Result<()> { let countries = get_filtered_relays().await?; let location = find_relay_by_hostname(&countries, &location_args.country) - .map_or(Constraint::from(location_args), Constraint::Only); - let update = CustomListLocationUpdate::Add { name, location }; + .map_or(Constraint::from(location_args), Constraint::Only) + .option() + .ok_or(anyhow!("\"any\" is not a valid location"))?; + let mut rpc = MullvadProxyClient::new().await?; - rpc.update_custom_list_location(update).await?; + + let mut list = find_list_by_name(&mut rpc, &name).await?; + list.locations.insert(location); + rpc.update_custom_list(list).await?; + Ok(()) } async fn remove_location(name: String, location_args: LocationArgs) -> Result<()> { - let location = Constraint::::from(location_args); - let update = CustomListLocationUpdate::Remove { name, location }; + let location = Constraint::::from(location_args) + .option() + .ok_or(anyhow!("\"any\" is not a valid location"))?; + let mut rpc = MullvadProxyClient::new().await?; - rpc.update_custom_list_location(update).await?; + + let mut list = find_list_by_name(&mut rpc, &name).await?; + list.locations.remove(&location); + rpc.update_custom_list(list).await?; + Ok(()) } async fn delete_list(name: String) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - rpc.delete_custom_list(name).await?; + let list = find_list_by_name(&mut rpc, &name).await?; + rpc.delete_custom_list(list.id.to_string()).await?; Ok(()) } async fn rename_list(name: String, new_name: String) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - rpc.rename_custom_list(name, new_name).await?; + + let mut list = find_list_by_name(&mut rpc, &name).await?; + list.name = new_name; + rpc.update_custom_list(list).await?; + Ok(()) } @@ -217,3 +233,15 @@ impl<'a> std::fmt::Display for GeographicLocationConstraintFormatter<'a> { } } } + +pub async fn find_list_by_name( + rpc: &mut MullvadProxyClient, + name: &str, +) -> Result { + rpc.get_settings() + .await? + .custom_lists + .into_iter() + .find(|list| list.name == name) + .ok_or(anyhow!("List not found")) +} diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index 8b7ca1a6be42..c63a98113310 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -5,7 +5,7 @@ pub mod account; pub mod auto_connect; pub mod beta_program; pub mod bridge; -pub mod custom_lists; +pub mod custom_list; pub mod dns; pub mod lan; pub mod lockdown; diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 78f7d1f3c458..2bccbbc24ec6 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -516,7 +516,9 @@ impl Relay { async fn set_custom_list(custom_list_name: String) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let list_id = rpc.get_custom_list(custom_list_name).await?.id; + let list_id = super::custom_list::find_list_by_name(&mut rpc, &custom_list_name) + .await? + .id; rpc.update_relay_settings(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { location: Some(Constraint::Only(LocationConstraint::CustomList { list_id })), ..Default::default() @@ -617,9 +619,11 @@ impl Relay { }; } Some(EntryLocation::CustomList { custom_list_name }) => { - let list = rpc.get_custom_list(custom_list_name).await?; + let list_id = super::custom_list::find_list_by_name(&mut rpc, &custom_list_name) + .await? + .id; wireguard_constraints.entry_location = - Constraint::Only(LocationConstraint::CustomList { list_id: list.id }); + Constraint::Only(LocationConstraint::CustomList { list_id }); } None => (), } diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 566b91e3f0a5..41f164397093 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -115,7 +115,7 @@ enum Cli { /// Manage custom lists #[clap(subcommand)] - CustomList(custom_lists::CustomList), + CustomList(custom_list::CustomList), } #[tokio::main] diff --git a/mullvad-daemon/src/custom_list.rs b/mullvad-daemon/src/custom_list.rs new file mode 100644 index 000000000000..8d244765ccbf --- /dev/null +++ b/mullvad-daemon/src/custom_list.rs @@ -0,0 +1,178 @@ +use crate::{new_selector_config, Daemon, Error, EventListener}; +use mullvad_types::{ + custom_list::{CustomList, Id}, + relay_constraints::{ + BridgeSettings, BridgeState, Constraint, LocationConstraint, RelaySettings, + }, +}; +use talpid_types::net::TunnelType; + +impl Daemon +where + L: EventListener + Clone + Send + 'static, +{ + pub async fn create_custom_list(&mut self, name: String) -> Result { + if self + .settings + .custom_lists + .iter() + .any(|list| list.name == name) + { + return Err(Error::CustomListExists); + } + + let new_list = CustomList::new(name); + let id = new_list.id; + + let settings_changed = self + .settings + .update(|settings| { + settings.custom_lists.add(new_list); + }) + .await + .map_err(Error::SettingsError); + + if let Ok(true) = settings_changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); + } + + settings_changed?; + Ok(id) + } + + pub async fn delete_custom_list(&mut self, id: Id) -> Result<(), Error> { + let Some(list_index) = self + .settings + .custom_lists + .iter() + .position(|elem| elem.id == id) + else { + return Err(Error::CustomListNotFound); + }; + let settings_changed = self + .settings + .update(|settings| { + // NOTE: Not using swap remove because it would make user output slightly + // more confusing and the cost is so small. + settings.custom_lists.remove(list_index); + }) + .await + .map_err(Error::SettingsError); + + if let Ok(true) = settings_changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); + + if self.change_should_cause_reconnect(id) { + log::info!("Initiating tunnel restart because a selected custom list was deleted"); + self.reconnect_tunnel(); + } + } + + settings_changed?; + Ok(()) + } + + pub async fn update_custom_list(&mut self, new_list: CustomList) -> Result<(), Error> { + let Some((list_index, old_list)) = self + .settings + .custom_lists + .iter() + .enumerate() + .find(|elem| elem.1.id == new_list.id) + else { + return Err(Error::CustomListNotFound); + }; + let id = old_list.id; + + if old_list.name != new_list.name + && self + .settings + .custom_lists + .iter() + .any(|list| list.name == new_list.name) + { + return Err(Error::CustomListExists); + } + + let settings_changed = self + .settings + .update(|settings| { + settings.custom_lists[list_index] = new_list; + }) + .await + .map_err(Error::SettingsError); + + if let Ok(true) = settings_changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); + + if self.change_should_cause_reconnect(id) { + log::info!("Initiating tunnel restart because a selected custom list changed"); + self.reconnect_tunnel(); + } + } + + settings_changed?; + Ok(()) + } + + fn change_should_cause_reconnect(&self, custom_list_id: Id) -> bool { + use mullvad_types::states::TunnelState; + let mut need_to_reconnect = false; + + if let RelaySettings::Normal(relay_settings) = &self.settings.relay_settings { + if let Constraint::Only(LocationConstraint::CustomList { list_id }) = + &relay_settings.location + { + need_to_reconnect |= list_id == &custom_list_id; + } + + if let TunnelState::Connecting { + endpoint, + location: _, + } + | TunnelState::Connected { + endpoint, + location: _, + } = &self.tunnel_state + { + match endpoint.tunnel_type { + TunnelType::Wireguard => { + if relay_settings.wireguard_constraints.use_multihop { + if let Constraint::Only(LocationConstraint::CustomList { list_id }) = + &relay_settings.wireguard_constraints.entry_location + { + need_to_reconnect |= list_id == &custom_list_id; + } + } + } + + TunnelType::OpenVpn => { + if !matches!(self.settings.bridge_state, BridgeState::Off) { + if let BridgeSettings::Normal(bridge_settings) = + &self.settings.bridge_settings + { + if let Constraint::Only(LocationConstraint::CustomList { + list_id, + }) = &bridge_settings.location + { + need_to_reconnect |= list_id == &custom_list_id; + } + } + } + } + } + } + } + + need_to_reconnect + } +} diff --git a/mullvad-daemon/src/custom_lists.rs b/mullvad-daemon/src/custom_lists.rs deleted file mode 100644 index 4627df58aec7..000000000000 --- a/mullvad-daemon/src/custom_lists.rs +++ /dev/null @@ -1,324 +0,0 @@ -use crate::{new_selector_config, settings, Daemon, EventListener}; -use mullvad_types::{ - custom_list::{CustomList, CustomListLocationUpdate, Id}, - relay_constraints::{ - BridgeSettings, BridgeState, Constraint, LocationConstraint, RelaySettings, - }, -}; -use talpid_types::net::TunnelType; - -#[derive(err_derive::Error, Debug)] -pub enum Error { - /// Custom list already exists - #[error(display = "A list with that name already exists")] - ListExists, - /// Custom list does not exist - #[error(display = "A list with that name does not exist")] - ListNotFound, - /// Can not add any to a custom list - #[error(display = "Can not add or remove 'any' to or from a custom list")] - CannotAddOrRemoveAny, - /// Location already exists in the custom list - #[error(display = "Location already exists in the custom list")] - LocationExists, - /// Location was not found in the list - #[error(display = "Location was not found in the list")] - LocationNotFoundInlist, - /// Custom list settings error - #[error(display = "Settings error")] - Settings(#[error(source)] settings::Error), -} - -impl Daemon -where - L: EventListener + Clone + Send + 'static, -{ - pub async fn delete_custom_list(&mut self, name: String) -> Result<(), Error> { - let custom_list = self.settings.custom_lists.get_custom_list_with_name(&name); - match &custom_list { - None => Err(Error::ListNotFound), - Some(custom_list) => { - let id = custom_list.id.clone(); - - let settings_changed = self - .settings - .update(|settings| { - let index = settings - .custom_lists - .custom_lists - .iter() - .position(|custom_list| custom_list.id == id) - .unwrap(); - // NOTE: Not using swap remove because it would make user output slightly - // more confusing and the cost is so small. - settings.custom_lists.custom_lists.remove(index); - }) - .await - .map_err(Error::Settings); - - if let Ok(true) = settings_changed { - let need_to_reconnect = self.change_should_cause_reconnect(&id); - - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - - if need_to_reconnect { - log::info!( - "Initiating tunnel restart because a selected custom list was deleted" - ); - self.reconnect_tunnel(); - } - } - - settings_changed.map(|_| ()) - } - } - } - - pub async fn create_custom_list(&mut self, name: String) -> Result<(), Error> { - if self - .settings - .custom_lists - .get_custom_list_with_name(&name) - .is_some() - { - return Err(Error::ListExists); - } - - let settings_changed = self - .settings - .update(|settings| { - let custom_list = CustomList::new(name); - settings.custom_lists.custom_lists.push(custom_list); - }) - .await - .map_err(Error::Settings); - - if let Ok(true) = settings_changed { - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - } - - settings_changed.map(|_| ()) - } - - pub async fn update_custom_list_location( - &mut self, - update: CustomListLocationUpdate, - ) -> Result<(), Error> { - match update { - CustomListLocationUpdate::Add { - name, - location: new_location, - } => { - if new_location.is_any() { - return Err(Error::CannotAddOrRemoveAny); - } - - if let Some(custom_list) = - self.settings.custom_lists.get_custom_list_with_name(&name) - { - let id = custom_list.id.clone(); - let new_location = new_location.unwrap(); - - let settings_changed = self - .settings - .update(|settings| { - let locations = &mut settings - .custom_lists - .custom_lists - .iter_mut() - .find(|custom_list| custom_list.id == id) - .unwrap() - .locations; - - if !locations.iter().any(|location| new_location == *location) { - locations.push(new_location); - } - }) - .await - .map_err(Error::Settings); - - if let Ok(true) = settings_changed { - let should_reconnect = self.change_should_cause_reconnect(&id); - - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - - if should_reconnect { - log::info!( - "Initiating tunnel restart because a selected custom list changed" - ); - self.reconnect_tunnel(); - } - } else if let Ok(false) = settings_changed { - return Err(Error::LocationExists); - } - - settings_changed.map(|_| ()) - } else { - Err(Error::ListNotFound) - } - } - CustomListLocationUpdate::Remove { - name, - location: location_to_remove, - } => { - if location_to_remove.is_any() { - return Err(Error::CannotAddOrRemoveAny); - } - - if let Some(custom_list) = - self.settings.custom_lists.get_custom_list_with_name(&name) - { - let id = custom_list.id.clone(); - let location_to_remove = location_to_remove.unwrap(); - - let settings_changed = self - .settings - .update(|settings| { - let locations = &mut settings - .custom_lists - .custom_lists - .iter_mut() - .find(|custom_list| custom_list.id == id) - .unwrap() - .locations; - if let Some(index) = locations - .iter() - .position(|location| location == &location_to_remove) - { - locations.remove(index); - } - }) - .await - .map_err(Error::Settings); - - if let Ok(true) = settings_changed { - let should_reconnect = self.change_should_cause_reconnect(&id); - - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - - if should_reconnect { - log::info!( - "Initiating tunnel restart because a selected custom list changed" - ); - self.reconnect_tunnel(); - } - } else if let Ok(false) = settings_changed { - return Err(Error::LocationNotFoundInlist); - } - - settings_changed.map(|_| ()) - } else { - Err(Error::ListNotFound) - } - } - } - } - - pub async fn rename_custom_list( - &mut self, - name: String, - new_name: String, - ) -> Result<(), Error> { - if self - .settings - .custom_lists - .get_custom_list_with_name(&new_name) - .is_some() - { - Err(Error::ListExists) - } else { - match self.settings.custom_lists.get_custom_list_with_name(&name) { - Some(custom_list) => { - let id = custom_list.id.clone(); - - let settings_changed = self - .settings - .update(|settings| { - settings - .custom_lists - .custom_lists - .iter_mut() - .find(|custom_list| custom_list.id == id) - .unwrap() - .name = new_name; - }) - .await; - - if let Ok(true) = settings_changed { - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - } - - Ok(()) - } - None => Err(Error::ListNotFound), - } - } - } - - fn change_should_cause_reconnect(&self, custom_list_id: &Id) -> bool { - use mullvad_types::states::TunnelState; - let mut need_to_reconnect = false; - - if let RelaySettings::Normal(relay_settings) = &self.settings.relay_settings { - if let Constraint::Only(LocationConstraint::CustomList { list_id }) = - &relay_settings.location - { - need_to_reconnect |= list_id == custom_list_id; - } - - if let TunnelState::Connecting { - endpoint, - location: _, - } - | TunnelState::Connected { - endpoint, - location: _, - } = &self.tunnel_state - { - match endpoint.tunnel_type { - TunnelType::Wireguard => { - if relay_settings.wireguard_constraints.use_multihop { - if let Constraint::Only(LocationConstraint::CustomList { list_id }) = - &relay_settings.wireguard_constraints.entry_location - { - need_to_reconnect |= list_id == custom_list_id; - } - } - } - - TunnelType::OpenVpn => { - if !matches!(self.settings.bridge_state, BridgeState::Off) { - if let BridgeSettings::Normal(bridge_settings) = - &self.settings.bridge_settings - { - if let Constraint::Only(LocationConstraint::CustomList { - list_id, - }) = &bridge_settings.location - { - need_to_reconnect |= list_id == custom_list_id; - } - } - } - } - } - } - } - - need_to_reconnect - } -} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index e757258c73d0..81ebe1050c54 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -5,7 +5,7 @@ pub mod account_history; mod api; #[cfg(not(target_os = "android"))] mod cleanup; -mod custom_lists; +mod custom_list; pub mod device; mod dns; pub mod exception_logging; @@ -40,7 +40,7 @@ use mullvad_relay_selector::{ use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, auth_failed::AuthFailed, - custom_list::{CustomList, CustomListLocationUpdate}, + custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettingsUpdate}, @@ -162,8 +162,13 @@ pub enum Error { #[error(display = "Tunnel state machine error")] TunnelError(#[error(source)] tunnel_state_machine::Error), - #[error(display = "Custom list error")] - CustomListError(#[error(source)] custom_lists::Error), + /// Custom list already exists + #[error(display = "A list with that name already exists")] + CustomListExists, + + /// Custom list does not exist + #[error(display = "A list with that name does not exist")] + CustomListNotFound, #[cfg(target_os = "macos")] #[error(display = "Failed to set exclusion group")] @@ -244,18 +249,12 @@ pub enum DaemonCommand { RotateWireguardKey(ResponseTx<(), Error>), /// Return a public key of the currently set wireguard private key, if there is one GetWireguardKey(ResponseTx, Error>), - /// List custom lists - ListCustomLists(ResponseTx, Error>), - /// Get custom list - GetCustomList(ResponseTx, String), /// Create custom list - CreateCustomList(ResponseTx<(), Error>, String), + CreateCustomList(ResponseTx, String), /// Delete custom list - DeleteCustomList(ResponseTx<(), Error>, String), - /// Update a custom list by adding or removing a location - UpdateCustomListLocation(ResponseTx<(), Error>, CustomListLocationUpdate), - /// Rename a custom list from the old name to a new name - RenameCustomList(ResponseTx<(), Error>, String, String), + DeleteCustomList(ResponseTx<(), Error>, mullvad_types::custom_list::Id), + /// Update a custom list with a given id + UpdateCustomList(ResponseTx<(), Error>, CustomList), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender>), /// Return whether the daemon is performing post-upgrade tasks @@ -1027,16 +1026,9 @@ where GetSettings(tx) => self.on_get_settings(tx), RotateWireguardKey(tx) => self.on_rotate_wireguard_key(tx), GetWireguardKey(tx) => self.on_get_wireguard_key(tx).await, - ListCustomLists(tx) => self.on_list_custom_lists(tx), - GetCustomList(tx, name) => self.on_get_custom_list(tx, name), CreateCustomList(tx, name) => self.on_create_custom_list(tx, name).await, - DeleteCustomList(tx, name) => self.on_delete_custom_list(tx, name).await, - UpdateCustomListLocation(tx, update) => { - self.on_update_custom_list_location(tx, update).await - } - RenameCustomList(tx, name, new_name) => { - self.on_rename_custom_list(tx, name, new_name).await - } + DeleteCustomList(tx, id) => self.on_delete_custom_list(tx, id).await, + UpdateCustomList(tx, update) => self.on_update_custom_list(tx, update).await, GetVersionInfo(tx) => self.on_get_version_info(tx), IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), @@ -2189,60 +2181,27 @@ where Self::oneshot_send(tx, result, "get_wireguard_key response"); } - fn on_list_custom_lists(&mut self, tx: ResponseTx, Error>) { - let result = self.settings.custom_lists.custom_lists.clone(); - Self::oneshot_send(tx, Ok(result), "list_custom_lists response"); - } - - fn on_get_custom_list(&mut self, tx: ResponseTx, name: String) { - let result = self - .settings - .custom_lists - .get_custom_list_with_name(&name) - .cloned() - .ok_or(Error::CustomListError(custom_lists::Error::ListNotFound)); - Self::oneshot_send(tx, result, "create_custom_list response"); - } - - async fn on_create_custom_list(&mut self, tx: ResponseTx<(), Error>, name: String) { - let result = self - .create_custom_list(name) - .await - .map_err(Error::CustomListError); + async fn on_create_custom_list( + &mut self, + tx: ResponseTx, + name: String, + ) { + let result = self.create_custom_list(name).await; Self::oneshot_send(tx, result, "create_custom_list response"); } - async fn on_delete_custom_list(&mut self, tx: ResponseTx<(), Error>, name: String) { - let result = self - .delete_custom_list(name) - .await - .map_err(Error::CustomListError); - Self::oneshot_send(tx, result, "delete_custom_list response"); - } - - async fn on_update_custom_list_location( + async fn on_delete_custom_list( &mut self, tx: ResponseTx<(), Error>, - update: CustomListLocationUpdate, + id: mullvad_types::custom_list::Id, ) { - let result = self - .update_custom_list_location(update) - .await - .map_err(Error::CustomListError); - Self::oneshot_send(tx, result, "update_custom_list_location response"); + let result = self.delete_custom_list(id).await; + Self::oneshot_send(tx, result, "delete_custom_list response"); } - async fn on_rename_custom_list( - &mut self, - tx: ResponseTx<(), Error>, - name: String, - new_name: String, - ) { - let result = self - .rename_custom_list(name, new_name) - .await - .map_err(Error::CustomListError); - Self::oneshot_send(tx, result, "rename_custom_list response"); + async fn on_update_custom_list(&mut self, tx: ResponseTx<(), Error>, new_list: CustomList) { + let result = self.update_custom_list(new_list).await; + Self::oneshot_send(tx, result, "update_custom_list response"); } fn on_get_settings(&self, tx: oneshot::Sender) { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 23f2578f5899..53586286403c 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -1,7 +1,4 @@ -use crate::{ - account_history, custom_lists, device, settings, DaemonCommand, DaemonCommandSender, - EventListener, -}; +use crate::{account_history, device, settings, DaemonCommand, DaemonCommandSender, EventListener}; use futures::{ channel::{mpsc, oneshot}, StreamExt, @@ -27,6 +24,7 @@ use mullvad_types::{ use std::path::PathBuf; use std::{ convert::{TryFrom, TryInto}, + str::FromStr, sync::{Arc, Mutex}, time::Duration, }; @@ -586,82 +584,35 @@ impl ManagementService for ManagementServiceImpl { // Custom lists // - async fn list_custom_lists( - &self, - _: Request<()>, - ) -> ServiceResult { - log::debug!("list_custom_lists"); - let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::ListCustomLists(tx))?; - self.wait_for_result(rx) - .await? - .map(|custom_lists| { - Response::new(mullvad_management_interface::types::CustomLists::from( - custom_lists, - )) - }) - .map_err(map_daemon_error) - } - - async fn get_custom_list( - &self, - request: Request, - ) -> ServiceResult { - log::debug!("get_custom_list"); - let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::GetCustomList(tx, request.into_inner()))?; - self.wait_for_result(rx) - .await? - .map(|custom_list| { - Response::new(mullvad_management_interface::types::CustomList::from( - custom_list, - )) - }) - .map_err(map_daemon_error) - } - - async fn create_custom_list(&self, request: Request) -> ServiceResult<()> { + async fn create_custom_list(&self, request: Request) -> ServiceResult { log::debug!("create_custom_list"); let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::CreateCustomList(tx, request.into_inner()))?; self.wait_for_result(rx) .await? - .map(Response::new) + .map(|response| Response::new(response.to_string())) .map_err(map_daemon_error) } async fn delete_custom_list(&self, request: Request) -> ServiceResult<()> { log::debug!("delete_custom_list"); let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::DeleteCustomList(tx, request.into_inner()))?; + self.send_command_to_daemon(DaemonCommand::DeleteCustomList( + tx, + mullvad_types::custom_list::Id::from_str(&request.into_inner()) + .map_err(|_| Status::invalid_argument("invalid ID"))?, + ))?; self.wait_for_result(rx) .await? .map(Response::new) .map_err(map_daemon_error) } - async fn update_custom_list_location( - &self, - request: Request, - ) -> ServiceResult<()> { - log::debug!("update_custom_list_location"); - let custom_list = - mullvad_types::custom_list::CustomListLocationUpdate::try_from(request.into_inner())?; - let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::UpdateCustomListLocation(tx, custom_list))?; - self.wait_for_result(rx) - .await? - .map(Response::new) - .map_err(map_daemon_error) - } - async fn rename_custom_list( - &self, - request: Request, - ) -> ServiceResult<()> { - log::debug!("rename_custom_list"); - let names: (String, String) = From::from(request.into_inner()); + async fn update_custom_list(&self, request: Request) -> ServiceResult<()> { + log::debug!("update_custom_list"); + let custom_list = mullvad_types::custom_list::CustomList::try_from(request.into_inner())?; let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::RenameCustomList(tx, names.0, names.1))?; + self.send_command_to_daemon(DaemonCommand::UpdateCustomList(tx, custom_list))?; self.wait_for_result(rx) .await? .map(Response::new) @@ -1006,7 +957,16 @@ fn map_daemon_error(error: crate::Error) -> Status { DaemonError::NoAccountToken | DaemonError::NoAccountTokenHistory => { Status::unauthenticated(error.to_string()) } - DaemonError::CustomListError(error) => map_custom_list_error(error), + DaemonError::CustomListExists => Status::with_details( + Code::AlreadyExists, + error.to_string(), + mullvad_management_interface::CUSTOM_LIST_LIST_EXISTS_DETAILS.into(), + ), + DaemonError::CustomListNotFound => Status::with_details( + Code::NotFound, + error.to_string(), + mullvad_management_interface::CUSTOM_LIST_LIST_NOT_FOUND_DETAILS.into(), + ), error => Status::unknown(error.to_string()), } } @@ -1087,36 +1047,6 @@ fn map_account_history_error(error: account_history::Error) -> Status { } } -/// Converts an instance of [`mullvad_daemon::account_history::Error`] into a tonic status. -fn map_custom_list_error(error: custom_lists::Error) -> Status { - match error { - custom_lists::Error::ListExists => Status::with_details( - Code::AlreadyExists, - error.to_string(), - mullvad_management_interface::CUSTOM_LIST_LIST_EXISTS_DETAILS.into(), - ), - custom_lists::Error::ListNotFound => Status::with_details( - Code::NotFound, - error.to_string(), - mullvad_management_interface::CUSTOM_LIST_LIST_NOT_FOUND_DETAILS.into(), - ), - custom_lists::Error::CannotAddOrRemoveAny => { - Status::new(Code::InvalidArgument, error.to_string()) - } - custom_lists::Error::LocationExists => Status::with_details( - Code::AlreadyExists, - error.to_string(), - mullvad_management_interface::CUSTOM_LIST_LOCATION_EXISTS_DETAILS.into(), - ), - custom_lists::Error::LocationNotFoundInlist => Status::with_details( - Code::NotFound, - error.to_string(), - mullvad_management_interface::CUSTOM_LIST_LOCATION_NOT_FOUND_DETAILS.into(), - ), - custom_lists::Error::Settings(error) => map_settings_error(error), - } -} - fn map_protobuf_type_err(err: types::FromProtobufTypeError) -> Status { match err { types::FromProtobufTypeError::InvalidArgument(err) => Status::invalid_argument(err), diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 0337e0775f19..f4b3eb961d11 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -69,12 +69,9 @@ service ManagementService { rpc GetWireguardKey(google.protobuf.Empty) returns (PublicKey) {} // Custom lists - rpc ListCustomLists(google.protobuf.Empty) returns (CustomLists) {} - rpc GetCustomList(google.protobuf.StringValue) returns (CustomList) {} - rpc CreateCustomList(google.protobuf.StringValue) returns (google.protobuf.Empty) {} + rpc CreateCustomList(google.protobuf.StringValue) returns (google.protobuf.StringValue) {} rpc DeleteCustomList(google.protobuf.StringValue) returns (google.protobuf.Empty) {} - rpc UpdateCustomListLocation(CustomListLocationUpdate) returns (google.protobuf.Empty) {} - rpc RenameCustomList(CustomListRename) returns (google.protobuf.Empty) {} + rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {} // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} @@ -287,7 +284,7 @@ message BridgeSettings { message LocationConstraint { oneof type { - string customList = 1; + string custom_list = 1; RelayLocation location = 2; } } @@ -319,29 +316,12 @@ message ObfuscationSettings { Udp2TcpObfuscationSettings udp2tcp = 2; } -message CustomListRename { - string name = 1; - string new_name = 2; -} - -message CustomListLocationUpdate { - enum State { - ADD = 0; - REMOVE = 1; - } - State state = 1; - string name = 2; - RelayLocation location = 3; -} - message CustomList { string id = 1; string name = 2; repeated RelayLocation locations = 3; } -message CustomLists { repeated CustomList custom_lists = 1; } - message CustomListSettings { repeated CustomList custom_lists = 1; } message Settings { diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index e9acad133726..a1ddc5e39afa 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -4,7 +4,7 @@ use crate::types; use futures::{Stream, StreamExt}; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, - custom_list::{CustomList, CustomListLocationUpdate}, + custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettingsUpdate}, @@ -16,6 +16,7 @@ use mullvad_types::{ }; #[cfg(target_os = "windows")] use std::path::Path; +use std::str::FromStr; #[cfg(target_os = "windows")] use talpid_types::split_tunnel::ExcludedProcess; use tonic::{Code, Status}; @@ -430,60 +431,27 @@ impl MullvadProxyClient { PublicKey::try_from(key).map_err(Error::InvalidResponse) } - pub async fn list_custom_lists(&mut self) -> Result> { - let result = self - .0 - .list_custom_lists(()) - .await - .map_err(map_custom_list_error)? - .into_inner() - .try_into() - .map_err(Error::InvalidResponse)?; - Ok(result) - } - - pub async fn get_custom_list(&mut self, name: String) -> Result { - let result = self + pub async fn create_custom_list(&mut self, name: String) -> Result { + let id = self .0 - .get_custom_list(name) - .await - .map_err(map_custom_list_error)? - .into_inner() - .try_into() - .map_err(Error::InvalidResponse)?; - Ok(result) - } - - pub async fn create_custom_list(&mut self, name: String) -> Result<()> { - self.0 .create_custom_list(name) .await - .map_err(map_custom_list_error)?; - Ok(()) - } - - pub async fn delete_custom_list(&mut self, name: String) -> Result<()> { - self.0 - .delete_custom_list(name) - .await - .map_err(map_custom_list_error)?; - Ok(()) + .map_err(map_custom_list_error)? + .into_inner(); + Id::from_str(&id).map_err(|_| Error::CustomListListNotFound) } - pub async fn update_custom_list_location( - &mut self, - custom_list_update: CustomListLocationUpdate, - ) -> Result<()> { + pub async fn delete_custom_list(&mut self, id: String) -> Result<()> { self.0 - .update_custom_list_location(types::CustomListLocationUpdate::from(custom_list_update)) + .delete_custom_list(id) .await .map_err(map_custom_list_error)?; Ok(()) } - pub async fn rename_custom_list(&mut self, name: String, new_name: String) -> Result<()> { + pub async fn update_custom_list(&mut self, custom_list: CustomList) -> Result<()> { self.0 - .rename_custom_list(types::CustomListRename::from((name, new_name))) + .update_custom_list(types::CustomList::from(custom_list)) .await .map_err(map_custom_list_error)?; Ok(()) @@ -605,26 +573,19 @@ fn map_location_error(status: Status) -> Error { fn map_custom_list_error(status: Status) -> Error { match status.code() { Code::NotFound => { - let details = status.details(); - if details == crate::CUSTOM_LIST_LOCATION_NOT_FOUND_DETAILS { - Error::LocationNotFoundInCustomlist - } else if details == crate::CUSTOM_LIST_LIST_NOT_FOUND_DETAILS { + if status.details() == crate::CUSTOM_LIST_LIST_NOT_FOUND_DETAILS { Error::CustomListListNotFound } else { Error::Rpc(status) } } Code::AlreadyExists => { - let details = status.details(); - if details == crate::CUSTOM_LIST_LOCATION_EXISTS_DETAILS { - Error::LocationExistsInCustomList - } else if details == crate::CUSTOM_LIST_LIST_EXISTS_DETAILS { + if status.details() == crate::CUSTOM_LIST_LIST_EXISTS_DETAILS { Error::CustomListExists } else { Error::Rpc(status) } } - Code::InvalidArgument => Error::CustomListCannotAddOrRemoveAny, _other => Error::Rpc(status), } } diff --git a/mullvad-management-interface/src/lib.rs b/mullvad-management-interface/src/lib.rs index b2ca7a4079dc..cf1a7988786e 100644 --- a/mullvad-management-interface/src/lib.rs +++ b/mullvad-management-interface/src/lib.rs @@ -26,9 +26,7 @@ use once_cell::sync::Lazy; static MULLVAD_MANAGEMENT_SOCKET_GROUP: Lazy> = Lazy::new(|| env::var("MULLVAD_MANAGEMENT_SOCKET_GROUP").ok()); -pub const CUSTOM_LIST_LOCATION_NOT_FOUND_DETAILS: &[u8] = b"custom_list_location_not_found"; pub const CUSTOM_LIST_LIST_NOT_FOUND_DETAILS: &[u8] = b"custom_list_list_not_found"; -pub const CUSTOM_LIST_LOCATION_EXISTS_DETAILS: &[u8] = b"custom_list_location_exists"; pub const CUSTOM_LIST_LIST_EXISTS_DETAILS: &[u8] = b"custom_list_list_exists"; #[derive(err_derive::Error, Debug)] @@ -100,9 +98,6 @@ pub enum Error { #[error(display = "A custom list with that name does not exist")] CustomListListNotFound, - #[error(display = "Can not add or remove 'any' to or from a custom list")] - CustomListCannotAddOrRemoveAny, - #[error(display = "Location already exists in the custom list")] LocationExistsInCustomList, diff --git a/mullvad-management-interface/src/types/conversions/custom_list.rs b/mullvad-management-interface/src/types/conversions/custom_list.rs index 63bb73652b79..62799168f7c1 100644 --- a/mullvad-management-interface/src/types/conversions/custom_list.rs +++ b/mullvad-management-interface/src/types/conversions/custom_list.rs @@ -1,30 +1,15 @@ -use crate::types::{proto, FromProtobufTypeError}; -use mullvad_types::{custom_list::Id, relay_constraints::GeographicLocationConstraint}; -use proto::RelayLocation; - -impl From<(String, String)> for proto::CustomListRename { - fn from(names: (String, String)) -> Self { - proto::CustomListRename { - name: names.0, - new_name: names.1, - } - } -} +use std::{collections::BTreeSet, str::FromStr}; -impl From for (String, String) { - fn from(names: proto::CustomListRename) -> Self { - (names.name, names.new_name) - } -} +use crate::types::{proto, FromProtobufTypeError}; +use mullvad_types::{ + custom_list::{CustomList, Id}, + relay_constraints::GeographicLocationConstraint, +}; -impl From<&mullvad_types::custom_list::CustomListsSettings> for proto::CustomListSettings { - fn from(settings: &mullvad_types::custom_list::CustomListsSettings) -> Self { +impl From for proto::CustomListSettings { + fn from(settings: mullvad_types::custom_list::CustomListsSettings) -> Self { Self { - custom_lists: settings - .custom_lists - .iter() - .map(|custom_list| proto::CustomList::from(custom_list.clone())) - .collect(), + custom_lists: settings.into_iter().map(proto::CustomList::from).collect(), } } } @@ -33,72 +18,13 @@ impl TryFrom for mullvad_types::custom_list::CustomLi type Error = FromProtobufTypeError; fn try_from(settings: proto::CustomListSettings) -> Result { - Ok(Self { - custom_lists: settings + Ok(Self::from( + settings .custom_lists .into_iter() .map(mullvad_types::custom_list::CustomList::try_from) - .collect::, _>>()?, - }) - } -} - -impl From - for proto::CustomListLocationUpdate -{ - fn from(custom_list: mullvad_types::custom_list::CustomListLocationUpdate) -> Self { - use mullvad_types::relay_constraints::Constraint; - match custom_list { - mullvad_types::custom_list::CustomListLocationUpdate::Add { name, location } => { - let location = match location { - Constraint::Any => None, - Constraint::Only(location) => Some(RelayLocation::from(location)), - }; - Self { - state: i32::from(proto::custom_list_location_update::State::Add), - name, - location, - } - } - mullvad_types::custom_list::CustomListLocationUpdate::Remove { name, location } => { - let location = match location { - Constraint::Any => None, - Constraint::Only(location) => Some(RelayLocation::from(location)), - }; - Self { - state: i32::from(proto::custom_list_location_update::State::Remove), - name, - location, - } - } - } - } -} - -impl TryFrom - for mullvad_types::custom_list::CustomListLocationUpdate -{ - type Error = FromProtobufTypeError; - - fn try_from(custom_list: proto::CustomListLocationUpdate) -> Result { - use mullvad_types::relay_constraints::Constraint; - let location: Constraint = - Constraint::::from( - custom_list - .location - .ok_or(FromProtobufTypeError::InvalidArgument("missing location"))?, - ); - match proto::custom_list_location_update::State::try_from(custom_list.state) { - Ok(proto::custom_list_location_update::State::Add) => Ok(Self::Add { - name: custom_list.name, - location, - }), - Ok(proto::custom_list_location_update::State::Remove) => Ok(Self::Remove { - name: custom_list.name, - location, - }), - Err(_) => Err(FromProtobufTypeError::InvalidArgument("incorrect state")), - } + .collect::, _>>()?, + )) } } @@ -121,18 +47,14 @@ impl TryFrom for mullvad_types::custom_list::CustomList { type Error = FromProtobufTypeError; fn try_from(custom_list: proto::CustomList) -> Result { - let locations: Result, _> = custom_list + let locations = custom_list .locations .into_iter() .map(GeographicLocationConstraint::try_from) - .collect(); - let locations = locations.map_err(|_| { - FromProtobufTypeError::InvalidArgument("Could not convert custom list from proto") - })?; + .collect::, Self::Error>>()?; Ok(Self { - id: Id::try_from(custom_list.id.as_str()).map_err(|_| { - FromProtobufTypeError::InvalidArgument("Id could not be parsed to a uuid") - })?, + id: Id::from_str(&custom_list.id) + .map_err(|_| FromProtobufTypeError::InvalidArgument("Invalid list ID"))?, name: custom_list.name, locations, }) @@ -149,7 +71,7 @@ impl TryFrom for GeographicLocationConstraint { relay_location.hostname.as_ref(), ) { ("", ..) => Err(FromProtobufTypeError::InvalidArgument( - "Relay location formatted incorrectly", + "Invalid geographic relay location", )), (_country, "", "") => Ok(GeographicLocationConstraint::Country( relay_location.country, @@ -174,27 +96,3 @@ impl TryFrom for GeographicLocationConstraint { } } } - -impl From> for proto::CustomLists { - fn from(custom_lists: Vec) -> Self { - let custom_lists = custom_lists - .into_iter() - .map(proto::CustomList::from) - .collect(); - proto::CustomLists { custom_lists } - } -} - -impl TryFrom for Vec { - type Error = FromProtobufTypeError; - - fn try_from(custom_lists: proto::CustomLists) -> Result { - let mut new_custom_lists = Vec::with_capacity(custom_lists.custom_lists.len()); - for custom_list in custom_lists.custom_lists { - new_custom_lists.push(mullvad_types::custom_list::CustomList::try_from( - custom_list, - )?); - } - Ok(new_custom_lists) - } -} diff --git a/mullvad-management-interface/src/types/conversions/relay_constraints.rs b/mullvad-management-interface/src/types/conversions/relay_constraints.rs index a3ec8af0da5f..b7b5f0060bdf 100644 --- a/mullvad-management-interface/src/types/conversions/relay_constraints.rs +++ b/mullvad-management-interface/src/types/conversions/relay_constraints.rs @@ -3,6 +3,7 @@ use mullvad_types::{ custom_list::Id, relay_constraints::{Constraint, RelaySettingsUpdate}, }; +use std::str::FromStr; use talpid_types::net::TunnelType; impl TryFrom<&proto::WireguardConstraints> @@ -494,7 +495,9 @@ impl From for proto::Locat )), }, LocationConstraint::CustomList { list_id } => Self { - r#type: Some(proto::location_constraint::Type::CustomList(list_id)), + r#type: Some(proto::location_constraint::Type::CustomList( + list_id.to_string(), + )), }, } } @@ -521,7 +524,7 @@ impl TryFrom } Some(proto::location_constraint::Type::CustomList(list_id)) => { let location = LocationConstraint::CustomList { - list_id: Id::try_from(list_id.as_str()).map_err(|_| { + list_id: Id::from_str(&list_id).map_err(|_| { FromProtobufTypeError::InvalidArgument("Id could not be parsed to a uuid") })?, }; diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index 5b8bf76d86e8..f123b417553b 100644 --- a/mullvad-management-interface/src/types/conversions/settings.rs +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -39,7 +39,9 @@ impl From<&mullvad_types::settings::Settings> for proto::Settings { &settings.obfuscation_settings, )), split_tunnel, - custom_lists: Some(proto::CustomListSettings::from(&settings.custom_lists)), + custom_lists: Some(proto::CustomListSettings::from( + settings.custom_lists.clone(), + )), } } } diff --git a/mullvad-types/src/custom_list.rs b/mullvad-types/src/custom_list.rs index ee4f914f75aa..58fd046a9e50 100644 --- a/mullvad-types/src/custom_list.rs +++ b/mullvad-types/src/custom_list.rs @@ -1,52 +1,139 @@ -use crate::relay_constraints::{Constraint, GeographicLocationConstraint}; +use crate::relay_constraints::GeographicLocationConstraint; #[cfg(target_os = "android")] -use jnix::{FromJava, IntoJava}; +use jnix::{ + jni::objects::{AutoLocal, JObject, JString}, + FromJava, IntoJava, JnixEnv, +}; use serde::{Deserialize, Serialize}; +use std::{ + collections::BTreeSet, + ops::{Deref, DerefMut}, + str::FromStr, +}; -pub type Id = String; +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub struct Id(uuid::Uuid); + +impl Deref for Id { + type Target = uuid::Uuid; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Id { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl FromStr for Id { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + uuid::Uuid::from_str(s).map(Id) + } +} + +#[cfg(target_os = "android")] +impl<'env, 'sub_env> FromJava<'env, JString<'sub_env>> for Id +where + 'env: 'sub_env, +{ + const JNI_SIGNATURE: &'static str = "Ljava/lang/String;"; + + fn from_java(env: &JnixEnv<'env>, source: JString<'sub_env>) -> Self { + let s = env + .get_string(source) + .expect("Failed to convert from Java String"); + Id::from_str(s.to_str().unwrap()).expect("invalid ID") + } +} + +#[cfg(target_os = "android")] +impl<'env, 'sub_env> FromJava<'env, JObject<'sub_env>> for Id +where + 'env: 'sub_env, +{ + const JNI_SIGNATURE: &'static str = "Ljava/lang/String;"; + + fn from_java(env: &JnixEnv<'env>, source: JObject<'sub_env>) -> Self { + Id::from_java(env, JString::from(source)) + } +} + +#[cfg(target_os = "android")] +impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for Id { + const JNI_SIGNATURE: &'static str = "Ljava/lang/String;"; + + type JavaType = AutoLocal<'env, 'borrow>; + + fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType { + let s = self.to_string(); + let jstring = env.new_string(&s).expect("Failed to create Java String"); + + env.auto_local(jstring) + } +} #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -#[cfg_attr(target_os = "android", derive(FromJava, IntoJava))] -#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] pub struct CustomListsSettings { - pub custom_lists: Vec, + custom_lists: Vec, +} + +impl From> for CustomListsSettings { + fn from(custom_lists: Vec) -> Self { + Self { custom_lists } + } } impl CustomListsSettings { - pub fn get_custom_list_with_name(&self, name: &String) -> Option<&CustomList> { - self.custom_lists - .iter() - .find(|custom_list| &custom_list.name == name) + pub fn add(&mut self, list: CustomList) { + self.custom_lists.push(list); + } + + pub fn remove(&mut self, index: usize) { + self.custom_lists.remove(index); } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum CustomListLocationUpdate { - Add { - name: String, - location: Constraint, - }, - Remove { - name: String, - location: Constraint, - }, +impl IntoIterator for CustomListsSettings { + type Item = CustomList; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.custom_lists.into_iter() + } +} + +impl Deref for CustomListsSettings { + type Target = [CustomList]; + + fn deref(&self) -> &Self::Target { + &self.custom_lists + } +} + +impl DerefMut for CustomListsSettings { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.custom_lists + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[cfg_attr(target_os = "android", derive(FromJava, IntoJava))] -#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] pub struct CustomList { pub id: Id, pub name: String, - pub locations: Vec, + pub locations: BTreeSet, } impl CustomList { pub fn new(name: String) -> Self { CustomList { - id: uuid::Uuid::new_v4().to_string(), + id: Id(uuid::Uuid::new_v4()), name, - locations: Vec::new(), + locations: BTreeSet::new(), } } } diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index dcce9fc9500b..014e84a5ff26 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -247,7 +247,7 @@ impl<'a> fmt::Display for RelaySettingsFormatter<'a> { #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[cfg_attr(target_os = "android", derive(IntoJava, FromJava))] +#[cfg_attr(target_os = "android", derive(FromJava, IntoJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] pub enum LocationConstraint { Location(GeographicLocationConstraint), @@ -271,11 +271,17 @@ impl ResolvedLocationConstraint { Constraint::Only(Self::Location(location)) } Constraint::Only(LocationConstraint::CustomList { list_id }) => custom_lists - .custom_lists .iter() - .find(|custom_list| custom_list.id == list_id) - .map(|custom_list| Constraint::Only(Self::Locations(custom_list.locations.clone()))) - .unwrap_or(Constraint::Any), + .find(|list| list.id == list_id) + .map(|custom_list| { + Constraint::Only(Self::Locations( + custom_list.locations.iter().cloned().collect(), + )) + }) + .unwrap_or_else(|| { + log::warn!("Resolved non-existent custom list"); + Constraint::Only(ResolvedLocationConstraint::Locations(vec![])) + }), } } } @@ -346,10 +352,9 @@ impl<'a> fmt::Display for LocationConstraintFormatter<'a> { match self.constraint { LocationConstraint::Location(location) => write!(f, "{}", location), LocationConstraint::CustomList { list_id } => self - .custom_lists .custom_lists .iter() - .find(|custom_list| &custom_list.id == list_id) + .find(|list| &list.id == list_id) .map(|custom_list| write!(f, "{}", custom_list.name)) .unwrap_or_else(|| write!(f, "invalid custom list")), } @@ -441,7 +446,7 @@ impl<'a> fmt::Display for RelayConstraintsFormatter<'a> { /// Limits the set of [`crate::relay_list::Relay`]s used by a `RelaySelector` based on /// location. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] #[cfg_attr(target_os = "android", derive(FromJava, IntoJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))]