diff options
| author | Jon Petersson <jon.petersson@mullvad.net> | 2025-03-10 13:45:43 +0100 |
|---|---|---|
| committer | Jon Petersson <jon.petersson@mullvad.net> | 2025-03-31 11:39:29 +0200 |
| commit | efbb2c3c0c95f7e7a195c03e9d2483ec731a578e (patch) | |
| tree | c297326cb661c12986e00c14ef8f47bd0cd37337 | |
| parent | 2640cd40b6a7a2468945b5c4c4be42bbe509c4e5 (diff) | |
| download | mullvadvpn-efbb2c3c0c95f7e7a195c03e9d2483ec731a578e.tar.xz mullvadvpn-efbb2c3c0c95f7e7a195c03e9d2483ec731a578e.zip | |
Implement call for getting relays on Rust side
| -rw-r--r-- | ios/MullvadRustRuntime/include/mullvad_rust_runtime.h | 8 | ||||
| -rw-r--r-- | mullvad-api/src/relay_list.rs | 69 | ||||
| -rw-r--r-- | mullvad-ios/src/api_client/api.rs | 62 | ||||
| -rw-r--r-- | mullvad-ios/src/api_client/response.rs | 31 |
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()); } |
