From 90acf0673c9a7f77f80441d45a00139de4e96dac Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Fri, 7 Feb 2025 15:30:25 +0100 Subject: [PATCH] Add documentation on Rust side --- .../include/mullvad_rust_runtime.h | 134 +++++++++++++----- .../xcshareddata/swiftpm/Package.resolved | 23 +++ mullvad-ios/src/api/api.rs | 8 +- mullvad-ios/src/api/cancellation.rs | 10 ++ mullvad-ios/src/api/completion.rs | 8 ++ mullvad-ios/src/api/mod.rs | 29 ++-- mullvad-ios/src/api/response.rs | 15 +- mullvad-ios/src/lib.rs | 4 +- 8 files changed, 170 insertions(+), 61 deletions(-) create mode 100644 ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h index 2e31430df6e6..faae315d6de0 100644 --- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h +++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h @@ -26,6 +26,29 @@ typedef struct ExchangeCancelToken ExchangeCancelToken; typedef struct RequestCancelHandle RequestCancelHandle; +typedef struct SwiftApiContext { + const struct ApiContext *_0; +} SwiftApiContext; + +typedef struct SwiftCancelHandle { + struct RequestCancelHandle *ptr; +} SwiftCancelHandle; + +typedef struct SwiftMullvadApiResponse { + uint8_t *body; + uintptr_t body_size; + uint16_t status_code; + uint8_t *error_description; + uint8_t *server_response_code; + bool success; + bool should_retry; + uint64_t retry_after; +} SwiftMullvadApiResponse; + +typedef struct CompletionCookie { + void *_0; +} CompletionCookie; + typedef struct ProxyHandle { void *context; uint16_t port; @@ -51,30 +74,87 @@ typedef struct EphemeralPeerParameters { struct WgTcpConnectionFunctions funcs; } EphemeralPeerParameters; -typedef struct SwiftApiContext { - const struct ApiContext *_0; -} SwiftApiContext; +extern const uint16_t CONFIG_SERVICE_PORT; -typedef struct SwiftCancelHandle { - struct RequestCancelHandle *ptr; -} SwiftCancelHandle; +/** + * # Safety + * + * `host` must be a pointer to a null terminated string representing a hostname for Mullvad API host. + * This hostname will be used for TLS validation but not used for domain name resolution. + * + * `address` must be a pointer to a null terminated string representing a socket address through which + * the Mullvad API can be reached directly. + * + * If a context cannot be constructed this function will panic since the call site would not be able + * to proceed in a meaningful way anyway. + * + * This function is safe. + */ +struct SwiftApiContext mullvad_api_init_new(const uint8_t *host, + const uint8_t *address); -typedef struct SwiftMullvadApiResponse { - uint8_t *body; - uintptr_t body_size; - uint16_t status_code; - uint8_t *error_description; - uint8_t *server_response_code; - bool success; - bool should_retry; - uint64_t retry_after; -} SwiftMullvadApiResponse; +/** + * # Safety + * + * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created + * by calling `mullvad_api_init_new`. + * + * `completion_cookie` must be pointing to a valid instance of `CompletionCookie`. `CompletionCookie` is + * safe because the pointer in `MullvadApiCompletion` is valid for the lifetime of the process where this + * type is intended to be used. + * + * This function is not safe to call multiple times with the same `CompletionCookie`. + */ +struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_context, + void *completion_cookie); -typedef struct CompletionCookie { - void *_0; -} CompletionCookie; +/** + * 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. + * + * # Safety + * + * `handle_ptr` must be pointing to a valid instance of `SwiftCancelHandle`. This function + * is not safe to call multiple times with the same `SwiftCancelHandle`. + */ +void mullvad_api_cancel_task(struct SwiftCancelHandle handle_ptr); -extern const uint16_t CONFIG_SERVICE_PORT; +/** + * Called by the Swift side to signal that the Rust `SwiftCancelHandle` can be safely + * dropped from memory. + * + * # Safety + * + * `handle_ptr` must be pointing to a valid instance of `SwiftCancelHandle`. This function + * is not safe to call multiple times with the same `SwiftCancelHandle`. + */ +void mullvad_api_cancel_task_drop(struct SwiftCancelHandle handle_ptr); + +/** + * Maps to `mullvadApiCompletionFinish` on Swift side to facilitate callback based completion flow when doing + * network calls through Mullvad API on Rust side. + * + * # Safety + * + * `response` must be pointing to a valid instance of `SwiftMullvadApiResponse`. + * + * `completion_cookie` must be pointing to a valid instance of `CompletionCookie`. `CompletionCookie` is safe + * because the pointer in `MullvadApiCompletion` is valid for the lifetime of the process where this type is + * intended to be used. + */ +extern void mullvad_api_completion_finish(struct SwiftMullvadApiResponse response, + struct CompletionCookie completion_cookie); + +/** + * Called by the Swift side to signal that the Rust `SwiftMullvadApiResponse` can be safely + * dropped from memory. + * + * # Safety + * + * `response` must be pointing to a valid instance of `SwiftMullvadApiResponse`. This function + * is not safe to call multiple times with the same `SwiftMullvadApiResponse`. + */ +void mullvad_response_drop(struct SwiftMullvadApiResponse response); /** * Initializes a valid pointer to an instance of `EncryptedDnsProxyState`. @@ -200,20 +280,6 @@ int32_t start_shadowsocks_proxy(const uint8_t *forward_address, */ int32_t stop_shadowsocks_proxy(struct ProxyHandle *proxy_config); -struct SwiftApiContext mullvad_api_init_new(const uint8_t *host, const uint8_t *address); - -struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_context, - void *completion_cookie); - -void mullvad_api_cancel_task(struct SwiftCancelHandle handle_ptr); - -void mullvad_api_cancel_task_drop(struct SwiftCancelHandle handle_ptr); - -extern void mullvad_api_completion_finish(struct SwiftMullvadApiResponse response, - struct CompletionCookie completion_cookie); - -void mullvad_response_drop(struct SwiftMullvadApiResponse response); - int32_t start_tunnel_obfuscator_proxy(const uint8_t *peer_address, uintptr_t peer_address_len, uint16_t peer_port, diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000000..327eb5a22f5e --- /dev/null +++ b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,23 @@ +{ + "originHash" : "c15149b2d59d9e9c72375f65339c04f41a19943e1117e682df27fc9f943fdc56", + "pins" : [ + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "173f567a2dfec11d74588eea82cecea555bdc0bc", + "version" : "1.4.0" + } + }, + { + "identity" : "wireguard-apple", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mullvad/wireguard-apple.git", + "state" : { + "revision" : "f19338dafd349fd6ddb1c1032b5705d362f56d2b" + } + } + ], + "version" : 3 +} diff --git a/mullvad-ios/src/api/api.rs b/mullvad-ios/src/api/api.rs index 929d4a7575c9..e553d9212baa 100644 --- a/mullvad-ios/src/api/api.rs +++ b/mullvad-ios/src/api/api.rs @@ -1,9 +1,13 @@ -use mullvad_api::{rest::{self, MullvadRestHandle}, ApiProxy}; +use mullvad_api::{ + rest::{self, MullvadRestHandle}, + ApiProxy, +}; use super::{ cancellation::{RequestCancelHandle, SwiftCancelHandle}, completion::{CompletionCookie, SwiftCompletionHandler}, - response::SwiftMullvadApiResponse, SwiftApiContext, + response::SwiftMullvadApiResponse, + SwiftApiContext, }; #[no_mangle] diff --git a/mullvad-ios/src/api/cancellation.rs b/mullvad-ios/src/api/cancellation.rs index 2768f78b3564..cf39e1ceb312 100644 --- a/mullvad-ios/src/api/cancellation.rs +++ b/mullvad-ios/src/api/cancellation.rs @@ -54,6 +54,11 @@ impl RequestCancelHandle { } } +/// 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. +/// +/// # Safety +/// `handle_ptr` must be pointing to a valid instance of `SwiftCancelHandle`. #[no_mangle] extern "C" fn mullvad_api_cancel_task(handle_ptr: SwiftCancelHandle) { if handle_ptr.ptr.is_null() { @@ -64,6 +69,11 @@ extern "C" fn mullvad_api_cancel_task(handle_ptr: SwiftCancelHandle) { handle.cancel() } +/// Called by the Swift side to signal that the Rust `SwiftCancelHandle` can be safely +/// dropped from memory. +/// +/// # Safety +/// `handle_ptr` must be pointing to a valid instance of `SwiftCancelHandle`. #[no_mangle] extern "C" fn mullvad_api_cancel_task_drop(handle_ptr: SwiftCancelHandle) { if handle_ptr.ptr.is_null() { diff --git a/mullvad-ios/src/api/completion.rs b/mullvad-ios/src/api/completion.rs index fc1e2adfbac6..3b0085f8c504 100644 --- a/mullvad-ios/src/api/completion.rs +++ b/mullvad-ios/src/api/completion.rs @@ -3,6 +3,14 @@ use std::sync::{Arc, Mutex}; use super::response::SwiftMullvadApiResponse; extern "C" { + /// Maps to `mullvadApiCompletionFinish` on Swift side to facilitate callback based completion flow when doing + /// network calls through Mullvad API on Rust side. + /// + /// # Safety + /// `response` must be pointing to a valid instance of `SwiftMullvadApiResponse`. + /// `completion_cookie` must be pointing to a valid instance of `CompletionCookie`. `CompletionCookie` is safe + /// because the pointer in `MullvadApiCompletion` is valid for the lifetime of the process where this type is + /// intended to be used. pub fn mullvad_api_completion_finish( response: SwiftMullvadApiResponse, completion_cookie: CompletionCookie, diff --git a/mullvad-ios/src/api/mod.rs b/mullvad-ios/src/api/mod.rs index 1d57b5d87aa0..3deb0d1dcb60 100644 --- a/mullvad-ios/src/api/mod.rs +++ b/mullvad-ios/src/api/mod.rs @@ -1,4 +1,4 @@ -use std::{ffi::CStr, ptr::null_mut, sync::Arc}; +use std::{ffi::CStr, sync::Arc}; use mullvad_api::{ proxy::{ApiConnectionMode, StaticConnectionModeProvider}, rest::MullvadRestHandle, ApiEndpoint, Runtime @@ -32,31 +32,30 @@ impl ApiContext { } } +/// # Safety +/// +/// `host` must be a pointer to a null terminated string representing a hostname for Mullvad API host. +/// This hostname will be used for TLS validation but not used for domain name resolution. +/// +/// `address` must be a pointer to a null terminated string representing a socket address through which +/// the Mullvad API can be reached directly. +/// +/// If a context cannot be constructed this function will panic since the call site would not be able +/// to proceed in a meaningful way anyway. #[no_mangle] pub extern "C" fn mullvad_api_init_new(host: *const u8, address: *const u8) -> SwiftApiContext { let host = unsafe { CStr::from_ptr(host.cast()) }; let address = unsafe { CStr::from_ptr(address.cast()) }; - let Ok(host) = host.to_str() else { - return SwiftApiContext(null_mut()); - }; - - let Ok(address) = address.to_str() else { - return SwiftApiContext(null_mut()); - }; + let host = host.to_str().unwrap(); + let address = address.to_str().unwrap(); let endpoint = ApiEndpoint { host: Some(String::from(host)), address: Some(address.parse().unwrap()), }; - let tokio_handle = match crate::mullvad_ios_runtime() { - Ok(tokio_handle) => tokio_handle, - Err(err) => { - log::error!("Failed to obtain a handle to a tokio runtime: {err}"); - return SwiftApiContext(null_mut()); - } - }; + let tokio_handle = crate::mullvad_ios_runtime().unwrap(); let api_context = tokio_handle.clone().block_on(async move { // It is imperative that the REST runtime is created within an async context, otherwise diff --git a/mullvad-ios/src/api/response.rs b/mullvad-ios/src/api/response.rs index c83a002392dd..b6dd3bee5755 100644 --- a/mullvad-ios/src/api/response.rs +++ b/mullvad-ios/src/api/response.rs @@ -1,11 +1,7 @@ -use std::{ - ffi::CString, - ptr::null_mut, -}; +use std::{ffi::CString, ptr::null_mut}; use mullvad_api::rest::{self, Response}; - #[repr(C)] pub struct SwiftMullvadApiResponse { body: *mut u8, @@ -50,7 +46,7 @@ impl SwiftMullvadApiResponse { let should_retry = err.is_network_error(); let error_description = to_cstr_pointer(err.to_string()); - let (status_code, server_response_code):(u16, _) = + let (status_code, server_response_code): (u16, _) = if let rest::Error::ApiError(status_code, error_code) = err { (status_code.into(), to_cstr_pointer(error_code)) } else { @@ -81,7 +77,7 @@ impl SwiftMullvadApiResponse { retry_after: 0, } } - + pub fn no_tokio_runtime() -> Self { Self { success: false, @@ -96,6 +92,11 @@ impl SwiftMullvadApiResponse { } } +/// Called by the Swift side to signal that the Rust `SwiftMullvadApiResponse` can be safely +/// dropped from memory. +/// +/// # Safety +/// `response` must be pointing to a valid instance of `SwiftMullvadApiResponse`. #[no_mangle] pub unsafe extern "C" fn mullvad_response_drop(response: SwiftMullvadApiResponse) { if !response.body.is_null() { diff --git a/mullvad-ios/src/lib.rs b/mullvad-ios/src/lib.rs index 22705037ec24..0e381b2007ed 100644 --- a/mullvad-ios/src/lib.rs +++ b/mullvad-ios/src/lib.rs @@ -1,8 +1,8 @@ #![cfg(target_os = "ios")] +mod api; mod encrypted_dns_proxy; mod ephemeral_peer_proxy; mod shadowsocks_proxy; -mod api; pub mod tunnel_obfuscator_proxy; #[repr(C)] @@ -34,5 +34,3 @@ mod ios { } use ios::*; - -