summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2025-06-04 11:39:14 +0200
committerBug Magnet <marco.nikic@mullvad.net>2025-06-04 11:39:14 +0200
commit1e42ad237f5fbb68027f0c3aefcf3b35c5b87500 (patch)
treecc871d6c0e360d910ebda7c6f05b1d71ad5d1216
parent64862490c2781e4be3b8e77d5c297198fcb63d54 (diff)
parente3ccff9b8313e5a13ce526c5b9cb531bd82f623c (diff)
downloadmullvadvpn-1e42ad237f5fbb68027f0c3aefcf3b35c5b87500.tar.xz
mullvadvpn-1e42ad237f5fbb68027f0c3aefcf3b35c5b87500.zip
Merge branch 'move-api-access-method-test-call-to-mullvad-api-ios-1195'
-rw-r--r--ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift18
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift16
-rw-r--r--ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift24
-rw-r--r--ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift6
-rw-r--r--ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift7
-rw-r--r--ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift123
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h26
-rw-r--r--ios/MullvadTypes/PersistentAccessMethod.swift8
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift17
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift5
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift2
-rw-r--r--mullvad-ios/src/api_client/api.rs86
-rw-r--r--mullvad-ios/src/api_client/response.rs41
14 files changed, 288 insertions, 93 deletions
diff --git a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
index fa0bdc0ac7..6d1fcff990 100644
--- a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
+++ b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
@@ -64,17 +64,25 @@ struct APIProxyStub: APIQuerying {
func initStorekitPayment(
accountNumber: String,
- retryStrategy: MullvadREST.REST.RetryStrategy,
- completionHandler: @escaping MullvadREST.ProxyCompletionHandler<String>
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<String>
) -> any MullvadTypes.Cancellable {
AnyCancellable()
}
func checkStorekitPayment(
accountNumber: String,
- transaction: MullvadTypes.StorekitTransaction,
- retryStrategy: MullvadREST.REST.RetryStrategy,
- completionHandler: @escaping MullvadREST.ProxyCompletionHandler<Void>
+ transaction: StorekitTransaction,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<Void>
+ ) -> any MullvadTypes.Cancellable {
+ AnyCancellable()
+ }
+
+ func checkApiAvailability(
+ retryStrategy: REST.RetryStrategy,
+ accessMethod: PersistentAccessMethod,
+ completion: @escaping ProxyCompletionHandler<Bool>
) -> any MullvadTypes.Cancellable {
AnyCancellable()
}
diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
index 8a74f8f6be..943c0afc3d 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
@@ -257,7 +257,7 @@ extension REST {
request: LegacyStorekitRequest,
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping ProxyCompletionHandler<REST.CreateApplePaymentResponse>
- ) -> any MullvadTypes.Cancellable {
+ ) -> any Cancellable {
AnyCancellable()
}
@@ -266,17 +266,25 @@ extension REST {
accountNumber: String,
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping ProxyCompletionHandler<String>
- ) -> any MullvadTypes.Cancellable {
+ ) -> any Cancellable {
AnyCancellable()
}
/// Not implemented. Use `MullvadAPIProxy` instead.
public func checkStorekitPayment(
accountNumber: String,
- transaction: MullvadTypes.StorekitTransaction,
+ transaction: StorekitTransaction,
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping ProxyCompletionHandler<Void>
- ) -> any MullvadTypes.Cancellable {
+ ) -> any Cancellable {
+ AnyCancellable()
+ }
+
+ public func checkApiAvailability(
+ retryStrategy: REST.RetryStrategy,
+ accessMethod: PersistentAccessMethod,
+ completion: @escaping ProxyCompletionHandler<Bool>
+ ) -> any Cancellable {
AnyCancellable()
}
}
diff --git a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift
index 6f270bf607..91f8888f91 100644
--- a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift
+++ b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift
@@ -60,6 +60,12 @@ public protocol APIQuerying: Sendable {
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping @Sendable ProxyCompletionHandler<Void>
) -> Cancellable
+
+ func checkApiAvailability(
+ retryStrategy: REST.RetryStrategy,
+ accessMethod: PersistentAccessMethod,
+ completion: @escaping @Sendable ProxyCompletionHandler<Bool>
+ ) -> Cancellable
}
extension REST {
@@ -159,6 +165,24 @@ extension REST {
})
}
+ public func checkApiAvailability(
+ retryStrategy: REST.RetryStrategy,
+ accessMethod: PersistentAccessMethod,
+ completion: @escaping @Sendable ProxyCompletionHandler<Bool>
+ ) -> Cancellable {
+ let responseHandler = rustEmptyResponseHandler()
+ return createNetworkOperation(
+ request: .checkApiAvailability(retryStrategy, accessMethod: accessMethod),
+ responseHandler: responseHandler
+ ) { result in
+ if case let .failure(err) = result {
+ completion(.failure(err))
+ } else {
+ completion(.success(true))
+ }
+ }
+ }
+
public func legacyStorekitPayment(
accountNumber: String,
request: LegacyStorekitRequest,
diff --git a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
index 5f27695672..e9ea133034 100644
--- a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
+++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
@@ -13,6 +13,7 @@ public enum APIRequest: Codable, Sendable {
case getAddressList(_ retryStrategy: REST.RetryStrategy)
case getRelayList(_ retryStrategy: REST.RetryStrategy, etag: String?)
case sendProblemReport(_ retryStrategy: REST.RetryStrategy, problemReportRequest: ProblemReportRequest)
+ case checkApiAvailability(_ retryStrategy: REST.RetryStrategy, accessMethod: PersistentAccessMethod)
// Account Proxy
case createAccount(_ retryStrategy: REST.RetryStrategy)
@@ -72,6 +73,8 @@ public enum APIRequest: Codable, Sendable {
"init-storekit-payment"
case .checkStorekitPayment:
"check-storekit-payment"
+ case .checkApiAvailability:
+ "check-api-availability"
}
}
@@ -90,7 +93,8 @@ public enum APIRequest: Codable, Sendable {
let .rotateDeviceKey(strategy, _, _, _),
let .legacyStorekitPayment(strategy, _, _),
let .initStorekitPayment(strategy, _),
- let .checkStorekitPayment(strategy, _, _):
+ let .checkStorekitPayment(strategy, _, _),
+ let .checkApiAvailability(strategy, _):
strategy
}
}
diff --git a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
index b7e6c8412f..03d430a3a7 100644
--- a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
+++ b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
@@ -114,6 +114,13 @@ public struct MullvadApiRequestFactory: Sendable {
accountNumber,
request.publicKey.rawValue.map { $0 }
))
+ case let .checkApiAvailability(retryStrategy, accessMethod):
+ return MullvadApiCancellable(handle: mullvad_ios_api_addrs_available(
+ apiContext.context,
+ rawCompletionPointer,
+ retryStrategy.toRustStrategy(),
+ convertAccessMethod(accessMethod: accessMethod)
+ ))
case let .legacyStorekitPayment(
retryStrategy: retryStrategy,
accountNumber: accountNumber,
diff --git a/ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift b/ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift
index f7d2e0238f..0504fb06c8 100644
--- a/ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift
+++ b/ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift
@@ -8,7 +8,6 @@
import MullvadTypes
-// swiftlint:disable:next function_body_length
public func initAccessMethodSettingsWrapper(methods: [PersistentAccessMethod])
-> SwiftAccessMethodSettingsWrapper {
// 1. Get all the built in access methods, it is expected that they are always available
@@ -21,67 +20,15 @@ public func initAccessMethodSettingsWrapper(methods: [PersistentAccessMethod])
let customMethods = methods.filter { defaultMethods.contains($0.proxyConfiguration) == false }
// 3. Convert the builtin access methods
- let directMethodRaw = convert_builtin_access_method_setting(
- directMethod.id.uuidString,
- directMethod.name,
- directMethod.isEnabled,
- UInt8(KindDirect.rawValue),
- nil
- )
- let bridgesMethodRaw = convert_builtin_access_method_setting(
- bridgesMethod.id.uuidString,
- bridgesMethod.name,
- bridgesMethod.isEnabled,
- UInt8(KindBridge.rawValue),
- nil
- )
- let encryptedDNSMethodRaw = convert_builtin_access_method_setting(
- encryptedDNSMethod.id.uuidString,
- encryptedDNSMethod.name,
- encryptedDNSMethod.isEnabled,
- UInt8(KindEncryptedDnsProxy.rawValue),
- nil
- )
+ let directMethodRaw = convertAccessMethod(accessMethod: directMethod)
+ let bridgesMethodRaw = convertAccessMethod(accessMethod: bridgesMethod)
+ let encryptedDNSMethodRaw = convertAccessMethod(accessMethod: encryptedDNSMethod)
var rawCustomMethods = ContiguousArray<UnsafeRawPointer?>([])
// 4. Convert the custom access methods (all takes different parameters)
for method in customMethods {
- if case let .shadowsocks(config) = method.proxyConfiguration {
- let serverAddress = config.server.rawValue.map { $0 }
- let shadowsocksConfiguration = new_shadowsocks_access_method_setting(
- serverAddress,
- UInt(serverAddress.count),
- config.port,
- config.password,
- config.cipher.rawValue.rawValue
- )
- let shadowsocksMethodRaw = convert_builtin_access_method_setting(
- method.id.uuidString,
- method.name,
- method.isEnabled,
- UInt8(KindShadowsocks.rawValue),
- shadowsocksConfiguration
- )
- rawCustomMethods.append(shadowsocksMethodRaw)
- }
- if case let .socks5(config) = method.proxyConfiguration {
- let serverAddress = config.server.rawValue.map { $0 }
- let socks5Configuration = new_socks5_access_method_setting(
- serverAddress,
- UInt(serverAddress.count),
- config.port,
- config.credential?.username,
- config.credential?.password
- )
- let socks5MethodRaw = convert_builtin_access_method_setting(
- method.id.uuidString,
- method.name,
- method.isEnabled,
- UInt8(KindSocks5Local.rawValue),
- socks5Configuration
- )
- rawCustomMethods.append(socks5MethodRaw)
- }
+ let rawMethod = convertAccessMethod(accessMethod: method)
+ rawCustomMethods.append(rawMethod)
}
// 5. Reunite them all in one, and pass it to rust
@@ -97,3 +44,63 @@ public func initAccessMethodSettingsWrapper(methods: [PersistentAccessMethod])
}
)
}
+
+public func convertAccessMethod(accessMethod: PersistentAccessMethod) -> UnsafeMutableRawPointer? {
+ switch accessMethod.proxyConfiguration {
+ case .direct, .bridges, .encryptedDNS:
+ return convert_builtin_access_method_setting(
+ accessMethod.id.uuidString,
+ accessMethod.name,
+ accessMethod.isEnabled,
+ accessMethod.kind(),
+ nil
+ )
+ case let .shadowsocks(configuration):
+ let serverAddress = configuration.server.rawValue.map { $0 }
+ let shadowsocksConfiguration = new_shadowsocks_access_method_setting(
+ serverAddress,
+ UInt(serverAddress.count),
+ configuration.port,
+ configuration.password,
+ configuration.cipher.rawValue.rawValue
+ )
+ let shadowsocksMethodRaw = convert_builtin_access_method_setting(
+ accessMethod.id.uuidString,
+ accessMethod.name,
+ accessMethod.isEnabled,
+ accessMethod.kind(),
+ shadowsocksConfiguration
+ )
+ return shadowsocksMethodRaw
+ case let .socks5(configuration):
+ let serverAddress = configuration.server.rawValue.map { $0 }
+ let socks5Configuration = new_socks5_access_method_setting(
+ serverAddress,
+ UInt(serverAddress.count),
+ configuration.port,
+ configuration.credential?.username,
+ configuration.credential?.password
+ )
+ let socks5MethodRaw = convert_builtin_access_method_setting(
+ accessMethod.id.uuidString,
+ accessMethod.name,
+ accessMethod.isEnabled,
+ accessMethod.kind(),
+ socks5Configuration
+ )
+ return socks5MethodRaw
+ }
+}
+
+fileprivate
+extension PersistentAccessMethod {
+ func kind() -> UInt8 {
+ switch kind {
+ case .direct: UInt8(KindDirect.rawValue)
+ case .bridges: UInt8(KindBridge.rawValue)
+ case .encryptedDNS: UInt8(KindEncryptedDnsProxy.rawValue)
+ case .shadowsocks: UInt8(KindShadowsocks.rawValue)
+ case .socks5: UInt8(KindSocks5Local.rawValue)
+ }
+ }
+}
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index 08bb177116..d4c0bf334f 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -89,10 +89,10 @@ typedef struct LateStringDeallocator {
typedef struct SwiftMullvadApiResponse {
uint8_t *body;
uintptr_t body_size;
- uint8_t *etag;
+ char *etag;
uint16_t status_code;
- uint8_t *error_description;
- uint8_t *server_response_code;
+ char *error_description;
+ char *server_response_code;
bool success;
} SwiftMullvadApiResponse;
@@ -366,6 +366,26 @@ struct SwiftCancelHandle mullvad_ios_get_addresses(struct SwiftApiContext api_co
* object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
* when completion finishes (in completion.finish).
*
+ * `retry_strategy` must have been created by a call to either of the following functions
+ * `mullvad_api_retry_strategy_never`, `mullvad_api_retry_strategy_constant` or `mullvad_api_retry_strategy_exponential`
+ *
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_ios_api_addrs_available(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ const void *access_method_setting);
+
+/**
+ * # Safety
+ *
+ * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+ * by calling `mullvad_api_init_new`.
+ *
+ * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+ * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+ * when completion finishes (in completion.finish).
+ *
* `etag` must be a pointer to a null terminated string.
*
* `retry_strategy` must have been created by a call to either of the following functions
diff --git a/ios/MullvadTypes/PersistentAccessMethod.swift b/ios/MullvadTypes/PersistentAccessMethod.swift
index 8b9d6d57ae..9a3310d06f 100644
--- a/ios/MullvadTypes/PersistentAccessMethod.swift
+++ b/ios/MullvadTypes/PersistentAccessMethod.swift
@@ -24,7 +24,7 @@ public struct PersistentAccessMethodStore: Codable {
}
/// Persistent access method model.
-public struct PersistentAccessMethod: Identifiable, Codable, Equatable {
+public struct PersistentAccessMethod: Identifiable, Codable, Equatable, Sendable {
/// The unique identifier used for referencing the access method entry in a persistent store.
public var id: UUID
@@ -63,7 +63,7 @@ public struct PersistentAccessMethod: Identifiable, Codable, Equatable {
}
/// Persistent proxy configuration.
-public enum PersistentProxyConfiguration: Codable, Equatable {
+public enum PersistentProxyConfiguration: Codable, Equatable, Sendable {
/// Direct communication without proxy.
case direct
@@ -98,7 +98,7 @@ extension PersistentProxyConfiguration {
}
/// Socks v5 proxy configuration.
- public struct SocksConfiguration: Codable, Equatable {
+ public struct SocksConfiguration: Codable, Equatable, Sendable {
/// Proxy server address.
public var server: AnyIPAddress
@@ -132,7 +132,7 @@ extension PersistentProxyConfiguration {
}
/// Shadowsocks configuration.
- public struct ShadowsocksConfiguration: Codable, Equatable {
+ public struct ShadowsocksConfiguration: Codable, Equatable, Sendable {
/// Server address.
public var server: AnyIPAddress
diff --git a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
index de0a0b2551..8e194bd74b 100644
--- a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
+++ b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
@@ -17,14 +17,24 @@ class ProxyConfigurationTester: ProxyConfigurationTesterProtocol {
private var cancellable: MullvadTypes.Cancellable?
private let transportProvider: ProxyConfigurationTransportProvider
private var headRequest: REST.APIAvailabilityTestRequest?
+ private let apiProxy: APIQuerying
- init(transportProvider: ProxyConfigurationTransportProvider) {
+ init(transportProvider: ProxyConfigurationTransportProvider, apiProxy: APIQuerying) {
self.transportProvider = transportProvider
+ self.apiProxy = apiProxy
}
- func start(configuration: PersistentProxyConfiguration, completion: @escaping @Sendable (Error?) -> Void) {
+ func start(configuration: PersistentAccessMethod, completion: @escaping @Sendable (Error?) -> Void) {
+ #if DEBUG
+ cancellable = apiProxy.checkApiAvailability(retryStrategy: .noRetry, accessMethod: configuration) { success in
+ switch success {
+ case .success: completion(nil)
+ case let .failure(error): completion(error)
+ }
+ }
+ #else
do {
- let transport = try transportProvider.makeTransport(with: configuration)
+ let transport = try transportProvider.makeTransport(with: configuration.proxyConfiguration)
let request = REST.APIAvailabilityTestRequest(transport: transport)
headRequest = request
cancellable = request.makeRequest { error in
@@ -35,6 +45,7 @@ class ProxyConfigurationTester: ProxyConfigurationTesterProtocol {
} catch {
completion(error)
}
+ #endif
}
func cancel() {
diff --git a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
index 6ebc7aa655..66b31f9193 100644
--- a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
+++ b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
@@ -15,7 +15,7 @@ protocol ProxyConfigurationTesterProtocol {
/// - Parameters:
/// - configuration: a proxy configuration.
/// - completion: a completion handler that receives `nil` upon success, otherwise the underlying error.
- func start(configuration: PersistentProxyConfiguration, completion: @escaping @Sendable (Error?) -> Void)
+ func start(configuration: PersistentAccessMethod, completion: @escaping @Sendable (Error?) -> Void)
/// Cancel testing proxy configuration.
func cancel()
diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
index 7aa56ed588..87a2cbf24e 100644
--- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
@@ -570,7 +570,10 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo
let navigationController = CustomNavigationController()
navigationController.view.setAccessibilityIdentifier(.settingsContainerView)
- let configurationTester = ProxyConfigurationTester(transportProvider: configuredTransportProvider)
+ let configurationTester = ProxyConfigurationTester(
+ transportProvider: configuredTransportProvider,
+ apiProxy: apiProxy
+ )
let coordinator = SettingsCoordinator(
navigationController: navigationController,
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift
index 7770c3b2e1..ac7a9125ff 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodInteractor.swift
@@ -52,7 +52,7 @@ struct EditAccessMethodInteractor: EditAccessMethodInteractorProtocol {
}
func startProxyConfigurationTest(_ completion: (@Sendable (Bool) -> Void)?) {
- guard let config = try? subject.value.intoPersistentProxyConfiguration() else { return }
+ guard let config = try? subject.value.intoPersistentAccessMethod() else { return }
let subject = subject
subject.value.testingStatus = .inProgress
diff --git a/mullvad-ios/src/api_client/api.rs b/mullvad-ios/src/api_client/api.rs
index 6578be1d14..14c36bc595 100644
--- a/mullvad-ios/src/api_client/api.rs
+++ b/mullvad-ios/src/api_client/api.rs
@@ -1,16 +1,18 @@
-use std::ffi::CStr;
+use std::ffi::{c_void, CStr};
use std::os::raw::c_char;
use mullvad_api::{
rest::{self, MullvadRestHandle},
ApiProxy, RelayListProxy,
};
+use mullvad_types::access_method::AccessMethodSetting;
use super::{
cancellation::{RequestCancelHandle, SwiftCancelHandle},
completion::{CompletionCookie, SwiftCompletionHandler},
do_request,
response::SwiftMullvadApiResponse,
+ retry_request,
retry_strategy::{RetryStrategy, SwiftRetryStrategy},
SwiftApiContext,
};
@@ -68,6 +70,78 @@ pub unsafe extern "C" fn mullvad_ios_get_addresses(
/// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
/// when completion finishes (in completion.finish).
///
+/// `retry_strategy` must have been created by a call to either of the following functions
+/// `mullvad_api_retry_strategy_never`, `mullvad_api_retry_strategy_constant` or `mullvad_api_retry_strategy_exponential`
+///
+/// This function is not safe to call multiple times with the same `CompletionCookie`.
+#[no_mangle]
+pub unsafe extern "C" fn mullvad_ios_api_addrs_available(
+ api_context: SwiftApiContext,
+ completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
+ access_method_setting: *const c_void,
+) -> SwiftCancelHandle {
+ let completion_handler = SwiftCompletionHandler::new(CompletionCookie::new(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.rust_context();
+ // SAFETY: See notes for `into_rust`
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
+ let completion = completion_handler.clone();
+ // SAFETY: `access_method_setting` must be a raw pointer resulting from a call to `convert_builtin_access_method_setting`
+ let access_method_setting: AccessMethodSetting =
+ unsafe { *Box::from_raw(access_method_setting as *mut _) };
+
+ let task = tokio_handle.clone().spawn(async move {
+ match api_context
+ .access_mode_handler
+ .resolve(access_method_setting.clone())
+ .await
+ {
+ Ok(Some(resolved_connection_mode)) => {
+ let oneshot_client = api_context
+ .api_client
+ .mullvad_rest_handle(resolved_connection_mode.connection_mode.into_provider());
+
+ match mullvad_ios_api_addrs_available_inner(oneshot_client, retry_strategy).await {
+ Ok(_) => completion.finish(SwiftMullvadApiResponse::ok()),
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::rest_error(err));
+ }
+ }
+ }
+ Ok(None) => {
+ log::error!("Invalid access method configuration, {access_method_setting:?}");
+ completion.finish(SwiftMullvadApiResponse::access_method_error(
+ mullvad_api::access_mode::Error::Resolve {
+ access_method: access_method_setting.access_method,
+ },
+ ));
+ }
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::access_method_error(err));
+ }
+ }
+ });
+
+ RequestCancelHandle::new(task, completion_handler.clone()).into_swift()
+}
+
+/// # Safety
+///
+/// `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+/// by calling `mullvad_api_init_new`.
+///
+/// This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+/// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+/// when completion finishes (in completion.finish).
+///
/// `etag` must be a pointer to a null terminated string.
///
/// `retry_strategy` must have been created by a call to either of the following functions
@@ -137,3 +211,13 @@ async fn mullvad_ios_get_relays_inner(
do_request(retry_strategy, future_factory).await
}
+
+async fn mullvad_ios_api_addrs_available_inner(
+ rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
+) -> Result<bool, rest::Error> {
+ let api = ApiProxy::new(rest_client);
+
+ let future_factory = || api.api_addrs_available();
+ retry_request(retry_strategy, future_factory).await
+}
diff --git a/mullvad-ios/src/api_client/response.rs b/mullvad-ios/src/api_client/response.rs
index 3b017e37c6..4871a5d4bf 100644
--- a/mullvad-ios/src/api_client/response.rs
+++ b/mullvad-ios/src/api_client/response.rs
@@ -1,5 +1,5 @@
use std::{
- ffi::CString,
+ ffi::{c_char, CString},
ptr::{self, null_mut},
};
@@ -12,10 +12,10 @@ use mullvad_api::{
pub struct SwiftMullvadApiResponse {
body: *mut u8,
body_size: usize,
- etag: *mut u8,
+ etag: *mut c_char,
status_code: u16,
- error_description: *mut u8,
- server_response_code: *mut u8,
+ error_description: *mut c_char,
+ server_response_code: *mut c_char,
success: bool,
}
@@ -33,7 +33,7 @@ impl SwiftMullvadApiResponse {
Some(etag) => {
let header_value =
CString::new(etag).map_err(|_| rest::Error::InvalidHeaderError)?;
- header_value.into_raw().cast()
+ header_value.into_raw()
}
None => ptr::null_mut(),
};
@@ -61,6 +61,25 @@ impl SwiftMullvadApiResponse {
}
}
+ pub fn access_method_error(err: mullvad_api::access_mode::Error) -> Self {
+ let to_cstr_pointer = |str| {
+ CString::new(str)
+ .map(|cstr| cstr.into_raw())
+ .unwrap_or(null_mut())
+ };
+ let error_description = to_cstr_pointer(err.to_string());
+
+ Self {
+ body: null_mut(),
+ body_size: 0,
+ etag: null_mut(),
+ status_code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
+ error_description,
+ server_response_code: null_mut(),
+ success: false,
+ }
+ }
+
pub fn rest_error(err: mullvad_api::rest::Error) -> Self {
if err.is_aborted() {
return Self::cancelled();
@@ -68,7 +87,7 @@ impl SwiftMullvadApiResponse {
let to_cstr_pointer = |str| {
CString::new(str)
- .map(|cstr| cstr.into_raw().cast())
+ .map(|cstr| cstr.into_raw())
.unwrap_or(null_mut())
};
@@ -94,7 +113,7 @@ impl SwiftMullvadApiResponse {
pub fn cancelled() -> Self {
Self {
success: false,
- error_description: c"Request was cancelled".to_owned().into_raw().cast(),
+ error_description: c"Request was cancelled".to_owned().into_raw(),
body: null_mut(),
body_size: 0,
etag: null_mut(),
@@ -106,7 +125,7 @@ impl SwiftMullvadApiResponse {
pub fn no_tokio_runtime() -> Self {
Self {
success: false,
- error_description: c"Failed to get Tokio runtime".to_owned().into_raw().cast(),
+ error_description: c"Failed to get Tokio runtime".to_owned().into_raw(),
body: null_mut(),
body_size: 0,
etag: null_mut(),
@@ -130,14 +149,14 @@ pub unsafe extern "C" fn mullvad_response_drop(response: SwiftMullvadApiResponse
}
if !response.etag.is_null() {
- let _ = CString::from_raw(response.etag.cast());
+ let _ = CString::from_raw(response.etag);
}
if !response.error_description.is_null() {
- let _ = CString::from_raw(response.error_description.cast());
+ let _ = CString::from_raw(response.error_description);
}
if !response.server_response_code.is_null() {
- let _ = CString::from_raw(response.server_response_code.cast());
+ let _ = CString::from_raw(response.server_response_code);
}
}