Skip to content

Commit 36e9c85

Browse files
committed
Merge branch 'use-new-version-endpoint'
2 parents 2094a39 + 6b8d2c7 commit 36e9c85

File tree

8 files changed

+225
-69
lines changed

8 files changed

+225
-69
lines changed

Cargo.lock

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

mullvad-api/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ workspace = true
1515
api-override = []
1616

1717
[dependencies]
18+
anyhow = { workspace = true }
1819
async-trait = "0.1"
1920
libc = "0.2"
2021
chrono = { workspace = true }
@@ -46,6 +47,7 @@ tokio-rustls = { version = "0.26.0", features = [
4647
tokio-socks = "0.5.1"
4748
rustls-pemfile = "2.1.3"
4849
uuid = { version = "1.4.1", features = ["v4"] }
50+
vec1 = { workspace = true }
4951

5052
mullvad-encrypted-dns-proxy = { path = "../mullvad-encrypted-dns-proxy" }
5153
mullvad-fs = { path = "../mullvad-fs" }
@@ -55,6 +57,9 @@ talpid-time = { path = "../talpid-time" }
5557

5658
shadowsocks = { workspace = true, features = ["stream-cipher"] }
5759

60+
[target.'cfg(not(target_os = "ios"))'.dependencies]
61+
mullvad-update = { path = "../mullvad-update", features = ["client"] }
62+
5863
[dev-dependencies]
5964
talpid-time = { path = "../talpid-time", features = ["test"] }
6065
tokio = { workspace = true, features = ["test-util", "time"] }

mullvad-api/src/lib.rs

+3-43
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@ use async_trait::async_trait;
33
#[cfg(target_os = "android")]
44
use futures::channel::mpsc;
55
use hyper::body::Incoming;
6+
use mullvad_types::account::{AccountData, AccountNumber, VoucherSubmission};
67
#[cfg(target_os = "android")]
78
use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
8-
use mullvad_types::{
9-
account::{AccountData, AccountNumber, VoucherSubmission},
10-
version::AppVersion,
11-
};
129
use proxy::{ApiConnectionMode, ConnectionModeProvider};
1310
use std::{
1411
collections::BTreeMap,
@@ -23,6 +20,8 @@ use talpid_types::ErrorExt;
2320
pub mod availability;
2421
use availability::ApiAvailability;
2522
pub mod rest;
23+
#[cfg(not(target_os = "ios"))]
24+
pub mod version;
2625

2726
mod abortable_stream;
2827
pub mod access_mode;
@@ -698,45 +697,6 @@ impl ProblemReportProxy {
698697
}
699698
}
700699

701-
#[derive(Clone)]
702-
pub struct AppVersionProxy {
703-
handle: rest::MullvadRestHandle,
704-
}
705-
706-
#[derive(serde::Deserialize, Debug)]
707-
pub struct AppVersionResponse {
708-
pub supported: bool,
709-
pub latest: AppVersion,
710-
pub latest_stable: Option<AppVersion>,
711-
pub latest_beta: AppVersion,
712-
}
713-
714-
impl AppVersionProxy {
715-
pub fn new(handle: rest::MullvadRestHandle) -> Self {
716-
Self { handle }
717-
}
718-
719-
pub fn version_check(
720-
&self,
721-
app_version: AppVersion,
722-
platform: &str,
723-
platform_version: String,
724-
) -> impl Future<Output = Result<AppVersionResponse, rest::Error>> + use<> {
725-
let service = self.handle.service.clone();
726-
727-
let path = format!("{APP_URL_PREFIX}/releases/{platform}/{app_version}");
728-
let request = self.handle.factory.get(&path);
729-
730-
async move {
731-
let request = request?
732-
.expected_status(&[StatusCode::OK])
733-
.header("M-Platform-Version", &platform_version)?;
734-
let response = service.request(request).await?;
735-
response.deserialize().await
736-
}
737-
}
738-
}
739-
740700
#[derive(Clone)]
741701
pub struct ApiProxy {
742702
handle: rest::MullvadRestHandle,

mullvad-api/src/rest.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use futures::{
1313
};
1414
use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full};
1515
use hyper::{
16-
body::{Body, Bytes, Incoming},
16+
body::{Body, Buf, Bytes, Incoming},
1717
header::{self, HeaderValue},
1818
Method, Uri,
1919
};
@@ -73,6 +73,14 @@ pub enum Error {
7373

7474
#[error("Set account number on factory with no access token store")]
7575
NoAccessTokenStore,
76+
77+
/// Failed to obtain versions
78+
#[error("Failed to obtain versions")]
79+
FetchVersions(#[from] Arc<anyhow::Error>),
80+
81+
/// Body exceeded size limit
82+
#[error("Body exceeded size limit")]
83+
BodyTooLarge,
7684
}
7785

7886
impl From<Infallible> for Error {
@@ -493,7 +501,7 @@ pub struct Response<B> {
493501
response: hyper::Response<B>,
494502
}
495503

496-
impl<B: Body> Response<B>
504+
impl<B: Body + Unpin> Response<B>
497505
where
498506
Error: From<<B as Body>::Error>,
499507
{
@@ -516,6 +524,20 @@ where
516524
pub async fn body(self) -> Result<Vec<u8>> {
517525
Ok(BodyExt::collect(self.response).await?.to_bytes().to_vec())
518526
}
527+
528+
pub async fn body_with_max_size(self, size_limit: usize) -> Result<Vec<u8>> {
529+
let mut data: Vec<u8> = vec![];
530+
let mut stream = self.response.into_data_stream();
531+
532+
while let Some(chunk) = stream.next().await {
533+
data.extend(chunk?.chunk());
534+
if data.len() > size_limit {
535+
return Err(Error::BodyTooLarge);
536+
}
537+
}
538+
539+
Ok(data)
540+
}
519541
}
520542

521543
#[derive(serde::Deserialize)]

mullvad-api/src/version.rs

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use std::future::Future;
2+
use std::sync::Arc;
3+
4+
use http::StatusCode;
5+
use mullvad_types::version::AppVersion;
6+
use mullvad_update::version::{VersionInfo, VersionParameters};
7+
use vec1::vec1;
8+
9+
use super::rest;
10+
use super::APP_URL_PREFIX;
11+
12+
#[derive(Clone)]
13+
pub struct AppVersionProxy {
14+
handle: super::rest::MullvadRestHandle,
15+
}
16+
17+
#[derive(serde::Deserialize, Debug)]
18+
pub struct AppVersionResponse {
19+
pub supported: bool,
20+
pub latest: AppVersion,
21+
pub latest_stable: Option<AppVersion>,
22+
pub latest_beta: AppVersion,
23+
}
24+
25+
impl AppVersionProxy {
26+
/// Public key to use for `version_check_2` response
27+
const VERSION_PROVIDER_PUBKEY: &str = include_str!("../../mullvad-update/stagemole-pubkey");
28+
29+
/// Maximum size of `version_check_2` response
30+
const SIZE_LIMIT: usize = 1024 * 1024;
31+
32+
pub fn new(handle: rest::MullvadRestHandle) -> Self {
33+
Self { handle }
34+
}
35+
36+
pub fn version_check(
37+
&self,
38+
app_version: AppVersion,
39+
platform: &str,
40+
platform_version: String,
41+
) -> impl Future<Output = Result<AppVersionResponse, rest::Error>> + use<> {
42+
let service = self.handle.service.clone();
43+
44+
let path = format!("{APP_URL_PREFIX}/releases/{platform}/{app_version}");
45+
let request = self.handle.factory.get(&path);
46+
47+
async move {
48+
let request = request?
49+
.expected_status(&[StatusCode::OK])
50+
.header("M-Platform-Version", &platform_version)?;
51+
let response = service.request(request).await?;
52+
response.deserialize().await
53+
}
54+
}
55+
56+
/// Get versions from `/app/releases/<platform>.json`
57+
pub fn version_check_2(
58+
&self,
59+
platform: &str,
60+
architecture: mullvad_update::format::Architecture,
61+
rollout: f32,
62+
lowest_metadata_version: usize,
63+
) -> impl Future<Output = Result<VersionInfo, rest::Error>> + use<> {
64+
let service = self.handle.service.clone();
65+
let path = format!("app/releases/{platform}.json");
66+
let request = self.handle.factory.get(&path);
67+
68+
let verifying_key =
69+
mullvad_update::format::key::VerifyingKey::from_hex(Self::VERSION_PROVIDER_PUBKEY)
70+
.expect("valid key");
71+
let verifying_keys = vec1![verifying_key];
72+
73+
async move {
74+
let request = request?.expected_status(&[StatusCode::OK]);
75+
let response = service.request(request).await?;
76+
let bytes = response.body_with_max_size(Self::SIZE_LIMIT).await?;
77+
78+
let response = mullvad_update::format::SignedResponse::deserialize_and_verify(
79+
&verifying_keys,
80+
&bytes,
81+
lowest_metadata_version,
82+
)
83+
.map_err(|err| rest::Error::FetchVersions(Arc::new(err)))?;
84+
85+
let params = VersionParameters {
86+
architecture,
87+
rollout,
88+
lowest_metadata_version,
89+
};
90+
91+
VersionInfo::try_from_response(&params, response.signed)
92+
.map_err(Arc::new)
93+
.map_err(rest::Error::FetchVersions)
94+
}
95+
}
96+
}

mullvad-daemon/src/version_check.rs

+16-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use futures::{
44
future::{BoxFuture, FusedFuture},
55
FutureExt, SinkExt, StreamExt, TryFutureExt,
66
};
7-
use mullvad_api::{availability::ApiAvailability, rest::MullvadRestHandle, AppVersionProxy};
7+
use mullvad_api::{
8+
availability::ApiAvailability,
9+
rest::MullvadRestHandle,
10+
version::{AppVersionProxy, AppVersionResponse},
11+
};
812
use mullvad_types::version::AppVersionInfo;
913
use mullvad_version::Version;
1014
use serde::{Deserialize, Serialize};
@@ -194,11 +198,8 @@ impl VersionUpdaterInner {
194198
self.last_app_version_info.as_ref().map(|(info, _)| info)
195199
}
196200

197-
/// Convert a [mullvad_api::AppVersionResponse] to an [AppVersionInfo].
198-
fn response_to_version_info(
199-
&self,
200-
response: mullvad_api::AppVersionResponse,
201-
) -> AppVersionInfo {
201+
/// Convert a [AppVersionResponse] to an [AppVersionInfo].
202+
fn response_to_version_info(&self, response: AppVersionResponse) -> AppVersionInfo {
202203
let suggested_upgrade = suggested_upgrade(
203204
&APP_VERSION,
204205
&response.latest_stable,
@@ -295,12 +296,9 @@ impl VersionUpdaterInner {
295296
mut self,
296297
mut rx: mpsc::Receiver<VersionUpdaterCommand>,
297298
update: impl Fn(AppVersionInfo) -> BoxFuture<'static, Result<(), Error>>,
298-
do_version_check: impl Fn()
299-
-> BoxFuture<'static, Result<mullvad_api::AppVersionResponse, Error>>,
300-
do_version_check_in_background: impl Fn() -> BoxFuture<
301-
'static,
302-
Result<mullvad_api::AppVersionResponse, Error>,
303-
>,
299+
do_version_check: impl Fn() -> BoxFuture<'static, Result<AppVersionResponse, Error>>,
300+
do_version_check_in_background: impl Fn()
301+
-> BoxFuture<'static, Result<AppVersionResponse, Error>>,
304302
) {
305303
let mut version_is_stale = self.wait_until_version_is_stale();
306304
let mut version_check = futures::future::Fuse::terminated();
@@ -422,9 +420,7 @@ struct ApiContext {
422420
}
423421

424422
/// Immediately query the API for the latest [AppVersionInfo].
425-
fn do_version_check(
426-
api: ApiContext,
427-
) -> BoxFuture<'static, Result<mullvad_api::AppVersionResponse, Error>> {
423+
fn do_version_check(api: ApiContext) -> BoxFuture<'static, Result<AppVersionResponse, Error>> {
428424
let download_future_factory = move || {
429425
api.version_proxy
430426
.version_check(
@@ -459,7 +455,7 @@ fn do_version_check(
459455
/// On any error, this function retries repeatedly every [UPDATE_INTERVAL_ERROR] until success.
460456
fn do_version_check_in_background(
461457
api: ApiContext,
462-
) -> BoxFuture<'static, Result<mullvad_api::AppVersionResponse, Error>> {
458+
) -> BoxFuture<'static, Result<AppVersionResponse, Error>> {
463459
let download_future_factory = move || {
464460
let when_available = api.api_handle.wait_background();
465461
let request = api.version_proxy.version_check(
@@ -721,21 +717,20 @@ mod test {
721717
}
722718
}
723719

724-
fn fake_version_check() -> BoxFuture<'static, Result<mullvad_api::AppVersionResponse, Error>> {
720+
fn fake_version_check() -> BoxFuture<'static, Result<AppVersionResponse, Error>> {
725721
Box::pin(async { Ok(fake_version_response()) })
726722
}
727723

728-
fn fake_version_check_err() -> BoxFuture<'static, Result<mullvad_api::AppVersionResponse, Error>>
729-
{
724+
fn fake_version_check_err() -> BoxFuture<'static, Result<AppVersionResponse, Error>> {
730725
Box::pin(retry_future(
731726
|| async { Err(Error::Download(mullvad_api::rest::Error::TimeoutError)) },
732727
|_| true,
733728
std::iter::repeat(UPDATE_INTERVAL_ERROR),
734729
))
735730
}
736731

737-
fn fake_version_response() -> mullvad_api::AppVersionResponse {
738-
mullvad_api::AppVersionResponse {
732+
fn fake_version_response() -> AppVersionResponse {
733+
AppVersionResponse {
739734
supported: true,
740735
latest: "2024.1".to_owned(),
741736
latest_stable: None,

mullvad-ios/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ talpid-future = { path = "../talpid-future" }
2828
talpid-types = { path = "../talpid-types" }
2929
talpid-tunnel-config-client = { path = "../talpid-tunnel-config-client" }
3030
mullvad-encrypted-dns-proxy = { path = "../mullvad-encrypted-dns-proxy" }
31-
mullvad-api = { path = "../mullvad-api" }
31+
mullvad-api = { path = "../mullvad-api", default-features = false }
3232
serde_json = { workspace = true }
3333

3434
shadowsocks-service = { workspace = true, features = [

0 commit comments

Comments
 (0)