summaryrefslogtreecommitdiffhomepage
path: root/ios
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 /ios
parent64862490c2781e4be3b8e77d5c297198fcb63d54 (diff)
parente3ccff9b8313e5a13ce526c5b9cb531bd82f623c (diff)
downloadmullvadvpn-1e42ad237f5fbb68027f0c3aefcf3b35c5b87500.tar.xz
mullvadvpn-1e42ad237f5fbb68027f0c3aefcf3b35c5b87500.zip
Merge branch 'move-api-access-method-test-call-to-mullvad-api-ios-1195'
Diffstat (limited to 'ios')
-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
12 files changed, 173 insertions, 81 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