Skip to content

Commit

Permalink
Check that the daemon version is correct post-upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkusPettersson98 committed Nov 5, 2024
1 parent 72d2dfb commit 53c17b1
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 33 deletions.
61 changes: 61 additions & 0 deletions mullvad-version/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,63 @@
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;

use regex::Regex;

/// The Mullvad VPN app product version
pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt"));

#[derive(Debug, Clone, PartialEq)]
pub struct Version {
pub year: String,
pub incremental: String,
pub beta: Option<String>,
}

impl Version {
pub fn parse(version: &str) -> Version {
Version::from_str(version).unwrap()
}
}

impl Display for Version {
/// Format Version as a string: year.incremental{-beta}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Version {
year,
incremental,
beta,
} = &self;
match beta {
Some(beta) => write!(f, "{year}.{incremental}-{beta}"),
None => write!(f, "{year}.{incremental}"),
}
}
}

impl FromStr for Version {
type Err = String;

fn from_str(version: &str) -> Result<Self, Self::Err> {
const VERSION_REGEX: &str =
r"^20([0-9]{2})\.([1-9][0-9]?)(-beta([1-9][0-9]?))?(-dev-[0-9a-f]+)?$";
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(VERSION_REGEX).unwrap());

let captures = RE
.captures(version)
.ok_or_else(|| format!("Version does not match expected format: {version}"))?;
let year = captures.get(1).expect("Missing year").as_str().to_owned();
let incremental = captures
.get(2)
.ok_or("Missing incremental")?
.as_str()
.to_owned();
let beta = captures.get(4).map(|m| m.as_str().to_owned());

Ok(Version {
year,
incremental,
beta,
})
}
}
38 changes: 5 additions & 33 deletions mullvad-version/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use regex::Regex;
use mullvad_version::Version;
use std::{env, process::exit};

const ANDROID_VERSION: &str =
include_str!(concat!(env!("OUT_DIR"), "/android-product-version.txt"));

const VERSION_REGEX: &str = r"^20([0-9]{2})\.([1-9][0-9]?)(-beta([1-9][0-9]?))?(-dev-[0-9a-f]+)?$";

const ANDROID_STABLE_VERSION_CODE_SUFFIX: &str = "99";

fn main() {
let command = env::args().nth(1);
match command.as_deref() {
Expand Down Expand Up @@ -53,7 +49,9 @@ fn to_semver(version: &str) -> String {
/// Version: 2021.34
/// versionCode: 21340099
fn to_android_version_code(version: &str) -> String {
let version = parse_version(version);
const ANDROID_STABLE_VERSION_CODE_SUFFIX: &str = "99";

let version = Version::parse(version);
format!(
"{}{:0>2}00{:0>2}",
version.year,
Expand All @@ -67,7 +65,7 @@ fn to_android_version_code(version: &str) -> String {
fn to_windows_h_format(version: &str) -> String {
let Version {
year, incremental, ..
} = parse_version(version);
} = Version::parse(version);

format!(
"#define MAJOR_VERSION 20{year}
Expand All @@ -76,29 +74,3 @@ fn to_windows_h_format(version: &str) -> String {
#define PRODUCT_VERSION \"{version}\""
)
}

struct Version {
year: String,
incremental: String,
beta: Option<String>,
}

fn parse_version(version: &str) -> Version {
let re = Regex::new(VERSION_REGEX).unwrap();
let captures = re
.captures(version)
.expect("Version does not match expected format");
let year = captures.get(1).expect("Missing year").as_str().to_owned();
let incremental = captures
.get(2)
.expect("Missing incremental")
.as_str()
.to_owned();
let beta = captures.get(4).map(|m| m.as_str().to_owned());

Version {
year,
incremental,
beta,
}
}
8 changes: 8 additions & 0 deletions test/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/test-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mullvad-api = { path = "../../mullvad-api", features = ["api-override"] }
mullvad-management-interface = { path = "../../mullvad-management-interface" }
mullvad-relay-selector = { path = "../../mullvad-relay-selector" }
mullvad-types = { path = "../../mullvad-types" }
mullvad-version = { path = "../../mullvad-version" }
talpid-types = { path = "../../talpid-types" }

ssh2 = "0.9.4"
Expand Down
15 changes: 15 additions & 0 deletions test/test-manager/src/tests/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ pub async fn test_upgrade_app(
if rpc.mullvad_daemon_get_status().await? != ServiceStatus::Running {
bail!(Error::DaemonNotRunning);
}

// Verify that the correct version was installed
let running_daemon_version = rpc.mullvad_daemon_version().await?;
let running_daemon_version =
mullvad_version::Version::parse(&running_daemon_version).to_string();
ensure!(
&TEST_CONFIG
.app_package_filename
.contains(&running_daemon_version),
Error::DaemonVersion {
expected: TEST_CONFIG.app_package_filename.clone(),
actual: running_daemon_version,
}
);

// Check if any traffic was observed
//
let guest_ip = pinger.guest_ip;
Expand Down
3 changes: 3 additions & 0 deletions test/test-manager/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub enum Error {
#[error("Daemon unexpectedly not running")]
DaemonNotRunning,

#[error("Incorrect deamon version installed. Expected {expected} but {actual} is installed")]
DaemonVersion { expected: String, actual: String },

#[error("The daemon returned an error: {0}")]
Daemon(String),

Expand Down
10 changes: 10 additions & 0 deletions test/test-rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ impl ServiceClient {
.map_err(Error::Tarpc)
}

/// Return the version string as reported by `mullvad --version`.
///
/// TODO: Replace with nicer version type.
pub async fn mullvad_daemon_version(&self) -> Result<String, Error> {
self.client
.mullvad_version(tarpc::context::current())
.await
.map_err(Error::Tarpc)?
}

/// Returns all Mullvad app files, directories, and other data found on the system.
pub async fn find_mullvad_app_traces(&self) -> Result<Vec<AppTrace>, Error> {
self.client
Expand Down
3 changes: 3 additions & 0 deletions test/test-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ mod service {
/// Return status of the system service.
async fn mullvad_daemon_get_status() -> mullvad_daemon::ServiceStatus;

/// Return version number of installed daemon.
async fn mullvad_version() -> Result<String, Error>;

/// Returns all Mullvad app files, directories, and other data found on the system.
async fn find_mullvad_app_traces() -> Result<Vec<AppTrace>, Error>;

Expand Down
19 changes: 19 additions & 0 deletions test/test-runner/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ use std::path::{Path, PathBuf};

use test_rpc::{AppTrace, Error};

/// Get the installed app version string
pub async fn version() -> Result<String, Error> {
let version = tokio::process::Command::new("mullvad")
.arg("--version")
.output()
.await
.map_err(|e| Error::Service(e.to_string()))?;
let version = String::from_utf8(version.stdout).map_err(|err| Error::Other(err.to_string()))?;
// HACK: The output from `mullvad --version` includes the `mullvad-cli` binary name followed by
// the version string. Simply remove the leading noise and get at the version string.
let Some(version) = version.split_whitespace().nth(1) else {
return Err(Error::Other(
"Could not parse version number from `mullvad-cli --version`".to_string(),
));
};
let version = version.to_string();
Ok(version)
}

#[cfg(target_os = "windows")]
pub fn find_traces() -> Result<Vec<AppTrace>, Error> {
// TODO: Check GUI data
Expand Down
5 changes: 5 additions & 0 deletions test/test-runner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ impl Service for TestServer {
get_pipe_status()
}

/// Get the installed app version
async fn mullvad_version(self, _: context::Context) -> Result<String, test_rpc::Error> {
app::version().await
}

async fn find_mullvad_app_traces(
self,
_: context::Context,
Expand Down

0 comments on commit 53c17b1

Please sign in to comment.