summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--mullvad-daemon/Cargo.toml4
-rw-r--r--mullvad-daemon/src/account_history.rs4
-rw-r--r--mullvad-daemon/src/event_loop.rs2
-rw-r--r--mullvad-daemon/src/geoip.rs2
-rw-r--r--mullvad-daemon/src/lib.rs27
-rw-r--r--mullvad-daemon/src/relays.rs2
-rw-r--r--mullvad-daemon/src/version_check.rs444
-rw-r--r--mullvad-daemon/src/wireguard.rs2
-rw-r--r--mullvad-rpc/Cargo.toml1
-rw-r--r--mullvad-rpc/src/lib.rs32
-rw-r--r--mullvad-types/src/version.rs6
12 files changed, 391 insertions, 136 deletions
diff --git a/Cargo.lock b/Cargo.lock
index bb0f1c8344..ed008e0bec 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1520,6 +1520,7 @@ dependencies = [
"ipnetwork 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"mullvad-types 0.1.0",
+ "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"talpid-types 0.1.0",
diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml
index c69cf1cbd4..0ebffd12ea 100644
--- a/mullvad-daemon/Cargo.toml
+++ b/mullvad-daemon/Cargo.toml
@@ -13,7 +13,8 @@ chrono = { version = "0.4", features = ["serde"] }
clap = "2.25"
err-derive = "0.2.1"
fern = { version = "0.5", features = ["colored"] }
-futures = "0.1"
+futures01 = { package = "futures", version = "0.1" }
+futures = { package = "futures", version = "0.3", features = [ "compat" ]}
ipnetwork = "0.16"
jsonrpc-client-core = "0.5"
jsonrpc-core = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" }
@@ -28,6 +29,7 @@ rand = "0.7"
regex = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
+tokio02 = { package = "tokio", version = "0.2", features = [ "io-util", "process", "rt-core", "rt-threaded", "stream", "fs"] }
tokio-core = "0.1"
tokio-retry = "0.2"
tokio-timer = "0.1"
diff --git a/mullvad-daemon/src/account_history.rs b/mullvad-daemon/src/account_history.rs
index 23dd85a487..4a96428d94 100644
--- a/mullvad-daemon/src/account_history.rs
+++ b/mullvad-daemon/src/account_history.rs
@@ -1,7 +1,7 @@
#[cfg(target_os = "android")]
-use futures::future::{Executor, Future};
+use futures01::future::{Executor, Future};
#[cfg(not(target_os = "android"))]
-use futures::{
+use futures01::{
future::{self, Executor, Future},
sync::oneshot,
};
diff --git a/mullvad-daemon/src/event_loop.rs b/mullvad-daemon/src/event_loop.rs
index 238b9f4eef..58f638a2ac 100644
--- a/mullvad-daemon/src/event_loop.rs
+++ b/mullvad-daemon/src/event_loop.rs
@@ -1,4 +1,4 @@
-use futures::{sync::oneshot, Future};
+use futures01::{sync::oneshot, Future};
use std::thread;
use tokio_core::reactor::{Core, Remote};
diff --git a/mullvad-daemon/src/geoip.rs b/mullvad-daemon/src/geoip.rs
index 5cc3085bfe..991ced7e5f 100644
--- a/mullvad-daemon/src/geoip.rs
+++ b/mullvad-daemon/src/geoip.rs
@@ -1,4 +1,4 @@
-use futures::{self, Future};
+use futures01::{self, Future};
use mullvad_rpc::{self, rest::RequestServiceHandle};
use mullvad_types::location::{AmIMullvad, GeoIpLocation};
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 63dca09237..a3a856f792 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -1,4 +1,5 @@
#![deny(rust_2018_idioms)]
+#![recursion_limit = "256"]
#[macro_use]
extern crate serde;
@@ -17,7 +18,7 @@ mod settings;
pub mod version;
mod version_check;
-use futures::{
+use futures01::{
future::{self, Executor},
stream::Wait,
sync::{
@@ -337,7 +338,7 @@ pub struct DaemonCommandChannel {
impl DaemonCommandChannel {
pub fn new() -> Self {
- let (untracked_sender, receiver) = futures::sync::mpsc::unbounded();
+ let (untracked_sender, receiver) = futures01::sync::mpsc::unbounded();
let sender = DaemonCommandSender(Arc::new(untracked_sender));
Self { sender, receiver }
@@ -464,6 +465,7 @@ pub struct Daemon<L: EventListener> {
rpc_runtime: mullvad_rpc::MullvadRpcRuntime,
rpc_handle: mullvad_rpc::rest::MullvadRestHandle,
wireguard_key_manager: wireguard::KeyManager,
+ version_updater_handle: version_check::VersionUpdaterHandle,
core_handle: event_loop::CoreHandle,
relay_selector: relays::RelaySelector,
last_generated_relay: Option<Relay>,
@@ -510,14 +512,6 @@ where
let (internal_event_tx, internal_event_rx) = command_channel.destructure();
- let app_version_info = version_check::load_cache(&cache_dir);
- let version_check_future = version_check::VersionUpdater::new(
- rpc_handle.clone(),
- cache_dir.clone(),
- internal_event_tx.to_specialized_sender(),
- app_version_info.clone(),
- );
- core_handle.remote.spawn(|_| version_check_future);
let mut settings = SettingsPersister::load(&settings_dir);
@@ -525,6 +519,15 @@ where
let _ = settings.set_show_beta_releases(true);
}
+ let app_version_info = version_check::load_cache(&cache_dir);
+ let (version_updater, version_updater_handle) = version_check::VersionUpdater::new(
+ rpc_handle.clone(),
+ cache_dir.clone(),
+ internal_event_tx.to_specialized_sender(),
+ app_version_info.clone(),
+ settings.show_beta_releases,
+ );
+ rpc_runtime.runtime().spawn(version_updater.run());
let account_history = account_history::AccountHistory::new(
&cache_dir,
&settings_dir,
@@ -609,6 +612,7 @@ where
accounts_proxy: AccountsProxy::new(rpc_handle.clone()),
rpc_handle,
wireguard_key_manager,
+ version_updater_handle,
core_handle,
relay_selector,
last_generated_relay: None,
@@ -1458,6 +1462,9 @@ where
if settings_changed {
self.event_listener
.notify_settings(self.settings.to_settings());
+ let runtime = self.rpc_runtime.runtime();
+ let mut handle = self.version_updater_handle.clone();
+ runtime.block_on(async { handle.set_show_beta_releases(enabled).await });
}
}
Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")),
diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs
index 44c1d7ef90..2f8e7a7c61 100644
--- a/mullvad-daemon/src/relays.rs
+++ b/mullvad-daemon/src/relays.rs
@@ -2,7 +2,7 @@
//! updated as well.
use chrono::{DateTime, Local};
-use futures::Future;
+use futures01::Future;
use mullvad_rpc::{rest::MullvadRestHandle, RelayListProxy};
use mullvad_types::{
endpoint::MullvadEndpoint,
diff --git a/mullvad-daemon/src/version_check.rs b/mullvad-daemon/src/version_check.rs
index 7a4446708d..c08238d23f 100644
--- a/mullvad-daemon/src/version_check.rs
+++ b/mullvad-daemon/src/version_check.rs
@@ -1,20 +1,33 @@
-use crate::{version::PRODUCT_VERSION, DaemonEventSender};
-use futures::{Async, Future, Poll};
+use crate::{
+ version::{is_beta_version, PRODUCT_VERSION},
+ DaemonEventSender,
+};
+use futures::{channel::mpsc, stream::FusedStream, FutureExt, SinkExt, StreamExt, TryFutureExt};
use mullvad_rpc::{rest::MullvadRestHandle, AppVersionProxy};
use mullvad_types::version::AppVersionInfo;
+use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{
- fs::File,
+ cmp::{Ord, Ordering, PartialOrd},
+ fs,
+ future::Future,
io,
path::{Path, PathBuf},
time::{Duration, Instant},
};
use talpid_core::mpsc::Sender;
use talpid_types::ErrorExt;
-use tokio_timer::{TimeoutError, Timer};
+use tokio02::fs::File;
const VERSION_INFO_FILENAME: &str = "version-info.json";
+lazy_static::lazy_static! {
+ static ref STABLE_REGEX: Regex = Regex::new(r"^(\d{4})\.(\d+)$").unwrap();
+ static ref BETA_REGEX: Regex = Regex::new(r"^(\d{4})\.(\d+)-beta(\d+)$").unwrap();
+ static ref APP_VERSION: Option<AppVersion> = AppVersion::from_str(PRODUCT_VERSION);
+ static ref IS_DEV_BUILD: bool = APP_VERSION.is_some();
+}
+
const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(15);
/// How often the updater should wake up to check the in-memory cache.
/// This exist to prevent problems around sleeping. If you set it to sleep
@@ -56,17 +69,14 @@ impl From<AppVersionInfo> for CachedAppVersionInfo {
#[error(no_from)]
pub enum Error {
#[error(display = "Failed to open app version cache file for reading")]
- ReadCachedRelays(#[error(source)] io::Error),
+ ReadVersionCache(#[error(source)] io::Error),
#[error(display = "Failed to open app version cache file for writing")]
- WriteRelayCache(#[error(source)] io::Error),
+ WriteVersionCache(#[error(source)] io::Error),
#[error(display = "Failure in serialization of the version info")]
Serialize(#[error(source)] serde_json::Error),
- #[error(display = "Timed out when trying to check the latest app version")]
- DownloadTimeout,
-
#[error(display = "Failed to check the latest app version")]
Download(#[error(source)] mullvad_rpc::rest::Error),
@@ -74,12 +84,6 @@ pub enum Error {
CacheVersionMismatch,
}
-impl<T> From<TimeoutError<T>> for Error {
- fn from(_: TimeoutError<T>) -> Error {
- Error::DownloadTimeout
- }
-}
-
pub(crate) struct VersionUpdater {
version_proxy: AppVersionProxy,
@@ -87,117 +91,199 @@ pub(crate) struct VersionUpdater {
update_sender: DaemonEventSender<AppVersionInfo>,
last_app_version_info: AppVersionInfo,
next_update_time: Instant,
- state: VersionUpdaterState,
+ show_beta_releases: bool,
+ rx: Option<mpsc::Receiver<bool>>,
+}
+
+#[derive(Clone)]
+pub(crate) struct VersionUpdaterHandle {
+ tx: mpsc::Sender<bool>,
}
-enum VersionUpdaterState {
- Sleeping(tokio_timer::Sleep),
- Updating(Box<dyn Future<Item = AppVersionInfo, Error = Error> + Send + 'static>),
+impl VersionUpdaterHandle {
+ pub async fn set_show_beta_releases(&mut self, show_beta_releases: bool) {
+ if self.tx.send(show_beta_releases).await.is_err() {
+ log::error!("Version updater already down, can't send new `show_beta_releases` state");
+ }
+ }
}
impl VersionUpdater {
pub fn new(
- rpc_handle: MullvadRestHandle,
+ mut rpc_handle: MullvadRestHandle,
cache_dir: PathBuf,
update_sender: DaemonEventSender<AppVersionInfo>,
last_app_version_info: AppVersionInfo,
- ) -> Self {
+ show_beta_releases: bool,
+ ) -> (Self, VersionUpdaterHandle) {
+ rpc_handle.factory.timeout = DOWNLOAD_TIMEOUT;
let version_proxy = AppVersionProxy::new(rpc_handle);
let cache_path = cache_dir.join(VERSION_INFO_FILENAME);
- Self {
- version_proxy,
- cache_path,
- update_sender,
- last_app_version_info,
- next_update_time: Instant::now(),
- state: VersionUpdaterState::Sleeping(Self::create_sleep_future()),
- }
- }
+ let (tx, rx) = mpsc::channel(1);
- fn create_sleep_future() -> tokio_timer::Sleep {
- Timer::default().sleep(UPDATE_CHECK_INTERVAL)
+ (
+ Self {
+ version_proxy,
+ cache_path,
+ update_sender,
+ last_app_version_info,
+ next_update_time: Instant::now(),
+ show_beta_releases,
+ rx: Some(rx),
+ },
+ VersionUpdaterHandle { tx },
+ )
}
fn create_update_future(
- &mut self,
- ) -> Box<dyn Future<Item = AppVersionInfo, Error = Error> + Send + 'static> {
- let download_future = self
- .version_proxy
- .version_check(PRODUCT_VERSION.to_owned(), PLATFORM)
- .map_err(Error::Download);
- let future = Timer::default().timeout(download_future, DOWNLOAD_TIMEOUT);
- Box::new(future)
+ &self,
+ ) -> impl Future<Output = Result<mullvad_rpc::AppVersionResponse, Error>> + Send + 'static {
+ let version_proxy = self.version_proxy.clone();
+ let download_future_factory = move || {
+ let response = version_proxy.version_check(PRODUCT_VERSION.to_owned(), PLATFORM);
+ response.map_err(Error::Download)
+ };
+
+ let should_retry = |result: &Result<_, _>| -> bool { result.is_err() };
+
+ Box::pin(talpid_core::future_retry::retry_future_with_backoff(
+ download_future_factory,
+ should_retry,
+ std::iter::repeat(UPDATE_INTERVAL_ERROR),
+ ))
}
- fn write_cache(&self) -> Result<(), Error> {
+ async fn write_cache(&self) -> Result<(), Error> {
log::debug!(
"Writing version check cache to {}",
self.cache_path.display()
);
- let file = File::create(&self.cache_path).map_err(Error::WriteRelayCache)?;
+ let mut file = File::create(&self.cache_path)
+ .await
+ .map_err(Error::WriteVersionCache)?;
let cached_app_version = CachedAppVersionInfo::from(self.last_app_version_info.clone());
- serde_json::to_writer_pretty(io::BufWriter::new(file), &cached_app_version)
- .map_err(Error::Serialize)
+ let mut buf = serde_json::to_vec_pretty(&cached_app_version).map_err(Error::Serialize)?;
+ let mut read_buf: &[u8] = buf.as_mut();
+
+ let _ = tokio02::io::copy(&mut read_buf, &mut file)
+ .await
+ .map_err(Error::WriteVersionCache)?;
+ Ok(())
}
-}
-impl Future for VersionUpdater {
- type Item = ();
- type Error = ();
+ fn response_to_version_info(
+ &mut self,
+ response: mullvad_rpc::AppVersionResponse,
+ ) -> AppVersionInfo {
+ let suggested_upgrade = APP_VERSION.and_then(|current_version| {
+ Self::suggested_upgrade(
+ &current_version,
+ &response,
+ self.show_beta_releases || is_beta_version(),
+ )
+ });
+
+ AppVersionInfo {
+ supported: response.supported,
+ latest_stable: response.latest_stable.unwrap_or_else(|| "".to_owned()),
+ latest_beta: response.latest_beta,
+ suggested_upgrade,
+ }
+ }
+
+ fn suggested_upgrade(
+ current_version: &AppVersion,
+ response: &mullvad_rpc::AppVersionResponse,
+ show_beta: bool,
+ ) -> Option<String> {
+ let stable_version = response
+ .latest_stable
+ .as_ref()
+ .and_then(|stable| AppVersion::from_str(stable));
+
+ let beta_version = if show_beta {
+ AppVersion::from_str(&response.latest_beta)
+ } else {
+ None
+ };
+
+ let latest_version = stable_version.iter().chain(beta_version.iter()).max()?;
+
+ if current_version < latest_version {
+ Some(latest_version.to_string())
+ } else {
+ None
+ }
+ }
+
+ pub async fn run(mut self) {
+ let mut rx = self.rx.take().unwrap().fuse();
+ let next_delay = || tokio02::time::delay_for(UPDATE_CHECK_INTERVAL).fuse();
+ let mut check_delay = next_delay();
+ let mut version_check = futures::future::Fuse::terminated();
+
+ // If this is a dev build ,there's no need to pester the API for version checks.
+ if *IS_DEV_BUILD {
+ while let Some(_) = rx.next().await {}
+ return;
+ }
- fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
- if self.update_sender.is_closed() {
- log::warn!("Version update receiver is closed, stopping version updater");
- return Ok(Async::Ready(()));
- }
- let next_state = match &mut self.state {
- VersionUpdaterState::Sleeping(timer) => match timer.poll() {
- Ok(Async::NotReady) => return Ok(Async::NotReady),
- Err(e) => {
- log::error!("Version check sleep error: {}", e);
- return Err(());
+ futures::select! {
+ show_beta_releases = rx.next() => {
+ match show_beta_releases {
+ Some(show_beta_releases ) => {
+ self.show_beta_releases = show_beta_releases;
+ },
+ // time to shut down
+ None => {
+ return;
+ },
+ }
+ },
+
+ _sleep = check_delay => {
+ if rx.is_terminated() || self.update_sender.is_closed() {
+ return;
}
- Ok(Async::Ready(())) => {
- if Instant::now() > self.next_update_time {
- VersionUpdaterState::Updating(self.create_update_future())
- } else {
- VersionUpdaterState::Sleeping(Self::create_sleep_future())
- }
+
+ if Instant::now() > self.next_update_time {
+ let download_future = self.create_update_future().fuse();
+ version_check = download_future;
+ } else {
+ check_delay = next_delay();
}
+
},
- VersionUpdaterState::Updating(future) => match future.poll() {
- Ok(Async::NotReady) => return Ok(Async::NotReady),
- Err(error) => {
- log::error!("{}", error.display_chain_with_msg("Version check failed"));
- self.next_update_time = Instant::now() + UPDATE_INTERVAL_ERROR;
- VersionUpdaterState::Sleeping(Self::create_sleep_future())
+
+ response = version_check => {
+ if rx.is_terminated() || self.update_sender.is_closed() {
+ return;
}
- Ok(Async::Ready(app_version_info)) => {
- log::debug!("Got new version check: {:?}", app_version_info);
- self.next_update_time = Instant::now() + UPDATE_INTERVAL;
- if app_version_info != self.last_app_version_info {
- if self.update_sender.send(app_version_info.clone()).is_err() {
- log::warn!(
- "Version update receiver is closed, stopping version updater"
- );
- return Ok(Async::Ready(()));
+ self.next_update_time = Instant::now() + UPDATE_INTERVAL;
+
+ match response {
+ Ok(version_info_response) => {
+ let new_version_info = self.response_to_version_info(version_info_response);
+ // if daemon can't be reached, return immediately
+ if self.update_sender.send(new_version_info.clone()).is_err() {
+ return;
}
- self.last_app_version_info = app_version_info;
- if let Err(e) = self.write_cache() {
- log::error!(
- "{}",
- e.display_chain_with_msg(
- "Unable to cache version check response"
- )
- );
+
+ self.last_app_version_info = new_version_info;
+ if let Err(err) = self.write_cache().await {
+ log::error!("Failed to save version cache to disk: {}", err);
+
}
- }
- VersionUpdaterState::Sleeping(Self::create_sleep_future())
+ },
+ Err(err) => {
+ log::error!("Failed to get fetch version info - {}", err);
+ },
}
+
+ check_delay = next_delay();
},
- };
- self.state = next_state;
+ }
}
}
}
@@ -205,7 +291,7 @@ impl Future for VersionUpdater {
fn try_load_cache(cache_dir: &Path) -> Result<AppVersionInfo, Error> {
let path = cache_dir.join(VERSION_INFO_FILENAME);
log::debug!("Loading version check cache from {}", path.display());
- let file = File::open(&path).map_err(Error::ReadCachedRelays)?;
+ let file = fs::File::open(&path).map_err(Error::ReadVersionCache)?;
let version_info: CachedAppVersionInfo =
serde_json::from_reader(io::BufReader::new(file)).map_err(Error::Serialize)?;
@@ -226,11 +312,179 @@ pub fn load_cache(cache_dir: &Path) -> AppVersionInfo {
);
// If we don't have a cache, start out with sane defaults.
AppVersionInfo {
- supported: true,
+ supported: *IS_DEV_BUILD,
latest_stable: PRODUCT_VERSION.to_owned(),
latest_beta: PRODUCT_VERSION.to_owned(),
- latest: PRODUCT_VERSION.to_owned(),
+ suggested_upgrade: None,
+ }
+ }
+ }
+}
+
+#[derive(Eq, PartialEq, Debug, Copy, Clone)]
+enum AppVersion {
+ Stable(u32, u32),
+ Beta(u32, u32, u32),
+}
+
+impl AppVersion {
+ fn from_str(version: &str) -> Option<Self> {
+ let get_int = |cap: &regex::Captures<'_>, idx| cap.get(idx)?.as_str().parse().ok();
+
+ if let Some(caps) = STABLE_REGEX.captures(version) {
+ let year = get_int(&caps, 1)?;
+ let version = get_int(&caps, 2)?;
+ Some(Self::Stable(year, version))
+ } else if let Some(caps) = BETA_REGEX.captures(version) {
+ let year = get_int(&caps, 1)?;
+ let version = get_int(&caps, 2)?;
+ let beta_version = get_int(&caps, 3)?;
+ Some(Self::Beta(year, version, beta_version))
+ } else {
+ None
+ }
+ }
+}
+
+impl Ord for AppVersion {
+ fn cmp(&self, other: &Self) -> Ordering {
+ use AppVersion::*;
+ match (self, other) {
+ (Stable(year, version), Stable(other_year, other_version)) => {
+ year.cmp(other_year).then(version.cmp(other_version))
}
+ // A stable version of the same year and version is always greater than a beta
+ (Stable(year, version), Beta(other_year, other_version, _)) => year
+ .cmp(other_year)
+ .then(version.cmp(other_version))
+ .then(Ordering::Greater),
+ (
+ Beta(year, version, beta_version),
+ Beta(other_year, other_version, other_beta_version),
+ ) => year
+ .cmp(other_year)
+ .then(version.cmp(other_version))
+ .then(beta_version.cmp(other_beta_version)),
+ (Beta(year, version, _beta_version), Stable(other_year, other_version)) => year
+ .cmp(other_year)
+ .then(version.cmp(other_version))
+ .then(Ordering::Less),
+ }
+ }
+}
+
+impl PartialOrd for AppVersion {
+ fn partial_cmp(&self, other: &AppVersion) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl ToString for AppVersion {
+ fn to_string(&self) -> String {
+ match self {
+ Self::Stable(year, version) => format!("{}.{}", year, version),
+ Self::Beta(year, version, beta_version) => {
+ format!("{}.{}-beta{}", year, version, beta_version)
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_version_regex() {
+ assert!(STABLE_REGEX.is_match("2020.4"));
+ assert!(!STABLE_REGEX.is_match("2020.4-beta3"));
+ assert!(BETA_REGEX.is_match("2020.4-beta3"));
+ assert!(!STABLE_REGEX.is_match("2020.5-beta1-dev-f16be4"));
+ assert!(!STABLE_REGEX.is_match("2020.5-dev-f16be4"));
+ assert!(!BETA_REGEX.is_match("2020.5-beta1-dev-f16be4"));
+ assert!(!BETA_REGEX.is_match("2020.5-dev-f16be4"));
+ assert!(!BETA_REGEX.is_match("2020.4"));
+ }
+
+ #[test]
+ fn test_version_parsing() {
+ let tests = vec![
+ ("2020.4", Some(AppVersion::Stable(2020, 4))),
+ ("2020.4-beta3", Some(AppVersion::Beta(2020, 4, 3))),
+ ("2020.15-beta1-dev-f16be4", None),
+ ("2020.15-dev-f16be4", None),
+ ("", None),
+ ];
+
+ for (input, expected_output) in tests {
+ assert_eq!(AppVersion::from_str(&input), expected_output,);
}
}
+
+ #[test]
+ fn test_version_upgrade_suggestions() {
+ let app_version_info = mullvad_rpc::AppVersionResponse {
+ supported: true,
+ latest: "2020.5-beta3".to_owned(),
+ latest_stable: Some("2020.4".to_string()),
+ latest_beta: "2020.5-beta3".to_string(),
+ };
+
+ let older_stable = AppVersion::from_str("2020.3").unwrap();
+ let current_stable = AppVersion::from_str("2020.4").unwrap();
+ let newer_stable = AppVersion::from_str("2021.5").unwrap();
+
+ let older_beta = AppVersion::from_str("2020.3-beta3").unwrap();
+ let current_beta = AppVersion::from_str("2020.5-beta3").unwrap();
+ let newer_beta = AppVersion::from_str("2021.5-beta3").unwrap();
+
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&older_stable, &app_version_info, false),
+ Some("2020.4".to_owned())
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&older_stable, &app_version_info, true),
+ Some("2020.5-beta3".to_owned())
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&current_stable, &app_version_info, false),
+ None
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&current_stable, &app_version_info, true),
+ Some("2020.5-beta3".to_owned())
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&newer_stable, &app_version_info, false),
+ None
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&newer_stable, &app_version_info, true),
+ None
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&older_beta, &app_version_info, false),
+ Some("2020.4".to_owned())
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&older_beta, &app_version_info, true),
+ Some("2020.5-beta3".to_owned())
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&current_beta, &app_version_info, false),
+ None
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&current_beta, &app_version_info, true),
+ None
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&newer_beta, &app_version_info, false),
+ None
+ );
+ assert_eq!(
+ VersionUpdater::suggested_upgrade(&newer_beta, &app_version_info, true),
+ None
+ );
+ }
}
diff --git a/mullvad-daemon/src/wireguard.rs b/mullvad-daemon/src/wireguard.rs
index 1b5913699f..7cf1209429 100644
--- a/mullvad-daemon/src/wireguard.rs
+++ b/mullvad-daemon/src/wireguard.rs
@@ -1,6 +1,6 @@
use crate::{account_history::AccountHistory, DaemonEventSender, InternalDaemonEvent};
use chrono::offset::Utc;
-use futures::{future::Executor, stream::Stream, sync::oneshot, Async, Future, Poll};
+use futures01::{future::Executor, stream::Stream, sync::oneshot, Async, Future, Poll};
use mullvad_rpc::rest::{Error as RestError, MullvadRestHandle};
use mullvad_types::account::AccountToken;
pub use mullvad_types::wireguard::*;
diff --git a/mullvad-rpc/Cargo.toml b/mullvad-rpc/Cargo.toml
index 3bd6a78fe4..3c5a29a617 100644
--- a/mullvad-rpc/Cargo.toml
+++ b/mullvad-rpc/Cargo.toml
@@ -16,6 +16,7 @@ http = "0.2"
hyper = "0.13"
ipnetwork = "0.16"
log = "0.4"
+regex = "1"
serde = "1"
serde_json = "1.0"
hyper-rustls = "0.20"
diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs
index 1394cfabbb..bc6a71a5ba 100644
--- a/mullvad-rpc/src/lib.rs
+++ b/mullvad-rpc/src/lib.rs
@@ -5,10 +5,11 @@ use futures01::future::Future as Future01;
use hyper::Method;
use mullvad_types::{
account::{AccountToken, VoucherSubmission},
- version::{AppVersion, AppVersionInfo},
+ version::AppVersion,
};
use std::{
collections::BTreeMap,
+ future::Future,
net::{IpAddr, Ipv4Addr},
path::Path,
};
@@ -278,12 +279,12 @@ pub struct AppVersionProxy {
handle: rest::MullvadRestHandle,
}
-#[derive(serde::Deserialize)]
-struct AppVersionResponse {
- supported: bool,
- latest: AppVersion,
- latest_stable: Option<AppVersion>,
- latest_beta: AppVersion,
+#[derive(serde::Deserialize, Debug)]
+pub struct AppVersionResponse {
+ pub supported: bool,
+ pub latest: AppVersion,
+ pub latest_stable: Option<AppVersion>,
+ pub latest_beta: AppVersion,
}
impl AppVersionProxy {
@@ -295,7 +296,7 @@ impl AppVersionProxy {
&self,
version: AppVersion,
platform: &str,
- ) -> impl Future01<Item = AppVersionInfo, Error = rest::Error> {
+ ) -> impl Future<Output = Result<AppVersionResponse, rest::Error>> {
let service = self.handle.service.clone();
let request = rest::send_request(
@@ -307,20 +308,7 @@ impl AppVersionProxy {
StatusCode::OK,
);
- let future = async move {
- let response: AppVersionResponse = rest::deserialize_body(request.await?).await?;
-
- let version_info = AppVersionInfo {
- supported: response.supported,
- latest: response.latest,
- latest_stable: response.latest_stable.unwrap_or_else(|| "".to_owned()),
- latest_beta: response.latest_beta,
- };
-
- Ok(version_info)
- };
-
- self.handle.service.compat_spawn(future)
+ async move { rest::deserialize_body(request.await?).await }
}
}
diff --git a/mullvad-types/src/version.rs b/mullvad-types/src/version.rs
index 1603c7752b..47f26f2de0 100644
--- a/mullvad-types/src/version.rs
+++ b/mullvad-types/src/version.rs
@@ -15,13 +15,15 @@ pub struct AppVersionInfo {
/// issues, so using it is no longer recommended.
/// The user should really upgrade when this is false.
pub supported: bool,
- /// Latest version
- pub latest: AppVersion,
/// Latest stable version
+ #[cfg_attr(target_os = "android", jnix(skip))]
pub latest_stable: AppVersion,
/// Equal to `latest_stable` when the newest release is a stable release. But will contain
/// beta versions when those are out for testing.
+ #[cfg_attr(target_os = "android", jnix(skip))]
pub latest_beta: AppVersion,
+ /// Whether should update to newer version
+ pub suggested_upgrade: Option<AppVersion>,
}
pub type AppVersion = String;