Skip to content

Commit a6f24fb

Browse files
committed
Implement call for getting relays on Rust side
1 parent 6f115d4 commit a6f24fb

File tree

4 files changed

+139
-31
lines changed

4 files changed

+139
-31
lines changed

ios/MullvadRustRuntime/include/mullvad_rust_runtime.h

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ typedef struct EncryptedDnsProxyState EncryptedDnsProxyState;
2424

2525
typedef struct ExchangeCancelToken ExchangeCancelToken;
2626

27+
typedef struct Option______u8 Option______u8;
28+
2729
typedef struct RequestCancelHandle RequestCancelHandle;
2830

2931
typedef struct RetryStrategy RetryStrategy;
@@ -43,6 +45,7 @@ typedef struct SwiftRetryStrategy {
4345
typedef struct SwiftMullvadApiResponse {
4446
uint8_t *body;
4547
uintptr_t body_size;
48+
uint8_t *etag;
4649
uint16_t status_code;
4750
uint8_t *error_description;
4851
uint8_t *server_response_code;
@@ -113,6 +116,11 @@ struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_co
113116
void *completion_cookie,
114117
struct SwiftRetryStrategy retry_strategy);
115118

119+
struct SwiftCancelHandle mullvad_api_get_relays(struct SwiftApiContext api_context,
120+
void *completion_cookie,
121+
struct SwiftRetryStrategy retry_strategy,
122+
struct Option______u8 etag);
123+
116124
/**
117125
* Called by the Swift side to signal that a Mullvad API call should be cancelled.
118126
* After this call, the cancel token is no longer valid.

mullvad-api/src/relay_list.rs

+41-28
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::rest;
44

5-
use hyper::{header, StatusCode};
5+
use hyper::{body::Incoming, header, Error, StatusCode};
66
use mullvad_types::{location, relay_list};
77
use talpid_types::net::wireguard;
88

@@ -29,44 +29,57 @@ impl RelayListProxy {
2929
}
3030

3131
/// Fetch the relay list
32-
pub fn relay_list(
32+
pub async fn relay_list(
3333
&self,
3434
etag: Option<String>,
35-
) -> impl Future<Output = Result<Option<relay_list::RelayList>, rest::Error>> + use<> {
36-
let service = self.handle.service.clone();
37-
let request = self.handle.factory.get("app/v1/relays");
35+
) -> Result<Option<relay_list::RelayList>, rest::Error> {
36+
let response = self.relay_list_response(etag.clone()).await.map_err(rest::Error::from)?;
3837

39-
async move {
40-
let mut request = request?
41-
.timeout(RELAY_LIST_TIMEOUT)
42-
.expected_status(&[StatusCode::NOT_MODIFIED, StatusCode::OK]);
38+
if etag.is_some() && response.status() == StatusCode::NOT_MODIFIED {
39+
return Ok(None);
40+
}
4341

44-
if let Some(ref tag) = etag {
45-
request = request.header(header::IF_NONE_MATCH, tag)?;
46-
}
42+
let etag = Self::extract_etag(&response);
4743

48-
let response = service.request(request).await?;
49-
if etag.is_some() && response.status() == StatusCode::NOT_MODIFIED {
50-
return Ok(None);
51-
}
44+
let relay_list: ServerRelayList = response.deserialize().await?;
45+
Ok(Some(relay_list.into_relay_list(etag)))
46+
}
5247

53-
let etag = response
54-
.headers()
55-
.get(header::ETAG)
56-
.and_then(|tag| match tag.to_str() {
57-
Ok(tag) => Some(tag.to_string()),
58-
Err(_) => {
59-
log::error!("Ignoring invalid tag from server: {:?}", tag.as_bytes());
60-
None
61-
}
62-
});
48+
pub async fn relay_list_response(
49+
&self,
50+
etag: Option<String>,
51+
) -> Result<rest::Response<Incoming>, rest::Error> {
52+
let service = self.handle.service.clone();
53+
let request = self.handle.factory.get("app/v1/relays");
6354

64-
let relay_list: ServerRelayList = response.deserialize().await?;
65-
Ok(Some(relay_list.into_relay_list(etag)))
55+
let mut request = request?
56+
.timeout(RELAY_LIST_TIMEOUT)
57+
.expected_status(&[StatusCode::NOT_MODIFIED, StatusCode::OK]);
58+
59+
if let Some(ref tag) = etag {
60+
request = request.header(header::IF_NONE_MATCH, tag)?;
6661
}
62+
63+
let response = service.request(request).await?;
64+
65+
Ok(response)
66+
}
67+
68+
pub fn extract_etag(response: &rest::Response<Incoming>) -> Option<String> {
69+
response
70+
.headers()
71+
.get(header::ETAG)
72+
.and_then(|tag| match tag.to_str() {
73+
Ok(tag) => Some(tag.to_string()),
74+
Err(_) => {
75+
log::error!("Ignoring invalid tag from server: {:?}", tag.as_bytes());
76+
None
77+
}
78+
})
6779
}
6880
}
6981

82+
7083
#[derive(Debug, serde::Deserialize)]
7184
struct ServerRelayList {
7285
locations: BTreeMap<String, Location>,

mullvad-ios/src/api_client/api.rs

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::{ffi::CStr, ptr};
2+
13
use mullvad_api::{
24
rest::{self, MullvadRestHandle},
3-
ApiProxy,
5+
ApiProxy, RelayListProxy,
46
};
57
use talpid_future::retry::retry_future;
68

@@ -69,3 +71,61 @@ async fn mullvad_api_get_addresses_inner(
6971

7072
SwiftMullvadApiResponse::with_body(response).await
7173
}
74+
75+
#[no_mangle]
76+
pub unsafe extern "C" fn mullvad_api_get_relays(
77+
api_context: SwiftApiContext,
78+
completion_cookie: *mut libc::c_void,
79+
retry_strategy: SwiftRetryStrategy,
80+
etag: Option<*const u8>,
81+
) -> SwiftCancelHandle {
82+
let completion_handler = SwiftCompletionHandler::new(CompletionCookie(completion_cookie));
83+
84+
let Ok(tokio_handle) = crate::mullvad_ios_runtime() else {
85+
completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime());
86+
return SwiftCancelHandle::empty();
87+
};
88+
89+
let api_context = api_context.into_rust_context();
90+
let retry_strategy = unsafe { retry_strategy.into_rust() };
91+
92+
let etag = match etag {
93+
Some(etag) => {
94+
let unwrapped_tag = unsafe { CStr::from_ptr(etag.cast()) }.to_str().unwrap();
95+
Some(String::from(unwrapped_tag))
96+
},
97+
None => None,
98+
};
99+
100+
let completion = completion_handler.clone();
101+
let task = tokio_handle.clone().spawn(async move {
102+
match mullvad_api_get_relays_inner(api_context.rest_handle(), retry_strategy, etag).await {
103+
Ok(response) => completion.finish(response),
104+
Err(err) => {
105+
log::error!("{err:?}");
106+
completion.finish(SwiftMullvadApiResponse::rest_error(err));
107+
}
108+
}
109+
});
110+
111+
RequestCancelHandle::new(task, completion_handler.clone()).into_swift()
112+
}
113+
114+
async fn mullvad_api_get_relays_inner(
115+
rest_client: MullvadRestHandle,
116+
retry_strategy: RetryStrategy,
117+
etag: Option<String>,
118+
) -> Result<SwiftMullvadApiResponse, rest::Error> {
119+
let api = RelayListProxy::new(rest_client);
120+
121+
let future_factory = || api.relay_list_response(etag.clone());
122+
123+
let should_retry = |result: &Result<_, rest::Error>| match result {
124+
Err(err) => err.is_network_error(),
125+
Ok(_) => false,
126+
};
127+
128+
let response = retry_future(future_factory, should_retry, retry_strategy.delays()).await?;
129+
130+
SwiftMullvadApiResponse::with_body(response).await
131+
}

mullvad-ios/src/api_client/response.rs

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,47 @@
1-
use std::{ffi::CString, ptr::null_mut};
1+
use std::{
2+
ffi::CString,
3+
ptr::{self, null_mut},
4+
};
25

3-
use mullvad_api::rest::{self, Response};
6+
use mullvad_api::{
7+
rest::{self, Response},
8+
RelayListProxy,
9+
};
410

511
#[repr(C)]
612
pub struct SwiftMullvadApiResponse {
713
body: *mut u8,
814
body_size: usize,
15+
etag: *mut u8,
916
status_code: u16,
1017
error_description: *mut u8,
1118
server_response_code: *mut u8,
1219
success: bool,
1320
}
21+
1422
impl SwiftMullvadApiResponse {
1523
pub async fn with_body(response: Response<hyper::body::Incoming>) -> Result<Self, rest::Error> {
24+
let maybe_etag = RelayListProxy::extract_etag(&response);
25+
1626
let status_code: u16 = response.status().into();
1727
let body: Vec<u8> = response.body().await?;
1828

1929
let body_size = body.len();
2030
let body = body.into_boxed_slice();
2131

32+
let etag = match maybe_etag {
33+
Some(etag) => {
34+
let header_value =
35+
CString::new(etag).map_err(|_| rest::Error::InvalidHeaderError)?;
36+
header_value.into_raw().cast()
37+
},
38+
None => ptr::null_mut(),
39+
};
40+
2241
Ok(Self {
2342
body: Box::<[u8]>::into_raw(body).cast(),
2443
body_size,
44+
etag,
2545
status_code,
2646
error_description: null_mut(),
2747
server_response_code: null_mut(),
@@ -51,6 +71,7 @@ impl SwiftMullvadApiResponse {
5171
Self {
5272
body: null_mut(),
5373
body_size: 0,
74+
etag: null_mut(),
5475
status_code,
5576
error_description,
5677
server_response_code,
@@ -64,6 +85,7 @@ impl SwiftMullvadApiResponse {
6485
error_description: c"Request was cancelled".to_owned().into_raw().cast(),
6586
body: null_mut(),
6687
body_size: 0,
88+
etag: null_mut(),
6789
status_code: 0,
6890
server_response_code: null_mut(),
6991
}
@@ -75,6 +97,7 @@ impl SwiftMullvadApiResponse {
7597
error_description: c"Failed to get Tokio runtime".to_owned().into_raw().cast(),
7698
body: null_mut(),
7799
body_size: 0,
100+
etag: null_mut(),
78101
status_code: 0,
79102
server_response_code: null_mut(),
80103
}
@@ -94,6 +117,10 @@ pub unsafe extern "C" fn mullvad_response_drop(response: SwiftMullvadApiResponse
94117
let _ = Vec::from_raw_parts(response.body, response.body_size, response.body_size);
95118
}
96119

120+
if !response.etag.is_null() {
121+
let _ = CString::from_raw(response.etag.cast());
122+
}
123+
97124
if !response.error_description.is_null() {
98125
let _ = CString::from_raw(response.error_description.cast());
99126
}

0 commit comments

Comments
 (0)