diff options
| -rw-r--r-- | mullvad-types/src/settings/migrations/mod.rs | 68 | ||||
| -rw-r--r-- | mullvad-types/src/settings/migrations/v1.rs | 170 | ||||
| -rw-r--r-- | mullvad-types/src/settings/mod.rs | 11 |
3 files changed, 83 insertions, 166 deletions
diff --git a/mullvad-types/src/settings/migrations/mod.rs b/mullvad-types/src/settings/migrations/mod.rs index 078a6498f9..a732848a7b 100644 --- a/mullvad-types/src/settings/migrations/mod.rs +++ b/mullvad-types/src/settings/migrations/mod.rs @@ -1,15 +1,16 @@ -use super::{Error, Result, Settings}; +use super::{Error, Result}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::io::Read; mod v1; -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] #[repr(u32)] pub enum SettingsVersion { V2 = 2, } +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V2; + impl<'de> Deserialize<'de> for SettingsVersion { fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> where @@ -35,66 +36,33 @@ impl Serialize for SettingsVersion { } -#[derive(Debug)] -enum VersionedSettings { - V1(v1::Settings), - V2(crate::settings::Settings), -} - -impl VersionedSettings { - /// Unrwaps the latest version of settings or panics. - fn unwrap(self) -> Settings { - match self { - VersionedSettings::V2(settings) => settings, - lower => { - panic!("Unexpected settings version - {:?}", lower); - } - } - } -} - - trait SettingsMigration { - fn read(&self, reader: &mut dyn Read) -> Result<VersionedSettings>; - fn migrate(&self, settings: VersionedSettings) -> VersionedSettings; -} - -fn migrations() -> Vec<Box<dyn SettingsMigration>> { - vec![Box::new(v1::Migration)] + fn version_matches(&self, settings: &mut serde_json::Value) -> bool; + fn migrate(&self, settings: &mut serde_json::Value) -> Result<()>; } pub fn try_migrate_settings(mut settings_file: &[u8]) -> Result<crate::settings::Settings> { - let mut migrations_to_apply = vec![]; - let mut valid_settings = None; + let mut settings: serde_json::Value = + serde_json::from_reader(&mut settings_file).map_err(Error::ParseError)?; - let migrations = migrations(); - for migration in migrations.iter() { - match migration.read(&mut settings_file) { - Ok(settings) => { - valid_settings = Some(migration.migrate(settings)); - break; - } - Err(_e) => { - migrations_to_apply.push(migration); - } - }; + if !settings.is_object() { + return Err(Error::NoMatchingVersion); } - if let Some(settings) = valid_settings { - let upgraded_settings = migrations_to_apply - .iter() - .rev() - .fold(settings, |old_settings, migration| { - migration.migrate(old_settings) - }); - return Ok(upgraded_settings.unwrap()); + for migration in &[Box::new(v1::Migration)] { + if !migration.version_matches(&mut settings) { + continue; + } + migration.migrate(&mut settings)?; } - return Err(Error::NoMatchingVersion); + + serde_json::from_value(settings).map_err(Error::ParseError) } #[cfg(test)] mod test { use super::SettingsVersion; + use serde_json; #[test] #[should_panic] diff --git a/mullvad-types/src/settings/migrations/v1.rs b/mullvad-types/src/settings/migrations/v1.rs index 3b4f1a2075..033c25a580 100644 --- a/mullvad-types/src/settings/migrations/v1.rs +++ b/mullvad-types/src/settings/migrations/v1.rs @@ -1,78 +1,33 @@ -use super::{Error, Result, VersionedSettings}; +use super::{Error, Result}; use crate::{ custom_tunnel::CustomTunnelEndpoint, relay_constraints::{ - BridgeConstraints, BridgeSettings, BridgeState, Constraint, LocationConstraint, - OpenVpnConstraints, RelaySettings as NewRelaySettings, WireguardConstraints, + Constraint, LocationConstraint, OpenVpnConstraints, RelaySettings as NewRelaySettings, + WireguardConstraints, }, - settings::TunnelOptions, }; use serde::{Deserialize, Serialize}; -use std::io::Read; use talpid_types::net::TunnelType; -/// Mullvad daemon settings. -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(default)] -pub struct Settings { - account_token: Option<String>, - relay_settings: RelaySettings, - bridge_settings: BridgeSettings, - bridge_state: BridgeState, - /// If the daemon should allow communication with private (LAN) networks. - allow_lan: bool, - /// Extra level of kill switch. When this setting is on, the disconnected state will block - /// the firewall to not allow any traffic in or out. - block_when_disconnected: bool, - /// If the daemon should connect the VPN tunnel directly on start or not. - auto_connect: bool, - /// Options that should be applied to tunnels of a specific type regardless of where the relays - /// might be located. - tunnel_options: TunnelOptions, -} - -impl Default for Settings { - fn default() -> Self { - Settings { - account_token: None, - relay_settings: RelaySettings::Normal(RelayConstraints { - location: Constraint::Only(LocationConstraint::Country("se".to_owned())), - tunnel: Constraint::Any, - }), - bridge_settings: BridgeSettings::Normal(BridgeConstraints::default()), - bridge_state: BridgeState::Auto, - allow_lan: false, - block_when_disconnected: false, - auto_connect: false, - tunnel_options: TunnelOptions::default(), - } - } -} - pub(super) struct Migration; + impl super::SettingsMigration for Migration { - fn read(&self, mut reader: &mut dyn Read) -> Result<VersionedSettings> { - serde_json::from_reader(&mut reader) - .map(VersionedSettings::V1) - .map_err(Error::ParseError) + fn version_matches(&self, settings: &mut serde_json::Value) -> bool { + settings.get("settings_version").is_none() } - fn migrate(&self, old: VersionedSettings) -> VersionedSettings { - match old { - VersionedSettings::V1(old) => VersionedSettings::V2(crate::settings::Settings { - account_token: old.account_token, - relay_settings: migrate_relay_settings(old.relay_settings), - bridge_settings: old.bridge_settings, - bridge_state: old.bridge_state, - allow_lan: old.allow_lan, - block_when_disconnected: old.block_when_disconnected, - auto_connect: old.auto_connect, - tunnel_options: old.tunnel_options, - show_beta_releases: false, - settings_version: super::SettingsVersion::V2, - }), - VersionedSettings::V2(new) => VersionedSettings::V2(new), - } + + fn migrate(&self, settings: &mut serde_json::Value) -> Result<()> { + let old_relay_settings: RelaySettings = + serde_json::from_value(settings["relay_settings"].clone()) + .map_err(Error::ParseError)?; + let new_relay_settings = migrate_relay_settings(old_relay_settings); + + settings["relay_settings"] = serde_json::json!(new_relay_settings); + settings["show_beta_releases"] = serde_json::json!(false); + settings["settings_version"] = serde_json::json!(super::SettingsVersion::V2); + + Ok(()) } } @@ -124,9 +79,10 @@ pub enum TunnelConstraints { #[cfg(test)] mod test { - use super::super::SettingsMigration; + use super::super::try_migrate_settings; use serde_json; - const OLD_SETTINGS: &str = r#" + + pub const NEW_SETTINGS: &str = r#" { "account_token": "1234", "relay_settings": { @@ -136,16 +92,16 @@ mod test { "country": "se" } }, - "tunnel": { - "only": { - "openvpn": { - "port": { - "only": 53 - }, - "protocol": { - "only": "udp" - } - } + "tunnel_protocol": "any", + "wireguard_constraints": { + "port": "any" + }, + "openvpn_constraints": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" } } } @@ -169,11 +125,12 @@ mod test { "generic": { "enable_ipv6": false } - } + }, + "settings_version": 2 } "#; - const NEW_SETTINGS: &str = r#" + const V1_SETTINGS: &str = r#" { "account_token": "1234", "relay_settings": { @@ -183,16 +140,16 @@ mod test { "country": "se" } }, - "tunnel_protocol": "any", - "wireguard_constraints": { - "port": "any" - }, - "openvpn_constraints": { - "port": { - "only": 53 - }, - "protocol": { - "only": "udp" + "tunnel": { + "only": { + "openvpn": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" + } + } } } } @@ -216,12 +173,11 @@ mod test { "generic": { "enable_ipv6": false } - }, - "settings_version": 2 + } } "#; - const SETTINGS_2019V3: &str = r#" + const V1_SETTINGS_2019V3: &str = r#" { "account_token": "1234", "relay_settings": { @@ -265,34 +221,20 @@ mod test { "#; #[test] - fn test_migration() { - let m = super::Migration; - let old_settings = m - .read(&mut OLD_SETTINGS.as_bytes()) - .expect("Failed to deserialize old format"); - let new_settings = serde_json::from_str(&NEW_SETTINGS).unwrap(); + fn test_v1_migration() { + let migrated_settings = + try_migrate_settings(V1_SETTINGS.as_bytes()).expect("Migration failed"); + let new_settings = serde_json::from_str(NEW_SETTINGS).unwrap(); - assert_eq!(&m.migrate(old_settings).unwrap(), &new_settings); + assert_eq!(&migrated_settings, &new_settings); } #[test] - #[should_panic] - fn test_deserialization_failure() { - let m = super::Migration; - m.read(&mut NEW_SETTINGS.as_bytes()) - .expect("Failed to deserialize old format"); - } - - #[test] - fn test_2019v3_migration() { - let m = super::Migration; - let old_settings = m - .read(&mut SETTINGS_2019V3.as_bytes()) - .expect("Failed to deserialize old format"); - - let new_settings = serde_json::from_str(&NEW_SETTINGS).unwrap(); - + fn test_v1_2019v3_migration() { + let migrated_settings = + try_migrate_settings(V1_SETTINGS_2019V3.as_bytes()).expect("Migration failed"); + let new_settings = serde_json::from_str(NEW_SETTINGS).unwrap(); - assert_eq!(&m.migrate(old_settings).unwrap(), &new_settings); + assert_eq!(&migrated_settings, &new_settings); } } diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index c589d1660a..fa5d2dfb8c 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -23,6 +23,9 @@ pub enum Error { #[error(display = "Malformed settings")] ParseError(#[error(source)] serde_json::Error), + #[error(display = "Settings version mismatch")] + VersionMismatch, + #[error(display = "Unable to read any version of the settings")] NoMatchingVersion, } @@ -74,14 +77,18 @@ impl Default for Settings { auto_connect: false, tunnel_options: TunnelOptions::default(), show_beta_releases: false, - settings_version: migrations::SettingsVersion::V2, + settings_version: migrations::CURRENT_SETTINGS_VERSION, } } } impl Settings { pub fn load_from_bytes(bytes: &[u8]) -> Result<Self> { - serde_json::from_slice(bytes).map_err(Error::ParseError) + let settings: Self = serde_json::from_slice(bytes).map_err(Error::ParseError)?; + if settings.settings_version < migrations::CURRENT_SETTINGS_VERSION { + return Err(Error::VersionMismatch); + } + Ok(settings) } pub fn migrate_from_bytes(bytes: &[u8]) -> Result<Self> { |
