diff options
| -rw-r--r-- | mullvad-types/src/settings/migrations/mod.rs | 12 | ||||
| -rw-r--r-- | mullvad-types/src/settings/migrations/v1.rs | 2 | ||||
| -rw-r--r-- | mullvad-types/src/settings/migrations/v2.rs | 174 | ||||
| -rw-r--r-- | mullvad-types/src/settings/mod.rs | 20 |
4 files changed, 190 insertions, 18 deletions
diff --git a/mullvad-types/src/settings/migrations/mod.rs b/mullvad-types/src/settings/migrations/mod.rs index a732848a7b..0d40e1e564 100644 --- a/mullvad-types/src/settings/migrations/mod.rs +++ b/mullvad-types/src/settings/migrations/mod.rs @@ -1,15 +1,17 @@ use super::{Error, Result}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; mod v1; +mod v2; #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] #[repr(u32)] pub enum SettingsVersion { V2 = 2, + V3 = 3, } -pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V2; +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V3; impl<'de> Deserialize<'de> for SettingsVersion { fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> @@ -18,6 +20,7 @@ impl<'de> Deserialize<'de> for SettingsVersion { { match <u32>::deserialize(deserializer)? { v if v == SettingsVersion::V2 as u32 => Ok(SettingsVersion::V2), + v if v == SettingsVersion::V3 as u32 => Ok(SettingsVersion::V3), v => Err(serde::de::Error::custom(format!( "{} is not a valid SettingsVersion", v @@ -49,7 +52,10 @@ pub fn try_migrate_settings(mut settings_file: &[u8]) -> Result<crate::settings: return Err(Error::NoMatchingVersion); } - for migration in &[Box::new(v1::Migration)] { + let migrations: Vec<Box<dyn SettingsMigration>> = + vec![Box::new(v1::Migration), Box::new(v2::Migration)]; + + for migration in &migrations { if !migration.version_matches(&mut settings) { continue; } @@ -73,7 +79,7 @@ mod test { #[test] #[should_panic] fn test_deserialization_failure_version_too_big() { - let _version: SettingsVersion = serde_json::from_str("3").expect("Version too big"); + let _version: SettingsVersion = serde_json::from_str("4").expect("Version too big"); } #[test] diff --git a/mullvad-types/src/settings/migrations/v1.rs b/mullvad-types/src/settings/migrations/v1.rs index 033c25a580..5d3a2d390d 100644 --- a/mullvad-types/src/settings/migrations/v1.rs +++ b/mullvad-types/src/settings/migrations/v1.rs @@ -126,7 +126,7 @@ mod test { "enable_ipv6": false } }, - "settings_version": 2 + "settings_version": 3 } "#; diff --git a/mullvad-types/src/settings/migrations/v2.rs b/mullvad-types/src/settings/migrations/v2.rs new file mode 100644 index 0000000000..7f57ed9075 --- /dev/null +++ b/mullvad-types/src/settings/migrations/v2.rs @@ -0,0 +1,174 @@ +use super::{Error, Result, SettingsVersion}; +use crate::wireguard::{MAX_ROTATION_INTERVAL, MIN_ROTATION_INTERVAL}; +use std::time::Duration; + + +pub(super) struct Migration; + +impl super::SettingsMigration for Migration { + fn version_matches(&self, settings: &mut serde_json::Value) -> bool { + settings + .get("settings_version") + .map(|version| version == SettingsVersion::V2 as u64) + .unwrap_or(false) + } + + fn migrate(&self, settings: &mut serde_json::Value) -> Result<()> { + // `show_beta_releases` used to be nullable + if settings + .get_mut("show_beta_releases") + .map(|val| val.is_null()) + .unwrap_or(false) + { + settings + .as_object_mut() + .ok_or(Error::NoMatchingVersion)? + .remove("show_beta_releases"); + } + + let automatic_rotation = || -> Option<u64> { + settings + .get("tunnel_options")? + .get("wireguard")? + .get("automatic_rotation") + .map(|ivl| ivl.as_u64())? + }(); + + if let Some(interval) = automatic_rotation { + let new_ivl = match Duration::from_secs(60 * 60 * interval) { + ivl if ivl < MIN_ROTATION_INTERVAL => { + log::warn!("Increasing key rotation interval since it is below minimum"); + MIN_ROTATION_INTERVAL + } + ivl if ivl > MAX_ROTATION_INTERVAL => { + log::warn!("Decreasing key rotation interval since it is above maximum"); + MAX_ROTATION_INTERVAL + } + ivl => ivl, + }; + + settings["tunnel_options"]["wireguard"]["rotation_interval"] = + serde_json::json!(new_ivl); + } + + settings["settings_version"] = serde_json::json!(SettingsVersion::V3); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::super::try_migrate_settings; + use serde_json; + + const V2_SETTINGS: &str = r#" +{ + "account_token": "1234", + "relay_settings": { + "normal": { + "location": { + "only": { + "country": "se" + } + }, + "tunnel": { + "only": { + "openvpn": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" + } + } + } + } + } + }, + "bridge_settings": { + "normal": { + "location": "any" + } + }, + "bridge_state": "auto", + "allow_lan": true, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "automatic_rotation": 24 + }, + "generic": { + "enable_ipv6": false + } + } +} +"#; + + pub const NEW_SETTINGS: &str = r#" +{ + "account_token": "1234", + "relay_settings": { + "normal": { + "location": { + "only": { + "country": "se" + } + }, + "tunnel_protocol": "any", + "wireguard_constraints": { + "port": "any" + }, + "openvpn_constraints": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" + } + } + } + }, + "bridge_settings": { + "normal": { + "location": "any" + } + }, + "bridge_state": "auto", + "allow_lan": true, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "rotation_interval": { + "secs": 86400, + "nanos": 0 + } + }, + "generic": { + "enable_ipv6": false + } + }, + "settings_version": 3 +} +"#; + + + #[test] + fn test_v2_migration() { + let migrated_settings = + try_migrate_settings(V2_SETTINGS.as_bytes()).expect("Migration failed"); + let new_settings = serde_json::from_str(NEW_SETTINGS).unwrap(); + + assert_eq!(&migrated_settings, &new_settings); + } +} diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index a69d6f4012..9ea32fdb3b 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -55,7 +55,6 @@ pub struct Settings { /// might be located. pub tunnel_options: TunnelOptions, /// Whether to notify users of beta updates. - #[serde(deserialize_with = "deserialize_show_beta_releases")] pub show_beta_releases: bool, /// Specifies settings schema version #[cfg_attr(target_os = "android", jnix(skip))] @@ -208,21 +207,14 @@ impl Default for TunnelOptions { } } -/// Used to deserialize the `show_beta_releases` field in the settings struct, as it used to be -/// a nullable field, but it is no longer. -fn deserialize_show_beta_releases<'de, D: serde::de::Deserializer<'de>>( - field: D, -) -> std::result::Result<bool, D::Error> { - Option::deserialize(field).map(|value| value.unwrap_or(false)) -} #[cfg(test)] mod test { use super::*; #[test] - fn test_deserialization_of_2020_4_format() { - let old_settings = br#"{ + fn test_deserialization() { + let settings = br#"{ "account_token": "0000000000000000", "relay_settings": { "normal": { @@ -258,16 +250,16 @@ mod test { }, "wireguard": { "mtu": null, - "automatic_rotation": null + "rotation_interval": null }, "generic": { "enable_ipv6": true } }, - "settings_version": 2, - "show_beta_releases": null + "settings_version": 3, + "show_beta_releases": false }"#; - let _ = Settings::load_from_bytes(old_settings).unwrap(); + let _ = Settings::load_from_bytes(settings).unwrap(); } } |
