summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2024-01-30 16:10:01 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-03-11 15:45:50 +0100
commita2bab9ba4349d03cf3c3b771a865ef71ca857ad0 (patch)
treed24bed6f3f385a262771bda3d638fe593baf7103
parent517de3acfa885bc499b8f2de6df190b703a93c96 (diff)
downloadmullvadvpn-a2bab9ba4349d03cf3c3b771a865ef71ca857ad0.tar.xz
mullvadvpn-a2bab9ba4349d03cf3c3b771a865ef71ca857ad0.zip
Add FFI for mullvad-api
-rw-r--r--mullvad-api/src/ios_ffi.rs317
-rw-r--r--mullvad-api/src/lib.rs21
2 files changed, 338 insertions, 0 deletions
diff --git a/mullvad-api/src/ios_ffi.rs b/mullvad-api/src/ios_ffi.rs
new file mode 100644
index 0000000000..ed5e0794a8
--- /dev/null
+++ b/mullvad-api/src/ios_ffi.rs
@@ -0,0 +1,317 @@
+use std::{net::SocketAddr, sync::Arc};
+
+use crate::{
+ rest::{self, MullvadRestHandle},
+ AccountsProxy, DevicesProxy,
+};
+
+#[derive(Debug, PartialEq)]
+#[repr(i32)]
+pub enum FfiError {
+ NoError = 0,
+ StringParsing = -1,
+ SocketAddressParsing = -2,
+ AsyncRuntimeInitialization = -3,
+ BadResponse = -4,
+ BufferTooSmall = -5,
+}
+
+/// IosMullvadApiClient is an FFI interface to our `mullvad-api`. It is a thread-safe to accessing
+/// our API.
+#[derive(Clone)]
+#[repr(C)]
+pub struct IosMullvadApiClient {
+ ptr: *const IosApiClientContext,
+}
+
+impl IosMullvadApiClient {
+ fn new(context: IosApiClientContext) -> Self {
+ let sync_context = Arc::new(context);
+ let ptr = Arc::into_raw(sync_context);
+ Self { ptr }
+ }
+
+ unsafe fn from_raw(self) -> Arc<IosApiClientContext> {
+ unsafe {
+ Arc::increment_strong_count(self.ptr);
+ }
+
+ Arc::from_raw(self.ptr)
+ }
+}
+
+struct IosApiClientContext {
+ tokio_runtime: tokio::runtime::Runtime,
+ api_runtime: crate::Runtime,
+ api_hostname: String,
+}
+
+impl IosApiClientContext {
+ fn rest_handle(self: Arc<Self>) -> MullvadRestHandle {
+ self.tokio_runtime.block_on(
+ self.api_runtime
+ .static_mullvad_rest_handle(self.api_hostname.clone()),
+ )
+ }
+
+ fn devices_proxy(self: Arc<Self>) -> DevicesProxy {
+ crate::DevicesProxy::new(self.rest_handle())
+ }
+
+ fn accounts_proxy(self: Arc<Self>) -> AccountsProxy {
+ crate::AccountsProxy::new(self.rest_handle())
+ }
+
+ fn tokio_handle(self: &Arc<Self>) -> tokio::runtime::Handle {
+ self.tokio_runtime.handle().clone()
+ }
+}
+
+/// Paramters:
+/// `api_address`: pointer to UTF-8 string containing a socket address representation
+/// ("143.32.4.32:9090"), the port is mandatory.
+///
+/// `api_address_len`: size of the API address string
+#[no_mangle]
+pub extern "C" fn mullvad_api_initialize_api_runtime(
+ context_ptr: *mut IosMullvadApiClient,
+ api_address_ptr: *const u8,
+ api_address_len: usize,
+ hostname: *const u8,
+ hostname_len: usize,
+) -> FfiError {
+ let Some(addr_str) = (unsafe { string_from_raw_ptr(api_address_ptr, api_address_len) }) else {
+ return FfiError::StringParsing;
+ };
+ let Some(api_hostname) = (unsafe { string_from_raw_ptr(hostname, hostname_len) }) else {
+ return FfiError::StringParsing;
+ };
+
+ let Ok(api_address): Result<SocketAddr, _> = addr_str.parse() else {
+ return FfiError::SocketAddressParsing;
+ };
+
+ let mut runtime_builder = tokio::runtime::Builder::new_multi_thread();
+
+ runtime_builder.worker_threads(2).enable_all();
+ let Ok(tokio_runtime) = runtime_builder.build() else {
+ return FfiError::AsyncRuntimeInitialization;
+ };
+
+ // It is imperative that the REST runtime is created within an async context, otherwise
+ // ApiAvailability panics.
+ let api_runtime = tokio_runtime.block_on(async {
+ crate::Runtime::with_static_addr(tokio_runtime.handle().clone(), api_address)
+ });
+
+ let ios_context = IosApiClientContext {
+ tokio_runtime,
+ api_runtime,
+ api_hostname,
+ };
+
+ let context = IosMullvadApiClient::new(ios_context);
+
+ unsafe {
+ std::ptr::write(context_ptr, context);
+ }
+
+ FfiError::NoError
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_remove_all_devices(
+ context: IosMullvadApiClient,
+ account_str_ptr: *const u8,
+ account_str_len: usize,
+) -> FfiError {
+ let ctx = unsafe { context.from_raw() };
+ let Some(account) = (unsafe { string_from_raw_ptr(account_str_ptr, account_str_len) }) else {
+ return FfiError::StringParsing;
+ };
+
+ let runtime = ctx.tokio_handle();
+ let device_proxy = ctx.devices_proxy();
+ let result = runtime.block_on(async move {
+ let devices = device_proxy.list(account.clone()).await?;
+ for device in devices {
+ device_proxy.remove(account.clone(), device.id).await?;
+ }
+ Result::<_, rest::Error>::Ok(())
+ });
+
+ match result {
+ Ok(()) => FfiError::NoError,
+ Err(_err) => FfiError::BadResponse,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_get_expiry(
+ context: IosMullvadApiClient,
+ account_str_ptr: *const u8,
+ account_str_len: usize,
+ expiry_unix_timestamp: *mut i64,
+) -> FfiError {
+ let Some(account) = (unsafe { string_from_raw_ptr(account_str_ptr, account_str_len) }) else {
+ return FfiError::StringParsing;
+ };
+
+ let ctx = unsafe { context.from_raw() };
+ let runtime = ctx.tokio_handle();
+
+ let account_proxy = ctx.accounts_proxy();
+ let result: Result<_, rest::Error> = runtime.block_on(async move {
+ let expiry = account_proxy.get_data(account).await?.expiry;
+ let expiry_timestamp = expiry.timestamp();
+
+ Ok(expiry_timestamp)
+ });
+
+ match result {
+ Ok(expiry) => {
+ // SAFETY: It is assumed that expiry_timestamp is a valid pointer to a `libc::timespec`
+ unsafe {
+ std::ptr::write(expiry_unix_timestamp, expiry);
+ }
+ FfiError::NoError
+ }
+ Err(_err) => FfiError::BadResponse,
+ }
+}
+
+/// Args:
+/// context: `IosApiContext`
+/// public_key: a pointer to a valid 32 byte array representing a WireGuard public key
+#[no_mangle]
+pub extern "C" fn mullvad_api_add_device(
+ context: IosMullvadApiClient,
+ account_str_ptr: *const u8,
+ account_str_len: usize,
+ public_key_ptr: *const u8,
+) -> FfiError {
+ let Some(account) = (unsafe { string_from_raw_ptr(account_str_ptr, account_str_len) }) else {
+ return FfiError::StringParsing;
+ };
+ let public_key_bytes: [u8; 32] = unsafe { std::ptr::read(public_key_ptr as *const _) };
+ let public_key = public_key_bytes.into();
+
+ let ctx = unsafe { context.from_raw() };
+ let runtime = ctx.tokio_handle();
+
+ let devices_proxy = ctx.devices_proxy();
+
+ let result: Result<_, rest::Error> = runtime.block_on(async move {
+ let (new_device, _) = devices_proxy.create(account, public_key).await?;
+ Ok(new_device)
+ });
+
+ match result {
+ Ok(_result) => FfiError::NoError,
+ Err(_err) => FfiError::BadResponse,
+ }
+}
+
+/// Args:
+/// context: `IosApiContext`
+/// account_str_ptr: A pointer to a byte buffer large enough to contain a valid account number
+/// string.
+/// account_str_len: A pointer to an unsigned pointer-sized integer specifying the length of the
+/// input buffer. If the buffer is big enough and a new account is created, it will contain the
+/// amount of bytes that were written to the buffer.
+#[no_mangle]
+pub extern "C" fn mullvad_api_create_account(
+ context: IosMullvadApiClient,
+ account_str_ptr: *mut u8,
+ account_str_len: *mut usize,
+) -> MullvadApiError {
+ let ctx = unsafe { context.from_raw() };
+ let runtime = ctx.tokio_handle();
+ let buffer_len = unsafe { ptr::read(account_str_len) };
+
+ let accounts_proxy = ctx.accounts_proxy();
+
+ let result: Result<_, rest::Error> = runtime.block_on(async move {
+ let new_account = accounts_proxy.create_account().await?;
+ Ok(new_account)
+ });
+
+ match result {
+ Ok(new_account) => {
+ let new_account_bytes = new_account.into_bytes();
+ if new_account_bytes.len() > buffer_len {
+ return MullvadApiError::with_str(
+ MullvadApiErrorKind::BufferTooSmall,
+ "Buffer for account number is too small",
+ );
+ }
+ unsafe {
+ ptr::copy(
+ new_account_bytes.as_ptr(),
+ account_str_ptr,
+ new_account_bytes.len(),
+ );
+ }
+
+ MullvadApiError::ok()
+ }
+ Err(err) => MullvadApiError::api_err(&err),
+ }
+}
+
+/// Args:
+/// context: `IosApiContext`
+/// public_key: a pointer to a valid 32 byte array representing a WireGuard public key
+#[no_mangle]
+pub extern "C" fn mullvad_api_delete_account(
+ context: IosMullvadApiClient,
+ account_str_ptr: *mut u8,
+ account_str_len: *mut usize,
+) -> MullvadApiError {
+ let ctx = unsafe { context.from_raw() };
+ let runtime = ctx.tokio_handle();
+ let buffer_len = unsafe { ptr::read(account_str_len) };
+
+ let accounts_proxy = ctx.accounts_proxy();
+
+ let result: Result<_, rest::Error> = runtime.block_on(async move {
+ let new_account = accounts_proxy.create_account().await?;
+ Ok(new_account)
+ });
+
+ match result {
+ Ok(new_account) => {
+ let new_account_bytes = new_account.into_bytes();
+ if new_account_bytes.len() > buffer_len {
+ return MullvadApiError::with_str(
+ MullvadApiErrorKind::BufferTooSmall,
+ "Buffer for account number is too small",
+ );
+ }
+ unsafe {
+ ptr::copy(
+ new_account_bytes.as_ptr(),
+ account_str_ptr,
+ new_account_bytes.len(),
+ );
+ }
+
+ MullvadApiError::ok()
+ }
+ Err(err) => MullvadApiError::api_err(&err),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_runtime_drop(context: IosMullvadApiClient) {
+ unsafe { Arc::decrement_strong_count(context.ptr) }
+}
+
+/// The return value is only valid for the lifetime of the `ptr` that's passed in
+///
+/// SAFETY: `ptr` must be valid for `size` bytes
+unsafe fn string_from_raw_ptr(ptr: *const u8, size: usize) -> Option<String> {
+ let slice = unsafe { std::slice::from_raw_parts(ptr, size) };
+
+ String::from_utf8(slice.to_vec()).ok()
+}
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs
index c4d5665a01..d7bc6acb47 100644
--- a/mullvad-api/src/lib.rs
+++ b/mullvad-api/src/lib.rs
@@ -454,6 +454,27 @@ impl Runtime {
rest::MullvadRestHandle::new(service, factory, self.availability_handle())
}
+ /// This is only to be used in test code
+ pub async fn static_mullvad_rest_handle(&self, hostname: String) -> rest::MullvadRestHandle {
+ let service = self
+ .new_request_service(
+ Some(hostname.clone()),
+ futures::stream::repeat(ApiConnectionMode::Direct),
+ #[cfg(target_os = "android")]
+ self.socket_bypass_tx.clone(),
+ )
+ .await;
+ let token_store = access::AccessTokenStore::new(service.clone());
+ let factory = rest::RequestFactory::new(hostname, Some(token_store));
+
+ rest::MullvadRestHandle::new(
+ service,
+ factory,
+ self.address_cache.clone(),
+ self.availability_handle(),
+ )
+ }
+
/// Returns a new request service handle
pub fn rest_handle(&self) -> rest::RequestServiceHandle {
self.new_request_service(