diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-06-04 11:39:14 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-06-04 11:39:14 +0200 |
| commit | 1e42ad237f5fbb68027f0c3aefcf3b35c5b87500 (patch) | |
| tree | cc871d6c0e360d910ebda7c6f05b1d71ad5d1216 /ios | |
| parent | 64862490c2781e4be3b8e77d5c297198fcb63d54 (diff) | |
| parent | e3ccff9b8313e5a13ce526c5b9cb531bd82f623c (diff) | |
| download | mullvadvpn-1e42ad237f5fbb68027f0c3aefcf3b35c5b87500.tar.xz mullvadvpn-1e42ad237f5fbb68027f0c3aefcf3b35c5b87500.zip | |
Merge branch 'move-api-access-method-test-call-to-mullvad-api-ios-1195'
Diffstat (limited to 'ios')
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 |
