summaryrefslogtreecommitdiffhomepage
path: root/mullvad-api/src
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2024-01-31 09:56:51 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-02-14 17:37:42 +0100
commit8136c8476a059867a338d3a6bebd68c9ba85aa0b (patch)
treeb8fa13aa2ecf089f79286a93146bf060c37b608f /mullvad-api/src
parente2c4fb47f4352cf1eb1200fbb29b9539c86b73e3 (diff)
downloadmullvadvpn-8136c8476a059867a338d3a6bebd68c9ba85aa0b.tar.xz
mullvadvpn-8136c8476a059867a338d3a6bebd68c9ba85aa0b.zip
Fix talpid-time for iOS, add FFI for mullvad-api
Diffstat (limited to 'mullvad-api/src')
-rw-r--r--mullvad-api/src/address_cache.rs5
-rw-r--r--mullvad-api/src/ffi/device.rs104
-rw-r--r--mullvad-api/src/ffi/error.rs62
-rw-r--r--mullvad-api/src/ffi/mod.rs428
-rw-r--r--mullvad-api/src/lib.rs56
-rw-r--r--mullvad-api/src/rest.rs15
6 files changed, 666 insertions, 4 deletions
diff --git a/mullvad-api/src/address_cache.rs b/mullvad-api/src/address_cache.rs
index ea93d96e26..f164e15f09 100644
--- a/mullvad-api/src/address_cache.rs
+++ b/mullvad-api/src/address_cache.rs
@@ -34,6 +34,11 @@ impl AddressCache {
Self::new_inner(API.address(), write_path)
}
+ pub fn with_static_addr(address: SocketAddr) -> Self {
+ Self::new_inner(address, None)
+ .expect("Failed to construct an address cache from a static address")
+ }
+
/// Initialize cache using `read_path`, and write changes to `write_path`.
pub async fn from_file(read_path: &Path, write_path: Option<Box<Path>>) -> Result<Self, Error> {
log::debug!("Loading API addresses from {}", read_path.display());
diff --git a/mullvad-api/src/ffi/device.rs b/mullvad-api/src/ffi/device.rs
new file mode 100644
index 0000000000..82ba5f59cc
--- /dev/null
+++ b/mullvad-api/src/ffi/device.rs
@@ -0,0 +1,104 @@
+use mullvad_types::device::Device;
+use std::{ffi::CString, ptr};
+
+#[repr(C)]
+pub struct MullvadApiDeviceIterator {
+ ptr: *mut DeviceIterator,
+}
+
+impl MullvadApiDeviceIterator {
+ pub fn new(iter: impl IntoIterator<Item = Device> + 'static) -> Self {
+ let iter = Box::new(DeviceIterator::from(iter));
+
+ Self {
+ ptr: Box::into_raw(iter),
+ }
+ }
+
+ fn is_done(&self) -> bool {
+ self.ptr.is_null()
+ }
+
+ unsafe fn as_iter(&mut self) -> &mut Box<dyn Iterator<Item = Device>> {
+ let wrapper = unsafe { &mut *self.ptr };
+ &mut wrapper.iter
+ }
+
+ fn drop(mut self) {
+ if self.ptr.is_null() {
+ return;
+ }
+
+ let _ = unsafe { Box::from_raw(self.ptr) };
+ self.ptr = ptr::null_mut();
+ }
+}
+
+#[repr(C)]
+pub struct MullvadApiDevice {
+ name_ptr: *const libc::c_char,
+ id: [u8; 16],
+}
+
+impl From<Device> for MullvadApiDevice {
+ fn from(dev: Device) -> Self {
+ let name = CString::new(dev.name).expect("Null bytes in name from API response");
+ let name_ptr = name.into_raw();
+ let id = *uuid::Uuid::parse_str(&dev.id)
+ .expect("Failed to parse UUID")
+ .as_bytes();
+
+ Self { name_ptr, id }
+ }
+}
+
+impl MullvadApiDevice {
+ fn drop(self) {
+ let _ = unsafe { CString::from_raw(self.name_ptr as *mut _) };
+ }
+}
+
+struct DeviceIterator {
+ iter: Box<dyn Iterator<Item = Device>>,
+}
+
+impl<T> From<T> for DeviceIterator
+where
+ T: IntoIterator<Item = Device> + 'static,
+{
+ fn from(i: T) -> Self {
+ let iter: Box<dyn Iterator<Item = Device>> = Box::new(i.into_iter());
+ Self { iter }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_device_iter_next(
+ mut iter: MullvadApiDeviceIterator,
+ device_ptr: *mut MullvadApiDevice,
+) -> bool {
+ if iter.is_done() {
+ return false;
+ }
+
+ // SAFETY: Asuming self.ptr is still valid since iter.is_done() returned false;
+ let iter = unsafe { iter.as_iter() };
+ let Some(device) = iter.next() else {
+ return false;
+ };
+
+ // SAFETY: Assuming device pointer is valid
+ unsafe { ptr::write(device_ptr, device.into()) }
+
+ return true;
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_device_iter_drop(iter: MullvadApiDeviceIterator) {
+ iter.drop()
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_device_drop(device: MullvadApiDevice) {
+ device.drop()
+}
diff --git a/mullvad-api/src/ffi/error.rs b/mullvad-api/src/ffi/error.rs
new file mode 100644
index 0000000000..8f3095fee0
--- /dev/null
+++ b/mullvad-api/src/ffi/error.rs
@@ -0,0 +1,62 @@
+use crate::rest;
+use std::ffi::CString;
+
+#[derive(Debug, PartialEq)]
+#[repr(C)]
+pub enum MullvadApiErrorKind {
+ NoError = 0,
+ StringParsing = -1,
+ SocketAddressParsing = -2,
+ AsyncRuntimeInitialization = -3,
+ BadResponse = -4,
+}
+
+/// MullvadApiErrorKind contains a description and an error kind. If the error kind is
+/// `MullvadApiErrorKind` is NoError, the pointer will be nil.
+#[repr(C)]
+pub struct MullvadApiError {
+ description: *mut libc::c_char,
+ kind: MullvadApiErrorKind,
+}
+
+impl MullvadApiError {
+ pub fn new(kind: MullvadApiErrorKind, error: &dyn std::error::Error) -> Self {
+ let description = CString::new(format!("{error:?}")).unwrap_or_default();
+ Self {
+ description: description.into_raw(),
+ kind,
+ }
+ }
+
+ pub fn api_err(error: rest::Error) -> Self {
+ Self::new(MullvadApiErrorKind::BadResponse, &error)
+ }
+
+ pub fn with_str(kind: MullvadApiErrorKind, description: &str) -> Self {
+ let description = CString::new(description).unwrap_or_default();
+ Self {
+ description: description.into_raw(),
+ kind,
+ }
+ }
+
+ pub fn ok() -> MullvadApiError {
+ Self {
+ description: std::ptr::null_mut(),
+ kind: MullvadApiErrorKind::NoError,
+ }
+ }
+
+ pub fn drop(self) {
+ if self.description.is_null() {
+ return;
+ }
+
+ let _ = unsafe { CString::from_raw(self.description) };
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_error_drop(error: MullvadApiError) {
+ error.drop()
+}
diff --git a/mullvad-api/src/ffi/mod.rs b/mullvad-api/src/ffi/mod.rs
new file mode 100644
index 0000000000..ba839f36a5
--- /dev/null
+++ b/mullvad-api/src/ffi/mod.rs
@@ -0,0 +1,428 @@
+use std::{
+ ffi::{CStr, CString},
+ net::SocketAddr,
+ ptr,
+ sync::Arc,
+};
+
+use crate::{
+ rest::{self, MullvadRestHandle},
+ AccountsProxy, DevicesProxy,
+};
+
+mod device;
+mod error;
+
+pub use error::{MullvadApiError, MullvadApiErrorKind};
+
+#[repr(C)]
+pub struct MullvadApiClient {
+ ptr: *const FfiClient,
+}
+
+impl MullvadApiClient {
+ fn new(client: FfiClient) -> Self {
+ let arc = Arc::new(client);
+ let ptr = Arc::into_raw(arc);
+ Self { ptr }
+ }
+
+ unsafe fn get_client(&self) -> Arc<FfiClient> {
+ // Incrementing before creating an Arc from a pointer. This way multiple threads can use
+ // it, and a single thread can decrement it.
+ unsafe { Arc::increment_strong_count(self.ptr) };
+
+ unsafe { Arc::from_raw(self.ptr) }
+ }
+
+ fn drop(self) {
+ if self.ptr.is_null() {
+ return;
+ }
+
+ let _ = unsafe { Arc::from_raw(self.ptr) };
+ }
+}
+
+/// A Mullvad API client that can be used via a C FFI.
+struct FfiClient {
+ tokio_runtime: tokio::runtime::Runtime,
+ api_runtime: crate::Runtime,
+ api_hostname: String,
+}
+
+impl FfiClient {
+ unsafe fn new(
+ api_address_ptr: *const libc::c_char,
+ hostname: *const libc::c_char,
+ ) -> Result<Self, MullvadApiError> {
+ // SAFETY: addr_str must be a valid pointer to a null-terminated string.
+ let addr_str = unsafe { string_from_raw_ptr(api_address_ptr)? };
+ // SAFETY: api_hostname must be a valid pointer to a null-terminated string.
+ let api_hostname = unsafe { string_from_raw_ptr(hostname)? };
+
+ let api_address: SocketAddr = addr_str.parse().map_err(|_| {
+ MullvadApiError::with_str(
+ MullvadApiErrorKind::SocketAddressParsing,
+ "Failed to parse API socket address",
+ )
+ })?;
+
+ let mut runtime_builder = tokio::runtime::Builder::new_multi_thread();
+
+ runtime_builder.worker_threads(2).enable_all();
+ let tokio_runtime = runtime_builder.build().map_err(|err| {
+ MullvadApiError::new(MullvadApiErrorKind::AsyncRuntimeInitialization, &err)
+ })?;
+
+ // 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 context = FfiClient {
+ tokio_runtime,
+ api_runtime,
+ api_hostname,
+ };
+
+ Ok(context)
+ }
+
+ unsafe fn add_device(
+ self: Arc<Self>,
+ account_str_ptr: *const libc::c_char,
+ public_key_ptr: *const u8,
+ ) -> Result<device::MullvadApiDevice, MullvadApiError> {
+ // SAFETY: account_str_ptr must be a valid pointer to a null-terminated string.
+ let account = unsafe { string_from_raw_ptr(account_str_ptr)? };
+
+ // SAFETY: assuming public_key_ptr is valid for 32 bytes
+ let public_key_bytes: [u8; 32] = unsafe { std::ptr::read(public_key_ptr as *const _) };
+ let public_key = public_key_bytes.into();
+
+ let runtime = self.tokio_handle();
+
+ let device_proxy = self.device_proxy();
+
+ let device = runtime
+ .block_on(async move {
+ let (device, _) = device_proxy.create(account, public_key).await?;
+ Ok(device)
+ })
+ .map_err(MullvadApiError::api_err)?;
+
+ Ok(device.into())
+ }
+
+ unsafe fn create_account(self: Arc<Self>) -> Result<String, MullvadApiError> {
+ let accounts_proxy = self.accounts_proxy();
+
+ self.tokio_handle()
+ .block_on(async move {
+ let new_account = accounts_proxy.create_account().await?;
+ Ok(new_account)
+ })
+ .map_err(MullvadApiError::api_err)
+ }
+
+ unsafe fn get_expiry(
+ self: Arc<Self>,
+ account_str_ptr: *const libc::c_char,
+ ) -> Result<i64, MullvadApiError> {
+ // SAFETY: account_str_ptr must be a valid pointer to a null-terminated string.
+ let account = unsafe { string_from_raw_ptr(account_str_ptr)? };
+
+ let account_proxy = self.accounts_proxy();
+ self.tokio_handle()
+ .block_on(async move {
+ let expiry_timestamp = account_proxy.get_data(account).await?.expiry.timestamp();
+ Ok(expiry_timestamp)
+ })
+ .map_err(MullvadApiError::api_err)
+ }
+
+ unsafe fn remove_all_devices(
+ self: Arc<Self>,
+ account_str_ptr: *const libc::c_char,
+ ) -> Result<(), MullvadApiError> {
+ // SAFETY: account_str_ptr must be a valid pointer to a null-terminated string.
+ let account = unsafe { string_from_raw_ptr(account_str_ptr)? };
+
+ let runtime = self.tokio_handle();
+ let device_proxy = self.device_proxy();
+ 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(())
+ })
+ .map_err(MullvadApiError::api_err)
+ }
+
+ unsafe fn list_devices(
+ self: Arc<Self>,
+ account_str_ptr: *const libc::c_char,
+ ) -> Result<device::MullvadApiDeviceIterator, MullvadApiError> {
+ // SAFETY: account_str_ptr must be a valid pointer to a null-terminated string.
+ let account = unsafe { string_from_raw_ptr(account_str_ptr)? };
+
+ let runtime = self.tokio_handle();
+ let device_proxy = self.device_proxy();
+
+ let devices = runtime
+ .block_on(device_proxy.list(account))
+ .map_err(MullvadApiError::api_err)?;
+
+ Ok(device::MullvadApiDeviceIterator::new(devices))
+ }
+
+ unsafe fn delete_account(
+ self: Arc<Self>,
+ account_str_ptr: *const libc::c_char,
+ ) -> Result<(), MullvadApiError> {
+ // SAFETY: account_str_ptr must be a valid pointer to a null-terminated string.
+ let account = unsafe { string_from_raw_ptr(account_str_ptr)? };
+
+ let runtime = self.tokio_handle();
+ let accounts_proxy = self.accounts_proxy();
+
+ runtime
+ .block_on(accounts_proxy.delete_account(account))
+ .map_err(MullvadApiError::api_err)
+ }
+
+ fn rest_handle(&self) -> MullvadRestHandle {
+ self.tokio_runtime.block_on(
+ self.api_runtime
+ .static_mullvad_rest_handle(self.api_hostname.clone()),
+ )
+ }
+
+ fn device_proxy(&self) -> DevicesProxy {
+ crate::DevicesProxy::new(self.rest_handle())
+ }
+
+ fn accounts_proxy(&self) -> AccountsProxy {
+ crate::AccountsProxy::new(self.rest_handle())
+ }
+
+ fn tokio_handle(&self) -> tokio::runtime::Handle {
+ self.tokio_runtime.handle().clone()
+ }
+}
+
+/// Initializes a Mullvad API client.
+///
+/// #Arguments
+/// * `client_ptr`: Must be a pointer to that is valid for the length of a `MullvadApiClient`
+/// struct.
+///
+/// * `api_address`: pointer to nul-terminated UTF-8 string containing a socket address
+/// representation
+/// ("143.32.4.32:9090"), the port is mandatory.
+///
+/// * `hostname`: pointer to a null-terminated UTF-8 string representing the hostname that will be
+/// used for TLS validation.
+#[no_mangle]
+pub extern "C" fn mullvad_api_client_initialize(
+ client_ptr: *mut MullvadApiClient,
+ api_address_ptr: *const libc::c_char,
+ hostname: *const libc::c_char,
+) -> MullvadApiError {
+ match unsafe { FfiClient::new(api_address_ptr, hostname) } {
+ Ok(client) => {
+ unsafe {
+ std::ptr::write(client_ptr, MullvadApiClient::new(client));
+ };
+ MullvadApiError::ok()
+ }
+ Err(err) => err,
+ }
+}
+
+/// Removes all devices from a given account
+///
+/// #Arguments
+/// * `client_ptr`: Must be a valid, initialized instance of `MullvadApiClient`
+///
+/// * `account_str_ptr`: pointer to nul-terminated UTF-8 string containing the account number of the
+/// account that will have all of it's devices removed.
+#[no_mangle]
+pub extern "C" fn mullvad_api_remove_all_devices(
+ client_ptr: MullvadApiClient,
+ account_ptr: *const libc::c_char,
+) -> MullvadApiError {
+ let client = unsafe { client_ptr.get_client() };
+ match unsafe { client.remove_all_devices(account_ptr) } {
+ Ok(_) => MullvadApiError::ok(),
+ Err(err) => err,
+ }
+}
+
+/// Removes all devices from a given account
+///
+/// #Arguments
+/// * `client_ptr`: Must be a valid, initialized instance of `MullvadApiClient`
+///
+/// * `account_str_ptr`: pointer to nul-terminated UTF-8 string containing the account number of the
+/// account that will have all of it's devices removed.
+///
+/// * `expiry_unix_timestamp`: a pointer to a signed 64 bit integer. If this function returns no
+/// error, the expiry timestamp will be written to this pointer.
+#[no_mangle]
+pub extern "C" fn mullvad_api_get_expiry(
+ client_ptr: MullvadApiClient,
+ account_str_ptr: *const libc::c_char,
+ expiry_unix_timestamp: *mut i64,
+) -> MullvadApiError {
+ let client = unsafe { client_ptr.get_client() };
+ match unsafe { client.get_expiry(account_str_ptr) } {
+ Ok(expiry) => {
+ unsafe { ptr::write(expiry_unix_timestamp, expiry) };
+ MullvadApiError::ok()
+ }
+ Err(err) => err,
+ }
+}
+
+/// Gets a list of all devices associated with the specified account from the API.
+///
+/// #Arguments
+/// * `client_ptr`: Must be a valid, initialized instance of `MullvadApiClient`
+///
+/// * `account_str_ptr`: pointer to nul-terminated UTF-8 string containing the account number of the
+/// account that will have all of it's devices removed.
+///
+/// * `device_iter_ptr`: a pointer to a `device::MullvadApiDeviceIterator`. If this function
+/// doesn't return an error, the pointer will be initialized with a valid instance of
+/// `device::MullvadApiDeviceIterator`, which can be used to iterate through the devices.
+#[no_mangle]
+pub extern "C" fn mullvad_api_list_devices(
+ client_ptr: MullvadApiClient,
+ account_str_ptr: *const libc::c_char,
+ device_iter_ptr: *mut device::MullvadApiDeviceIterator,
+) -> MullvadApiError {
+ let client = unsafe { client_ptr.get_client() };
+ match unsafe { client.list_devices(account_str_ptr) } {
+ Ok(iter) => {
+ unsafe { ptr::write(device_iter_ptr, iter) };
+ MullvadApiError::ok()
+ }
+ Err(err) => err,
+ }
+}
+
+/// Adds a device to the specified account with the specified public key. Note that the device
+/// name, associated addresess and UUID are not returned.
+///
+/// #Arguments
+/// * `client_ptr`: Must be a valid, initialized instance of `MullvadApiClient`
+///
+/// * `account_str_ptr`: pointer to nul-terminated UTF-8 string containing the account number of the
+/// account that will have a device added to ita device added to it.
+///
+/// * `public_key_ptr`: a pointer to 32 bytes of a WireGuard public key that will be uploaded.
+///
+/// * `new_device_ptr`: a pointer to enough memory to allocate a `MullvadApiDevice`. If this
+/// function doesn't return an error, it will be initialized.
+#[no_mangle]
+pub extern "C" fn mullvad_api_add_device(
+ client_ptr: MullvadApiClient,
+ account_str_ptr: *const libc::c_char,
+ public_key_ptr: *const u8,
+ new_device_ptr: *mut device::MullvadApiDevice,
+) -> MullvadApiError {
+ // SAFETY: Assuming MullvadApiClient is initialized
+ let client = unsafe { client_ptr.get_client() };
+ // SAFETY: Asuming `new_device_ptr` is valid.
+ match unsafe { client.add_device(account_str_ptr, public_key_ptr) } {
+ Ok(device) => {
+ // SAFETY: Asuming `new_device_ptr` is valid.
+ // SAFETY: Asuming `new_device_ptr` is valid.
+ unsafe { ptr::write(new_device_ptr, device) };
+ MullvadApiError::ok()
+ }
+ Err(err) => err,
+ }
+}
+
+/// Creates a new account.
+///
+/// #Arguments
+/// * `client_ptr`: Must be a valid, initialized instance of `MullvadApiClient`
+///
+/// * `account_str_ptr`: If a new account is created successfully, a pointer to an allocated C
+/// string containing the new
+/// account number will be written to this pointer. It must be freed via
+/// `mullvad_api_cstring_drop`.
+#[no_mangle]
+pub extern "C" fn mullvad_api_create_account(
+ client_ptr: MullvadApiClient,
+ account_str_ptr: *mut *const libc::c_char,
+) -> MullvadApiError {
+ let client = unsafe { client_ptr.get_client() };
+ match unsafe { client.create_account() } {
+ Ok(new_account) => {
+ let Ok(account) = CString::new(new_account) else {
+ return MullvadApiError::with_str(
+ MullvadApiErrorKind::BadResponse,
+ "Account number string c ontained null bytes",
+ );
+ };
+
+ unsafe { ptr::write(account_str_ptr, account.into_raw()) };
+ MullvadApiError::ok()
+ }
+ Err(err) => err,
+ }
+}
+
+/// Deletes the specified account.
+///
+/// #Arguments
+/// * `client_ptr`: Must be a valid, initialized instance of `MullvadApiClient`
+///
+/// * `account_str_ptr`: A null-terminated string representing the account to be deleted.
+#[no_mangle]
+pub extern "C" fn mullvad_api_delete_account(
+ client_ptr: MullvadApiClient,
+ account_str_ptr: *const libc::c_char,
+) -> MullvadApiError {
+ let client = unsafe { client_ptr.get_client() };
+ match unsafe { client.delete_account(account_str_ptr) } {
+ Ok(_) => MullvadApiError::ok(),
+ Err(err) => err,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mullvad_api_client_drop(client: MullvadApiClient) {
+ client.drop()
+}
+
+/// Deallocates a CString returned by the Mullvad API client.
+#[no_mangle]
+pub extern "C" fn mullvad_api_cstring_drop(cstr_ptr: *mut libc::c_char) {
+ let _ = unsafe { CString::from_raw(cstr_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 libc::c_char) -> Result<String, MullvadApiError> {
+ let cstr = unsafe { CStr::from_ptr(ptr) };
+
+ Ok(cstr
+ .to_str()
+ .map_err(|_| {
+ MullvadApiError::with_str(
+ MullvadApiErrorKind::StringParsing,
+ "Failed to parse UTF-8 string",
+ )
+ })?
+ .to_owned())
+}
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs
index 40ab7395cd..b211c20268 100644
--- a/mullvad-api/src/lib.rs
+++ b/mullvad-api/src/lib.rs
@@ -35,6 +35,10 @@ mod access;
mod address_cache;
pub mod device;
mod relay_list;
+
+#[cfg(target_os = "ios")]
+pub mod ffi;
+
pub use address_cache::AddressCache;
pub use device::DevicesProxy;
pub use hyper::StatusCode;
@@ -307,6 +311,17 @@ impl Runtime {
)
}
+ // TODO: gate for ios only
+ pub fn with_static_addr(handle: tokio::runtime::Handle, address: SocketAddr) -> Self {
+ Runtime {
+ handle,
+ address_cache: AddressCache::with_static_addr(address),
+ api_availability: ApiAvailability::new(availability::State::default()),
+ #[cfg(target_os = "android")]
+ socket_bypass_tx,
+ }
+ }
+
fn new_inner(
handle: tokio::runtime::Handle,
#[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>,
@@ -412,6 +427,27 @@ impl Runtime {
)
}
+ /// 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 async fn rest_handle(&self) -> rest::RequestServiceHandle {
self.new_request_service(
@@ -500,6 +536,26 @@ impl AccountsProxy {
}
}
+ #[cfg(target_os = "ios")]
+ pub fn delete_account(
+ &self,
+ account: AccountToken,
+ ) -> impl Future<Output = Result<(), rest::Error>> {
+ let service = self.handle.service.clone();
+ let factory = self.handle.factory.clone();
+
+ async move {
+ let request = factory
+ .delete(&format!("{ACCOUNTS_URL_PREFIX}/accounts/me"))?
+ .account(account.clone())?
+ .header("Mullvad-Account-Number", &account)?
+ .expected_status(&[StatusCode::NO_CONTENT]);
+
+ let _ = service.request(request).await?;
+ Ok(())
+ }
+ }
+
#[cfg(target_os = "android")]
pub fn init_play_purchase(
&mut self,
diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs
index f0838f918d..0560642bb0 100644
--- a/mullvad-api/src/rest.rs
+++ b/mullvad-api/src/rest.rs
@@ -19,6 +19,7 @@ use hyper::{
};
use mullvad_types::account::AccountToken;
use std::{
+ borrow::Cow,
error::Error as StdError,
str::FromStr,
sync::{Arc, Weak},
@@ -440,15 +441,18 @@ struct NewErrorResponse {
#[derive(Clone)]
pub struct RequestFactory {
- hostname: &'static str,
+ hostname: Cow<'static, str>,
token_store: Option<AccessTokenStore>,
default_timeout: Duration,
}
impl RequestFactory {
- pub fn new(hostname: &'static str, token_store: Option<AccessTokenStore>) -> Self {
+ pub fn new(
+ hostname: impl Into<Cow<'static, str>>,
+ token_store: Option<AccessTokenStore>,
+ ) -> Self {
Self {
- hostname,
+ hostname: hostname.into(),
token_store,
default_timeout: DEFAULT_TIMEOUT,
}
@@ -523,7 +527,10 @@ impl RequestFactory {
.uri(uri)
.header(header::USER_AGENT, HeaderValue::from_static(USER_AGENT))
.header(header::ACCEPT, HeaderValue::from_static("application/json"))
- .header(header::HOST, HeaderValue::from_static(self.hostname));
+ .header(
+ header::HOST,
+ HeaderValue::from_str(&self.hostname).map_err(|_| Error::InvalidHeaderError)?,
+ );
let result = request.body(hyper::Body::empty())?;
Ok(result)