summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-03-10 13:45:43 +0100
committerJon Petersson <jon.petersson@mullvad.net>2025-03-31 11:39:29 +0200
commitefbb2c3c0c95f7e7a195c03e9d2483ec731a578e (patch)
treec297326cb661c12986e00c14ef8f47bd0cd37337
parent2640cd40b6a7a2468945b5c4c4be42bbe509c4e5 (diff)
downloadmullvadvpn-efbb2c3c0c95f7e7a195c03e9d2483ec731a578e.tar.xz
mullvadvpn-efbb2c3c0c95f7e7a195c03e9d2483ec731a578e.zip
Implement call for getting relays on Rust side
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h8
-rw-r--r--mullvad-api/src/relay_list.rs69
-rw-r--r--mullvad-ios/src/api_client/api.rs62
-rw-r--r--mullvad-ios/src/api_client/response.rs31
4 files changed, 139 insertions, 31 deletions
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index e054de0044..abdc8ea809 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -24,6 +24,8 @@ typedef struct EncryptedDnsProxyState EncryptedDnsProxyState;
typedef struct ExchangeCancelToken ExchangeCancelToken;
+typedef struct Option______u8 Option______u8;
+
typedef struct RequestCancelHandle RequestCancelHandle;
typedef struct RetryStrategy RetryStrategy;
@@ -43,6 +45,7 @@ typedef struct SwiftRetryStrategy {
typedef struct SwiftMullvadApiResponse {
uint8_t *body;
uintptr_t body_size;
+ uint8_t *etag;
uint16_t status_code;
uint8_t *error_description;
uint8_t *server_response_code;
@@ -113,6 +116,11 @@ struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_co
void *completion_cookie,
struct SwiftRetryStrategy retry_strategy);
+struct SwiftCancelHandle mullvad_api_get_relays(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ struct Option______u8 etag);
+
/**
* Called by the Swift side to signal that a Mullvad API call should be cancelled.
* After this call, the cancel token is no longer valid.
diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs
index f19961bc8a..2b34996c23 100644
--- a/mullvad-api/src/relay_list.rs
+++ b/mullvad-api/src/relay_list.rs
@@ -2,7 +2,7 @@
use crate::rest;
-use hyper::{header, StatusCode};
+use hyper::{body::Incoming, header, Error, StatusCode};
use mullvad_types::{location, relay_list};
use talpid_types::net::wireguard;
@@ -29,44 +29,57 @@ impl RelayListProxy {
}
/// Fetch the relay list
- pub fn relay_list(
+ pub async fn relay_list(
&self,
etag: Option<String>,
- ) -> impl Future<Output = Result<Option<relay_list::RelayList>, rest::Error>> + use<> {
- let service = self.handle.service.clone();
- let request = self.handle.factory.get("app/v1/relays");
+ ) -> Result<Option<relay_list::RelayList>, rest::Error> {
+ let response = self.relay_list_response(etag.clone()).await.map_err(rest::Error::from)?;
- async move {
- let mut request = request?
- .timeout(RELAY_LIST_TIMEOUT)
- .expected_status(&[StatusCode::NOT_MODIFIED, StatusCode::OK]);
+ if etag.is_some() && response.status() == StatusCode::NOT_MODIFIED {
+ return Ok(None);
+ }
- if let Some(ref tag) = etag {
- request = request.header(header::IF_NONE_MATCH, tag)?;
- }
+ let etag = Self::extract_etag(&response);
- let response = service.request(request).await?;
- if etag.is_some() && response.status() == StatusCode::NOT_MODIFIED {
- return Ok(None);
- }
+ let relay_list: ServerRelayList = response.deserialize().await?;
+ Ok(Some(relay_list.into_relay_list(etag)))
+ }
- let etag = response
- .headers()
- .get(header::ETAG)
- .and_then(|tag| match tag.to_str() {
- Ok(tag) => Some(tag.to_string()),
- Err(_) => {
- log::error!("Ignoring invalid tag from server: {:?}", tag.as_bytes());
- None
- }
- });
+ pub async fn relay_list_response(
+ &self,
+ etag: Option<String>,
+ ) -> Result<rest::Response<Incoming>, rest::Error> {
+ let service = self.handle.service.clone();
+ let request = self.handle.factory.get("app/v1/relays");
- let relay_list: ServerRelayList = response.deserialize().await?;
- Ok(Some(relay_list.into_relay_list(etag)))
+ let mut request = request?
+ .timeout(RELAY_LIST_TIMEOUT)
+ .expected_status(&[StatusCode::NOT_MODIFIED, StatusCode::OK]);
+
+ if let Some(ref tag) = etag {
+ request = request.header(header::IF_NONE_MATCH, tag)?;
}
+
+ let response = service.request(request).await?;
+
+ Ok(response)
+ }
+
+ pub fn extract_etag(response: &rest::Response<Incoming>) -> Option<String> {
+ response
+ .headers()
+ .get(header::ETAG)
+ .and_then(|tag| match tag.to_str() {
+ Ok(tag) => Some(tag.to_string()),
+ Err(_) => {
+ log::error!("Ignoring invalid tag from server: {:?}", tag.as_bytes());
+ None
+ }
+ })
}
}
+
#[derive(Debug, serde::Deserialize)]
struct ServerRelayList {
locations: BTreeMap<String, Location>,
diff --git a/mullvad-ios/src/api_client/api.rs b/mullvad-ios/src/api_client/api.rs
index 847e81f0eb..d3c521159f 100644
--- a/mullvad-ios/src/api_client/api.rs
+++ b/mullvad-ios/src/api_client/api.rs
@@ -1,6 +1,8 @@
+use std::{ffi::CStr, ptr};
+
use mullvad_api::{
rest::{self, MullvadRestHandle},
- ApiProxy,
+ ApiProxy, RelayListProxy,
};
use talpid_future::retry::retry_future;
@@ -69,3 +71,61 @@ async fn mullvad_api_get_addresses_inner(
SwiftMullvadApiResponse::with_body(response).await
}
+
+#[no_mangle]
+pub unsafe extern "C" fn mullvad_api_get_relays(
+ api_context: SwiftApiContext,
+ completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
+ etag: Option<*const u8>,
+) -> SwiftCancelHandle {
+ let completion_handler = SwiftCompletionHandler::new(CompletionCookie(completion_cookie));
+
+ let Ok(tokio_handle) = crate::mullvad_ios_runtime() else {
+ completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime());
+ return SwiftCancelHandle::empty();
+ };
+
+ let api_context = api_context.into_rust_context();
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
+
+ let etag = match etag {
+ Some(etag) => {
+ let unwrapped_tag = unsafe { CStr::from_ptr(etag.cast()) }.to_str().unwrap();
+ Some(String::from(unwrapped_tag))
+ },
+ None => None,
+ };
+
+ let completion = completion_handler.clone();
+ let task = tokio_handle.clone().spawn(async move {
+ match mullvad_api_get_relays_inner(api_context.rest_handle(), retry_strategy, etag).await {
+ Ok(response) => completion.finish(response),
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::rest_error(err));
+ }
+ }
+ });
+
+ RequestCancelHandle::new(task, completion_handler.clone()).into_swift()
+}
+
+async fn mullvad_api_get_relays_inner(
+ rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
+ etag: Option<String>,
+) -> Result<SwiftMullvadApiResponse, rest::Error> {
+ let api = RelayListProxy::new(rest_client);
+
+ let future_factory = || api.relay_list_response(etag.clone());
+
+ let should_retry = |result: &Result<_, rest::Error>| match result {
+ Err(err) => err.is_network_error(),
+ Ok(_) => false,
+ };
+
+ let response = retry_future(future_factory, should_retry, retry_strategy.delays()).await?;
+
+ SwiftMullvadApiResponse::with_body(response).await
+} \ No newline at end of file
diff --git a/mullvad-ios/src/api_client/response.rs b/mullvad-ios/src/api_client/response.rs
index 6ffbadb5d6..7f09ae30b1 100644
--- a/mullvad-ios/src/api_client/response.rs
+++ b/mullvad-ios/src/api_client/response.rs
@@ -1,27 +1,47 @@
-use std::{ffi::CString, ptr::null_mut};
+use std::{
+ ffi::CString,
+ ptr::{self, null_mut},
+};
-use mullvad_api::rest::{self, Response};
+use mullvad_api::{
+ rest::{self, Response},
+ RelayListProxy,
+};
#[repr(C)]
pub struct SwiftMullvadApiResponse {
body: *mut u8,
body_size: usize,
+ etag: *mut u8,
status_code: u16,
error_description: *mut u8,
server_response_code: *mut u8,
success: bool,
}
+
impl SwiftMullvadApiResponse {
pub async fn with_body(response: Response<hyper::body::Incoming>) -> Result<Self, rest::Error> {
+ let maybe_etag = RelayListProxy::extract_etag(&response);
+
let status_code: u16 = response.status().into();
let body: Vec<u8> = response.body().await?;
let body_size = body.len();
let body = body.into_boxed_slice();
+ let etag = match maybe_etag {
+ Some(etag) => {
+ let header_value =
+ CString::new(etag).map_err(|_| rest::Error::InvalidHeaderError)?;
+ header_value.into_raw().cast()
+ },
+ None => ptr::null_mut(),
+ };
+
Ok(Self {
body: Box::<[u8]>::into_raw(body).cast(),
body_size,
+ etag,
status_code,
error_description: null_mut(),
server_response_code: null_mut(),
@@ -51,6 +71,7 @@ impl SwiftMullvadApiResponse {
Self {
body: null_mut(),
body_size: 0,
+ etag: null_mut(),
status_code,
error_description,
server_response_code,
@@ -64,6 +85,7 @@ impl SwiftMullvadApiResponse {
error_description: c"Request was cancelled".to_owned().into_raw().cast(),
body: null_mut(),
body_size: 0,
+ etag: null_mut(),
status_code: 0,
server_response_code: null_mut(),
}
@@ -75,6 +97,7 @@ impl SwiftMullvadApiResponse {
error_description: c"Failed to get Tokio runtime".to_owned().into_raw().cast(),
body: null_mut(),
body_size: 0,
+ etag: null_mut(),
status_code: 0,
server_response_code: null_mut(),
}
@@ -94,6 +117,10 @@ pub unsafe extern "C" fn mullvad_response_drop(response: SwiftMullvadApiResponse
let _ = Vec::from_raw_parts(response.body, response.body_size, response.body_size);
}
+ if !response.etag.is_null() {
+ let _ = CString::from_raw(response.etag.cast());
+ }
+
if !response.error_description.is_null() {
let _ = CString::from_raw(response.error_description.cast());
}