Skip to content

Commit e914beb

Browse files
Merge branch 'prevent-apt-and-dnf-from-being-confused-about-package-des-1412'
2 parents 3de3c63 + e2d67b8 commit e914beb

File tree

14 files changed

+143
-44
lines changed

14 files changed

+143
-44
lines changed

mullvad-version/src/lib.rs

+61
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,63 @@
1+
use std::fmt::Display;
2+
use std::str::FromStr;
3+
use std::sync::LazyLock;
4+
5+
use regex::Regex;
6+
17
/// The Mullvad VPN app product version
28
pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt"));
9+
10+
#[derive(Debug, Clone, PartialEq)]
11+
pub struct Version {
12+
pub year: String,
13+
pub incremental: String,
14+
pub beta: Option<String>,
15+
}
16+
17+
impl Version {
18+
pub fn parse(version: &str) -> Version {
19+
Version::from_str(version).unwrap()
20+
}
21+
}
22+
23+
impl Display for Version {
24+
/// Format Version as a string: year.incremental{-beta}
25+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26+
let Version {
27+
year,
28+
incremental,
29+
beta,
30+
} = &self;
31+
match beta {
32+
Some(beta) => write!(f, "{year}.{incremental}-{beta}"),
33+
None => write!(f, "{year}.{incremental}"),
34+
}
35+
}
36+
}
37+
38+
impl FromStr for Version {
39+
type Err = String;
40+
41+
fn from_str(version: &str) -> Result<Self, Self::Err> {
42+
const VERSION_REGEX: &str =
43+
r"^20([0-9]{2})\.([1-9][0-9]?)(-beta([1-9][0-9]?))?(-dev-[0-9a-f]+)?$";
44+
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(VERSION_REGEX).unwrap());
45+
46+
let captures = RE
47+
.captures(version)
48+
.ok_or_else(|| format!("Version does not match expected format: {version}"))?;
49+
let year = captures.get(1).expect("Missing year").as_str().to_owned();
50+
let incremental = captures
51+
.get(2)
52+
.ok_or("Missing incremental")?
53+
.as_str()
54+
.to_owned();
55+
let beta = captures.get(4).map(|m| m.as_str().to_owned());
56+
57+
Ok(Version {
58+
year,
59+
incremental,
60+
beta,
61+
})
62+
}
63+
}

mullvad-version/src/main.rs

+5-33
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
use regex::Regex;
1+
use mullvad_version::Version;
22
use std::{env, process::exit};
33

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

7-
const VERSION_REGEX: &str = r"^20([0-9]{2})\.([1-9][0-9]?)(-beta([1-9][0-9]?))?(-dev-[0-9a-f]+)?$";
8-
9-
const ANDROID_STABLE_VERSION_CODE_SUFFIX: &str = "99";
10-
117
fn main() {
128
let command = env::args().nth(1);
139
match command.as_deref() {
@@ -53,7 +49,9 @@ fn to_semver(version: &str) -> String {
5349
/// Version: 2021.34
5450
/// versionCode: 21340099
5551
fn to_android_version_code(version: &str) -> String {
56-
let version = parse_version(version);
52+
const ANDROID_STABLE_VERSION_CODE_SUFFIX: &str = "99";
53+
54+
let version = Version::parse(version);
5755
format!(
5856
"{}{:0>2}00{:0>2}",
5957
version.year,
@@ -67,7 +65,7 @@ fn to_android_version_code(version: &str) -> String {
6765
fn to_windows_h_format(version: &str) -> String {
6866
let Version {
6967
year, incremental, ..
70-
} = parse_version(version);
68+
} = Version::parse(version);
7169

7270
format!(
7371
"#define MAJOR_VERSION 20{year}
@@ -76,29 +74,3 @@ fn to_windows_h_format(version: &str) -> String {
7674
#define PRODUCT_VERSION \"{version}\""
7775
)
7876
}
79-
80-
struct Version {
81-
year: String,
82-
incremental: String,
83-
beta: Option<String>,
84-
}
85-
86-
fn parse_version(version: &str) -> Version {
87-
let re = Regex::new(VERSION_REGEX).unwrap();
88-
let captures = re
89-
.captures(version)
90-
.expect("Version does not match expected format");
91-
let year = captures.get(1).expect("Missing year").as_str().to_owned();
92-
let incremental = captures
93-
.get(2)
94-
.expect("Missing incremental")
95-
.as_str()
96-
.to_owned();
97-
let beta = captures.get(4).map(|m| m.as_str().to_owned());
98-
99-
Version {
100-
year,
101-
incremental,
102-
beta,
103-
}
104-
}

test/Cargo.lock

+8-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ shadowsocks-service = "1.20.3"
7575
windows-sys = "0.52.0"
7676
chrono = { version = "0.4.26", default-features = false }
7777
clap = { version = "4.2.7", features = ["cargo", "derive"] }
78-
once_cell = "1.16.0"
7978
bytes = "1.3.0"
8079
async-trait = "0.1.58"
8180
surge-ping = "0.8"

test/test-manager/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ thiserror = { workspace = true }
2222
bytes = { workspace = true }
2323
test_macro = { path = "./test_macro" }
2424
ipnetwork = "0.20"
25-
once_cell = { workspace = true }
2625
inventory = "0.3"
2726
data-encoding-macro = "0.1.12"
2827
itertools = "0.10.5"
@@ -57,6 +56,7 @@ mullvad-api = { path = "../../mullvad-api", features = ["api-override"] }
5756
mullvad-management-interface = { path = "../../mullvad-management-interface" }
5857
mullvad-relay-selector = { path = "../../mullvad-relay-selector" }
5958
mullvad-types = { path = "../../mullvad-types" }
59+
mullvad-version = { path = "../../mullvad-version" }
6060
talpid-types = { path = "../../talpid-types" }
6161

6262
ssh2 = "0.9.4"

test/test-manager/src/tests/config.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
use once_cell::sync::OnceCell;
1+
use std::sync::OnceLock;
22
use std::{ops::Deref, path::Path};
33
use test_rpc::meta::Os;
44

5+
pub static TEST_CONFIG: TestConfigContainer = TestConfigContainer::new();
6+
57
/// Default `mullvad_host`. This should match the production env.
68
pub const DEFAULT_MULLVAD_HOST: &str = "mullvad.net";
79
/// Bundled OpenVPN CA certificate use with the installed Mullvad app.
@@ -110,9 +112,13 @@ impl Default for BootstrapScript {
110112
}
111113

112114
#[derive(Debug, Clone)]
113-
pub struct TestConfigContainer(OnceCell<TestConfig>);
115+
pub struct TestConfigContainer(OnceLock<TestConfig>);
114116

115117
impl TestConfigContainer {
118+
const fn new() -> Self {
119+
TestConfigContainer(OnceLock::new())
120+
}
121+
116122
/// Initializes the constants.
117123
///
118124
/// # Panics
@@ -130,5 +136,3 @@ impl Deref for TestConfigContainer {
130136
self.0.get().unwrap()
131137
}
132138
}
133-
134-
pub static TEST_CONFIG: TestConfigContainer = TestConfigContainer(OnceCell::new());

test/test-manager/src/tests/install.rs

+14
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ pub async fn test_upgrade_app(
107107
if rpc.mullvad_daemon_get_status().await? != ServiceStatus::Running {
108108
bail!(Error::DaemonNotRunning);
109109
}
110+
111+
// Verify that the correct version was installed
112+
let running_daemon_version = rpc.mullvad_daemon_version().await?;
113+
let running_daemon_version =
114+
mullvad_version::Version::parse(&running_daemon_version).to_string();
115+
ensure!(
116+
&TEST_CONFIG
117+
.app_package_filename
118+
.contains(&running_daemon_version),
119+
"Incorrect deamon version installed. Expected {expected} but {actual} is installed",
120+
expected = TEST_CONFIG.app_package_filename.clone(),
121+
actual = running_daemon_version
122+
);
123+
110124
// Check if any traffic was observed
111125
//
112126
let guest_ip = pinger.guest_ip;

test/test-rpc/src/client.rs

+10
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ impl ServiceClient {
141141
.map_err(Error::Tarpc)
142142
}
143143

144+
/// Return the version string as reported by `mullvad --version`.
145+
///
146+
/// TODO: Replace with nicer version type.
147+
pub async fn mullvad_daemon_version(&self) -> Result<String, Error> {
148+
self.client
149+
.mullvad_version(tarpc::context::current())
150+
.await
151+
.map_err(Error::Tarpc)?
152+
}
153+
144154
/// Returns all Mullvad app files, directories, and other data found on the system.
145155
pub async fn find_mullvad_app_traces(&self) -> Result<Vec<AppTrace>, Error> {
146156
self.client

test/test-rpc/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ mod service {
156156
/// Return status of the system service.
157157
async fn mullvad_daemon_get_status() -> mullvad_daemon::ServiceStatus;
158158

159+
/// Return version number of installed daemon.
160+
async fn mullvad_version() -> Result<String, Error>;
161+
159162
/// Returns all Mullvad app files, directories, and other data found on the system.
160163
async fn find_mullvad_app_traces() -> Result<Vec<AppTrace>, Error>;
161164

test/test-runner/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ tokio = { workspace = true }
1818
tokio-serial = { workspace = true }
1919
thiserror = { workspace = true }
2020
log = { workspace = true }
21-
once_cell = { workspace = true }
2221
parity-tokio-ipc = "0.9"
2322
bytes = { workspace = true }
2423
serde = { workspace = true }

test/test-runner/src/app.rs

+19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@ use std::path::{Path, PathBuf};
33

44
use test_rpc::{AppTrace, Error};
55

6+
/// Get the installed app version string
7+
pub async fn version() -> Result<String, Error> {
8+
let version = tokio::process::Command::new("mullvad")
9+
.arg("--version")
10+
.output()
11+
.await
12+
.map_err(|e| Error::Service(e.to_string()))?;
13+
let version = String::from_utf8(version.stdout).map_err(|err| Error::Other(err.to_string()))?;
14+
// HACK: The output from `mullvad --version` includes the `mullvad-cli` binary name followed by
15+
// the version string. Simply remove the leading noise and get at the version string.
16+
let Some(version) = version.split_whitespace().nth(1) else {
17+
return Err(Error::Other(
18+
"Could not parse version number from `mullvad-cli --version`".to_string(),
19+
));
20+
};
21+
let version = version.to_string();
22+
Ok(version)
23+
}
24+
625
#[cfg(target_os = "windows")]
726
pub fn find_traces() -> Result<Vec<AppTrace>, Error> {
827
// TODO: Check GUI data

test/test-runner/src/main.rs

+5
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ impl Service for TestServer {
136136
get_pipe_status()
137137
}
138138

139+
/// Get the installed app version
140+
async fn mullvad_version(self, _: context::Context) -> Result<String, test_rpc::Error> {
141+
app::version().await
142+
}
143+
139144
async fn find_mullvad_app_traces(
140145
self,
141146
_: context::Context,

test/test-runner/src/net.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,10 @@ pub fn get_interface_mac(_interface: &str) -> Result<Option<[u8; 6]>, test_rpc::
251251

252252
#[cfg(target_os = "windows")]
253253
pub fn get_default_interface() -> &'static str {
254-
use once_cell::sync::OnceCell;
254+
use std::sync::OnceLock;
255255
use talpid_platform_metadata::WindowsVersion;
256256

257-
static WINDOWS_VERSION: OnceCell<WindowsVersion> = OnceCell::new();
257+
static WINDOWS_VERSION: OnceLock<WindowsVersion> = OnceLock::new();
258258
let version = WINDOWS_VERSION
259259
.get_or_init(|| WindowsVersion::new().expect("failed to obtain Windows version"));
260260

test/test-runner/src/package.rs

+7
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ fn apt_command() -> Command {
173173
// instead.
174174
cmd.args(["-o", "DPkg::Lock::Timeout=60"]);
175175
cmd.arg("-qy");
176+
// `apt` may consider installing a development build to be a downgrade from the baseline if the
177+
// major version is identical, in which case the ordering is incorrectly based on the git hash
178+
// suffix.
179+
//
180+
// Note that this is only sound if we take precaution to check the installed version after
181+
// running this command.
182+
cmd.arg("--allow-downgrades");
176183

177184
cmd.env("DEBIAN_FRONTEND", "noninteractive");
178185

0 commit comments

Comments
 (0)