summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-02-21 15:08:46 +0100
committerJon Petersson <jon.petersson@mullvad.net>2025-02-21 15:08:46 +0100
commitcd92d028a9a72a2870cefed3c16958765538bbdc (patch)
tree13ed3e3895269a8bb6980b5d6eab3238f1e09252 /ios
parent9a8535ef787d784d2d75dbd1474e1a1846413e83 (diff)
parent041bf3f20f3cb0ed6703065eee4d09b5f938f730 (diff)
downloadmullvadvpn-cd92d028a9a72a2870cefed3c16958765538bbdc.tar.xz
mullvadvpn-cd92d028a9a72a2870cefed3c16958765538bbdc.zip
Merge branch 'urlsession'prepare-2025.5-beta1
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift7
-rw-r--r--ios/MullvadMockData/MullvadREST/MockProxyFactory.swift10
-rw-r--r--ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift37
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift33
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTDefaults.swift8
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTProxy.swift15
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift14
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift57
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift168
-rw-r--r--ios/MullvadRESTTests/RequestExecutorTests.swift3
-rw-r--r--ios/MullvadRustRuntime/MullvadApiCancellable.swift23
-rw-r--r--ios/MullvadRustRuntime/MullvadApiCompletion.swift28
-rw-r--r--ios/MullvadRustRuntime/MullvadApiContext.swift27
-rw-r--r--ios/MullvadRustRuntime/MullvadApiResponse.swift55
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h107
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj50
-rw-r--r--ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift1
-rw-r--r--ios/MullvadVPN/AppDelegate.swift6
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift3
19 files changed, 623 insertions, 29 deletions
diff --git a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
index 1330be345f..3c8350b8fb 100644
--- a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
+++ b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
@@ -12,6 +12,13 @@ import MullvadTypes
import WireGuardKitTypes
struct APIProxyStub: APIQuerying {
+ func mullvadApiGetAddressList(
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
func getAddressList(
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]>
diff --git a/ios/MullvadMockData/MullvadREST/MockProxyFactory.swift b/ios/MullvadMockData/MullvadREST/MockProxyFactory.swift
index ad8192963b..f6a101808c 100644
--- a/ios/MullvadMockData/MullvadREST/MockProxyFactory.swift
+++ b/ios/MullvadMockData/MullvadREST/MockProxyFactory.swift
@@ -8,6 +8,7 @@
import Foundation
import MullvadREST
+import MullvadRustRuntime
import MullvadTypes
import WireGuardKitTypes
@@ -28,11 +29,13 @@ public struct MockProxyFactory: ProxyFactoryProtocol {
public static func makeProxyFactory(
transportProvider: any RESTTransportProvider,
- addressCache: REST.AddressCache
+ addressCache: REST.AddressCache,
+ apiContext: MullvadApiContext
) -> any ProxyFactoryProtocol {
let basicConfiguration = REST.ProxyConfiguration(
transportProvider: transportProvider,
- addressCacheStore: addressCache
+ addressCacheStore: addressCache,
+ apiContext: apiContext
)
let authenticationProxy = REST.AuthenticationProxy(
@@ -44,7 +47,8 @@ public struct MockProxyFactory: ProxyFactoryProtocol {
let authConfiguration = REST.AuthProxyConfiguration(
proxyConfiguration: basicConfiguration,
- accessTokenManager: accessTokenManager
+ accessTokenManager: accessTokenManager,
+ apiContext: apiContext
)
return MockProxyFactory(configuration: authConfiguration)
diff --git a/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift b/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift
new file mode 100644
index 0000000000..89bf2dd725
--- /dev/null
+++ b/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift
@@ -0,0 +1,37 @@
+//
+// MullvadApiRequestFactory.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-02-07.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadRustRuntime
+import MullvadTypes
+
+enum MullvadApiRequest {
+ case getAddressList
+}
+
+struct MullvadApiRequestFactory {
+ let apiContext: MullvadApiContext
+
+ func makeRequest(_ request: MullvadApiRequest) -> REST.MullvadApiRequestHandler {
+ { completion in
+ let pointerClass = MullvadApiCompletion { apiResponse in
+ try? completion?(apiResponse)
+ }
+
+ let rawPointer = Unmanaged.passRetained(pointerClass).toOpaque()
+
+ return switch request {
+ case .getAddressList:
+ MullvadApiCancellable(handle: mullvad_api_get_addresses(apiContext.context, rawPointer))
+ }
+ }
+ }
+}
+
+extension REST {
+ typealias MullvadApiRequestHandler = (((MullvadApiResponse) throws -> Void)?) -> MullvadApiCancellable
+}
diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
index 5ac58668c0..4c8e144d6d 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
@@ -7,10 +7,17 @@
//
import Foundation
+import MullvadRustRuntime
import MullvadTypes
+import Operations
import WireGuardKitTypes
public protocol APIQuerying: Sendable {
+ func mullvadApiGetAddressList(
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
+ ) -> Cancellable
+
func getAddressList(
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
@@ -55,6 +62,32 @@ extension REST {
)
}
+ public func mullvadApiGetAddressList(
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
+ ) -> Cancellable {
+ let requestHandler = mullvadApiRequestFactory.makeRequest(.getAddressList)
+
+ let responseHandler = rustResponseHandler(
+ decoding: [AnyIPEndpoint].self,
+ with: responseDecoder
+ )
+
+ let networkOperation = MullvadApiNetworkOperation(
+ name: "get-api-addrs",
+ dispatchQueue: dispatchQueue,
+ retryStrategy: retryStrategy,
+ requestHandler: requestHandler,
+ responseDecoder: responseDecoder,
+ responseHandler: responseHandler,
+ completionHandler: completionHandler
+ )
+
+ operationQueue.addOperation(networkOperation)
+
+ return networkOperation
+ }
+
public func getAddressList(
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
diff --git a/ios/MullvadREST/ApiHandlers/RESTDefaults.swift b/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
index 250401b019..d115abc37a 100644
--- a/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadRustRuntime
import MullvadTypes
// swiftlint:disable force_cast
@@ -28,6 +29,13 @@ extension REST {
/// Default network timeout for API requests.
public static let defaultAPINetworkTimeout: Duration = .seconds(10)
+
+ /// API context used for API requests via Rust runtime.
+ // swiftlint:disable:next force_try
+ public static let apiContext = try! MullvadApiContext(
+ host: defaultAPIHostname,
+ address: defaultAPIEndpoint
+ )
}
// swiftlint:enable force_cast
diff --git a/ios/MullvadREST/ApiHandlers/RESTProxy.swift b/ios/MullvadREST/ApiHandlers/RESTProxy.swift
index 0da8ab546c..75d1e31fd8 100644
--- a/ios/MullvadREST/ApiHandlers/RESTProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTProxy.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadRustRuntime
import MullvadTypes
import Operations
@@ -26,6 +27,8 @@ extension REST {
/// URL request factory.
let requestFactory: REST.RequestFactory
+ let mullvadApiRequestFactory: MullvadApiRequestFactory
+
/// URL response decoder.
let responseDecoder: JSONDecoder
@@ -40,6 +43,7 @@ extension REST {
self.configuration = configuration
self.requestFactory = requestFactory
+ self.mullvadApiRequestFactory = MullvadApiRequestFactory(apiContext: configuration.apiContext)
self.responseDecoder = responseDecoder
}
@@ -132,13 +136,16 @@ extension REST {
public class ProxyConfiguration: @unchecked Sendable {
public let transportProvider: RESTTransportProvider
public let addressCacheStore: AddressCache
+ public let apiContext: MullvadApiContext
public init(
transportProvider: RESTTransportProvider,
- addressCacheStore: AddressCache
+ addressCacheStore: AddressCache,
+ apiContext: MullvadApiContext
) {
self.transportProvider = transportProvider
self.addressCacheStore = addressCacheStore
+ self.apiContext = apiContext
}
}
@@ -147,13 +154,15 @@ extension REST {
public init(
proxyConfiguration: ProxyConfiguration,
- accessTokenManager: RESTAccessTokenManagement
+ accessTokenManager: RESTAccessTokenManagement,
+ apiContext: MullvadApiContext
) {
self.accessTokenManager = accessTokenManager
super.init(
transportProvider: proxyConfiguration.transportProvider,
- addressCacheStore: proxyConfiguration.addressCacheStore
+ addressCacheStore: proxyConfiguration.addressCacheStore,
+ apiContext: apiContext
)
}
}
diff --git a/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift b/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
index 331fb49030..46acaa94bf 100644
--- a/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
@@ -7,6 +7,8 @@
//
import Foundation
+import MullvadRustRuntime
+
public protocol ProxyFactoryProtocol {
var configuration: REST.AuthProxyConfiguration { get }
@@ -16,7 +18,8 @@ public protocol ProxyFactoryProtocol {
static func makeProxyFactory(
transportProvider: RESTTransportProvider,
- addressCache: REST.AddressCache
+ addressCache: REST.AddressCache,
+ apiContext: MullvadApiContext
) -> ProxyFactoryProtocol
}
@@ -26,11 +29,13 @@ extension REST {
public static func makeProxyFactory(
transportProvider: any RESTTransportProvider,
- addressCache: REST.AddressCache
+ addressCache: REST.AddressCache,
+ apiContext: MullvadApiContext
) -> any ProxyFactoryProtocol {
let basicConfiguration = REST.ProxyConfiguration(
transportProvider: transportProvider,
- addressCacheStore: addressCache
+ addressCacheStore: addressCache,
+ apiContext: apiContext
)
let authenticationProxy = REST.AuthenticationProxy(
@@ -42,7 +47,8 @@ extension REST {
let authConfiguration = REST.AuthProxyConfiguration(
proxyConfiguration: basicConfiguration,
- accessTokenManager: accessTokenManager
+ accessTokenManager: accessTokenManager,
+ apiContext: apiContext
)
return ProxyFactory(configuration: authConfiguration)
diff --git a/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift b/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift
index 9790514507..1b6d7f950b 100644
--- a/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadRustRuntime
import MullvadTypes
protocol RESTResponseHandler<Success> {
@@ -15,7 +16,14 @@ protocol RESTResponseHandler<Success> {
func handleURLResponse(_ response: HTTPURLResponse, data: Data) -> REST.ResponseHandlerResult<Success>
}
+protocol RESTRustResponseHandler<Success> {
+ associatedtype Success
+
+ func handleResponse(_ response: MullvadApiResponse) -> REST.ResponseHandlerResult<Success>
+}
+
extension REST {
+ // TODO: We could probably remove the `decoding` case when network requests are fully merged to Mullvad API.
/// Responser handler result type.
enum ResponseHandlerResult<Success> {
/// Response handler succeeded and produced a value.
@@ -66,4 +74,53 @@ extension REST {
}
}
}
+
+ final class RustResponseHandler<Success>: RESTRustResponseHandler {
+ typealias HandlerBlock = (MullvadApiResponse) -> REST.ResponseHandlerResult<Success>
+
+ private let handlerBlock: HandlerBlock
+
+ init(_ block: @escaping HandlerBlock) {
+ handlerBlock = block
+ }
+
+ func handleResponse(_ response: MullvadApiResponse) -> REST.ResponseHandlerResult<Success> {
+ handlerBlock(response)
+ }
+ }
+
+ /// Returns default response handler that parses JSON response into the
+ /// given `Decodable` type if possible, otherwise attempts to decode
+ /// the server error.
+ static func rustResponseHandler<T: Decodable>(
+ decoding type: T.Type,
+ with decoder: JSONDecoder
+ ) -> RustResponseHandler<T> {
+ RustResponseHandler { response in
+ guard let body = response.body else {
+ return .unhandledResponse(nil)
+ }
+
+ do {
+ let decoded = try decoder.decode(type, from: body)
+ return .decoding { decoded }
+ } catch {
+ return .unhandledResponse(
+ try? decoder.decode(
+ ServerErrorResponse.self,
+ from: body
+ )
+ )
+ }
+ }
+ }
+
+ /// Returns default response handler that parses JSON response into the
+ /// given `Decodable` type if possible, otherwise attempts to decode
+ /// the server error.
+ static func rustEmptyResponseHandler() -> RustResponseHandler<Void> {
+ RustResponseHandler { _ in
+ .success(())
+ }
+ }
}
diff --git a/ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift b/ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift
new file mode 100644
index 0000000000..1bcd218444
--- /dev/null
+++ b/ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift
@@ -0,0 +1,168 @@
+//
+// RESTRustNetworkOperation.swift
+// MullvadREST
+//
+// Created by Jon Petersson on 2025-01-29.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadLogging
+import MullvadRustRuntime
+import MullvadTypes
+import Operations
+
+extension REST {
+ class MullvadApiNetworkOperation<Success: Sendable>: ResultOperation<Success>, @unchecked Sendable {
+ private let logger: Logger
+
+ private let requestHandler: MullvadApiRequestHandler
+ private var responseDecoder: JSONDecoder
+ private let responseHandler: any RESTRustResponseHandler<Success>
+ private var networkTask: MullvadApiCancellable?
+
+ private let retryStrategy: RetryStrategy
+ private var retryDelayIterator: AnyIterator<Duration>
+ private var retryTimer: DispatchSourceTimer?
+ private var retryCount = 0
+
+ init(
+ name: String,
+ dispatchQueue: DispatchQueue,
+ retryStrategy: RetryStrategy,
+ requestHandler: @escaping MullvadApiRequestHandler,
+ responseDecoder: JSONDecoder,
+ responseHandler: some RESTRustResponseHandler<Success>,
+ completionHandler: CompletionHandler? = nil
+ ) {
+ self.retryStrategy = retryStrategy
+ retryDelayIterator = retryStrategy.makeDelayIterator()
+ self.responseDecoder = responseDecoder
+ self.requestHandler = requestHandler
+ self.responseHandler = responseHandler
+
+ var logger = Logger(label: "REST.RustNetworkOperation")
+ logger[metadataKey: "name"] = .string(name)
+ self.logger = logger
+
+ super.init(
+ dispatchQueue: dispatchQueue,
+ completionQueue: .main,
+ completionHandler: completionHandler
+ )
+ }
+
+ override public func operationDidCancel() {
+ retryTimer?.cancel()
+ networkTask?.cancel()
+
+ retryTimer = nil
+ networkTask = nil
+ }
+
+ override public func main() {
+ startRequest()
+ }
+
+ func startRequest() {
+ dispatchPrecondition(condition: .onQueue(dispatchQueue))
+
+ guard !isCancelled else {
+ finish(result: .failure(OperationError.cancelled))
+ return
+ }
+
+ networkTask = requestHandler { [weak self] response in
+ guard let self else { return }
+
+ if let error = response.restError() {
+ if response.shouldRetry {
+ retryRequest(with: error)
+ } else {
+ finish(result: .failure(error))
+ }
+
+ return
+ }
+
+ let decodedResponse = responseHandler.handleResponse(response)
+
+ switch decodedResponse {
+ case let .success(value):
+ finish(result: .success(value))
+ case let .decoding(block):
+ finish(result: .success(try block()))
+ case let .unhandledResponse(error):
+ finish(result: .failure(REST.Error.unhandledResponse(Int(response.statusCode), error)))
+ }
+ }
+ }
+
+ private func retryRequest(with error: REST.Error) {
+ // Check if retry count is not exceeded.
+ guard retryCount < retryStrategy.maxRetryCount else {
+ if retryStrategy.maxRetryCount > 0 {
+ logger.debug("Ran out of retry attempts (\(retryStrategy.maxRetryCount))")
+ }
+ finish(result: .failure(error))
+ return
+ }
+
+ // Increment retry count.
+ retryCount += 1
+
+ // Retry immediately if retry delay is set to never.
+ guard retryStrategy.delay != .never else {
+ startRequest()
+ return
+ }
+
+ guard let waitDelay = retryDelayIterator.next() else {
+ logger.debug("Retry delay iterator failed to produce next value.")
+
+ finish(result: .failure(error))
+ return
+ }
+
+ logger.debug("Retry in \(waitDelay.logFormat()).")
+
+ // Create timer to delay retry.
+ let timer = DispatchSource.makeTimerSource(queue: dispatchQueue)
+
+ timer.setEventHandler { [weak self] in
+ self?.startRequest()
+ }
+
+ timer.setCancelHandler { [weak self] in
+ self?.finish(result: .failure(OperationError.cancelled))
+ }
+
+ timer.schedule(wallDeadline: .now() + waitDelay.timeInterval)
+ timer.activate()
+
+ retryTimer = timer
+ }
+ }
+}
+
+extension MullvadApiResponse {
+ public func restError() -> REST.Error? {
+ guard !success else {
+ return nil
+ }
+
+ guard let serverResponseCode else {
+ return .transport(MullvadApiTransportError.connectionFailed(description: errorDescription))
+ }
+
+ let response = REST.ServerErrorResponse(
+ code: REST.ServerResponseCode(rawValue: serverResponseCode),
+ detail: errorDescription
+ )
+ return .unhandledResponse(Int(statusCode), response)
+ }
+}
+
+enum MullvadApiTransportError: Error {
+ case connectionFailed(description: String?)
+}
diff --git a/ios/MullvadRESTTests/RequestExecutorTests.swift b/ios/MullvadRESTTests/RequestExecutorTests.swift
index e4ffa58827..36b3ca2b3c 100644
--- a/ios/MullvadRESTTests/RequestExecutorTests.swift
+++ b/ios/MullvadRESTTests/RequestExecutorTests.swift
@@ -27,7 +27,8 @@ final class RequestExecutorTests: XCTestCase {
let proxyFactory = REST.ProxyFactory.makeProxyFactory(
transportProvider: transportProvider,
- addressCache: addressCache
+ addressCache: addressCache,
+ apiContext: REST.apiContext
)
timerServerProxy = TimeServerProxy(configuration: proxyFactory.configuration)
}
diff --git a/ios/MullvadRustRuntime/MullvadApiCancellable.swift b/ios/MullvadRustRuntime/MullvadApiCancellable.swift
new file mode 100644
index 0000000000..0f0e0fe6e4
--- /dev/null
+++ b/ios/MullvadRustRuntime/MullvadApiCancellable.swift
@@ -0,0 +1,23 @@
+//
+// MullvadApiCancellable.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-02-07.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+public class MullvadApiCancellable {
+ private let handle: SwiftCancelHandle
+
+ public init(handle: consuming SwiftCancelHandle) {
+ self.handle = handle
+ }
+
+ deinit {
+ mullvad_api_cancel_task_drop(handle)
+ }
+
+ public func cancel() {
+ mullvad_api_cancel_task(handle)
+ }
+}
diff --git a/ios/MullvadRustRuntime/MullvadApiCompletion.swift b/ios/MullvadRustRuntime/MullvadApiCompletion.swift
new file mode 100644
index 0000000000..ca61c6791f
--- /dev/null
+++ b/ios/MullvadRustRuntime/MullvadApiCompletion.swift
@@ -0,0 +1,28 @@
+//
+// MullvadApiCompletion.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-01-16.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+@_silgen_name("mullvad_api_completion_finish")
+func mullvadApiCompletionFinish(
+ response: SwiftMullvadApiResponse,
+ completionCookie: UnsafeMutableRawPointer
+) {
+ let completionBridge = Unmanaged<MullvadApiCompletion>
+ .fromOpaque(completionCookie)
+ .takeRetainedValue()
+ let apiResponse = MullvadApiResponse(response: response)
+
+ completionBridge.completion(apiResponse)
+}
+
+public class MullvadApiCompletion {
+ public var completion: (MullvadApiResponse) -> Void
+
+ public init(completion: @escaping ((MullvadApiResponse) -> Void)) {
+ self.completion = completion
+ }
+}
diff --git a/ios/MullvadRustRuntime/MullvadApiContext.swift b/ios/MullvadRustRuntime/MullvadApiContext.swift
new file mode 100644
index 0000000000..f637590612
--- /dev/null
+++ b/ios/MullvadRustRuntime/MullvadApiContext.swift
@@ -0,0 +1,27 @@
+//
+// MullvadApiContext.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-01-24.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadTypes
+
+public struct MullvadApiContext: Sendable {
+ enum MullvadApiContextError: Error {
+ case failedToConstructApiClient
+ }
+
+ public let context: SwiftApiContext
+
+ public init(host: String, address: AnyIPEndpoint) throws {
+ context = mullvad_api_init_new(host, address.description)
+
+ if context._0 == nil {
+ throw MullvadApiContextError.failedToConstructApiClient
+ }
+ }
+}
+
+extension SwiftApiContext: @unchecked @retroactive Sendable {}
diff --git a/ios/MullvadRustRuntime/MullvadApiResponse.swift b/ios/MullvadRustRuntime/MullvadApiResponse.swift
new file mode 100644
index 0000000000..a709342b4e
--- /dev/null
+++ b/ios/MullvadRustRuntime/MullvadApiResponse.swift
@@ -0,0 +1,55 @@
+//
+// MullvadApiResponse.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-01-24.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+public class MullvadApiResponse {
+ private let response: SwiftMullvadApiResponse
+
+ public init(response: consuming SwiftMullvadApiResponse) {
+ self.response = response
+ }
+
+ deinit {
+ mullvad_response_drop(response)
+ }
+
+ public var body: Data? {
+ guard let body = response.body else {
+ return nil
+ }
+
+ return Data(UnsafeBufferPointer(start: body, count: Int(response.body_size)))
+ }
+
+ public var errorDescription: String? {
+ return if response.error_description == nil {
+ nil
+ } else {
+ String(cString: response.error_description)
+ }
+ }
+
+ public var statusCode: UInt16 {
+ response.status_code
+ }
+
+ public var serverResponseCode: String? {
+ return if response.server_response_code == nil {
+ nil
+ } else {
+ String(cString: response.server_response_code)
+ }
+ }
+
+ public var shouldRetry: Bool {
+ response.should_retry
+ }
+
+ public var success: Bool {
+ response.success
+ }
+}
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index b10f4f81f2..faae315d6d 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -14,6 +14,8 @@ enum TunnelObfuscatorProtocol {
};
typedef uint8_t TunnelObfuscatorProtocol;
+typedef struct ApiContext ApiContext;
+
/**
* A thin wrapper around [`mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState`] that
* can start a local forwarder (see [`Self::start`]).
@@ -22,6 +24,31 @@ typedef struct EncryptedDnsProxyState EncryptedDnsProxyState;
typedef struct ExchangeCancelToken ExchangeCancelToken;
+typedef struct RequestCancelHandle RequestCancelHandle;
+
+typedef struct SwiftApiContext {
+ const struct ApiContext *_0;
+} SwiftApiContext;
+
+typedef struct SwiftCancelHandle {
+ struct RequestCancelHandle *ptr;
+} SwiftCancelHandle;
+
+typedef struct SwiftMullvadApiResponse {
+ uint8_t *body;
+ uintptr_t body_size;
+ uint16_t status_code;
+ uint8_t *error_description;
+ uint8_t *server_response_code;
+ bool success;
+ bool should_retry;
+ uint64_t retry_after;
+} SwiftMullvadApiResponse;
+
+typedef struct CompletionCookie {
+ void *_0;
+} CompletionCookie;
+
typedef struct ProxyHandle {
void *context;
uint16_t port;
@@ -50,6 +77,86 @@ typedef struct EphemeralPeerParameters {
extern const uint16_t CONFIG_SERVICE_PORT;
/**
+ * # Safety
+ *
+ * `host` must be a pointer to a null terminated string representing a hostname for Mullvad API host.
+ * This hostname will be used for TLS validation but not used for domain name resolution.
+ *
+ * `address` must be a pointer to a null terminated string representing a socket address through which
+ * the Mullvad API can be reached directly.
+ *
+ * If a context cannot be constructed this function will panic since the call site would not be able
+ * to proceed in a meaningful way anyway.
+ *
+ * This function is safe.
+ */
+struct SwiftApiContext mullvad_api_init_new(const uint8_t *host,
+ const uint8_t *address);
+
+/**
+ * # Safety
+ *
+ * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+ * by calling `mullvad_api_init_new`.
+ *
+ * `completion_cookie` must be pointing to a valid instance of `CompletionCookie`. `CompletionCookie` is
+ * safe because the pointer in `MullvadApiCompletion` is valid for the lifetime of the process where this
+ * type is intended to be used.
+ *
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_context,
+ void *completion_cookie);
+
+/**
+ * Called by the Swift side to signal that a Mullvad API call should be cancelled.
+ * After this call, the cancel token is no longer valid.
+ *
+ * # Safety
+ *
+ * `handle_ptr` must be pointing to a valid instance of `SwiftCancelHandle`. This function
+ * is not safe to call multiple times with the same `SwiftCancelHandle`.
+ */
+void mullvad_api_cancel_task(struct SwiftCancelHandle handle_ptr);
+
+/**
+ * Called by the Swift side to signal that the Rust `SwiftCancelHandle` can be safely
+ * dropped from memory.
+ *
+ * # Safety
+ *
+ * `handle_ptr` must be pointing to a valid instance of `SwiftCancelHandle`. This function
+ * is not safe to call multiple times with the same `SwiftCancelHandle`.
+ */
+void mullvad_api_cancel_task_drop(struct SwiftCancelHandle handle_ptr);
+
+/**
+ * Maps to `mullvadApiCompletionFinish` on Swift side to facilitate callback based completion flow when doing
+ * network calls through Mullvad API on Rust side.
+ *
+ * # Safety
+ *
+ * `response` must be pointing to a valid instance of `SwiftMullvadApiResponse`.
+ *
+ * `completion_cookie` must be pointing to a valid instance of `CompletionCookie`. `CompletionCookie` is safe
+ * because the pointer in `MullvadApiCompletion` is valid for the lifetime of the process where this type is
+ * intended to be used.
+ */
+extern void mullvad_api_completion_finish(struct SwiftMullvadApiResponse response,
+ struct CompletionCookie completion_cookie);
+
+/**
+ * Called by the Swift side to signal that the Rust `SwiftMullvadApiResponse` can be safely
+ * dropped from memory.
+ *
+ * # Safety
+ *
+ * `response` must be pointing to a valid instance of `SwiftMullvadApiResponse`. This function
+ * is not safe to call multiple times with the same `SwiftMullvadApiResponse`.
+ */
+void mullvad_response_drop(struct SwiftMullvadApiResponse response);
+
+/**
* Initializes a valid pointer to an instance of `EncryptedDnsProxyState`.
*
* # Safety
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index ecb371e025..2b1036fadf 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -504,7 +504,7 @@
7A2960FD2A964BB700389B82 /* AlertPresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2960FC2A964BB700389B82 /* AlertPresentation.swift */; };
7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307AD82A8CD8DA0017618B /* Duration.swift */; };
7A307ADB2A8F56DF0017618B /* Duration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */; };
- 7A3215742D3E5A85005DF395 /* DAITASettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3215732D3E5A7B005DF395 /* DAITASettingsCoordinator.swift */; };
+ 7A3215722D3934E6005DF395 /* MullvadApiCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3215702D392F0B005DF395 /* MullvadApiCompletion.swift */; };
7A33538F2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */; };
7A3353912AAA014400F0A71C /* SimulatorVPNConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */; };
7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */; };
@@ -598,7 +598,10 @@
7A8A19242CF4C9BF000BCB5B /* MultihopPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */; };
7A8A19262CF4D37B000BCB5B /* DAITAPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19252CF4D373000BCB5B /* DAITAPage.swift */; };
7A8A19282CF603EB000BCB5B /* SettingsViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19272CF603E3000BCB5B /* SettingsViewControllerFactory.swift */; };
- 7A99D36D2D54FCC400891FF7 /* relays.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A99D36C2D54FCC400891FF7 /* relays.json */; };
+ 7A95B6792D5F729300687524 /* DAITASettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A95B6782D5F729300687524 /* DAITASettingsCoordinator.swift */; };
+ 7A95B67B2D5F758300687524 /* relays.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A95B67A2D5F758300687524 /* relays.json */; };
+ 7A99D36F2D56070400891FF7 /* MullvadApiRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A99D36E2D5606F900891FF7 /* MullvadApiRequestFactory.swift */; };
+ 7A99D3712D56222000891FF7 /* MullvadApiCancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A99D3702D56220E00891FF7 /* MullvadApiCancellable.swift */; };
7A9BE5A22B8F88C500E2A7D0 /* LocationNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */; };
7A9BE5A32B8F89B900E2A7D0 /* LocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389F72B864CDF008E77E1 /* LocationNode.swift */; };
7A9BE5A52B90760C00E2A7D0 /* CustomListsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */; };
@@ -639,6 +642,9 @@
7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */; };
7AB4CCB92B69097E006037F5 /* IPOverrideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */; };
7AB4CCBB2B691BBB006037F5 /* IPOverrideInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */; };
+ 7AB931242D43C2CA005FCEBA /* MullvadApiContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB931232D43C2C2005FCEBA /* MullvadApiContext.swift */; };
+ 7AB931262D43D22F005FCEBA /* MullvadApiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB931252D43D222005FCEBA /* MullvadApiResponse.swift */; };
+ 7AB9312F2D4A5D0A005FCEBA /* RESTRustNetworkOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB9312D2D4A5D0A005FCEBA /* RESTRustNetworkOperation.swift */; };
7ABCA5B32A9349F20044A708 /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */; };
7ABCA5B42A9349F20044A708 /* Routing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7ABCA5B72A9353C60044A708 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9F72983D36800BE19F7 /* Coordinator.swift */; };
@@ -1072,11 +1078,10 @@
F0F56B092C0E058A009D676B /* ObserverList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CC40EE24A601900019D96E /* ObserverList.swift */; };
F0FADDEA2BE90AAA000D0B02 /* LaunchArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */; };
F0FADDEC2BE90AB0000D0B02 /* LaunchArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */; };
+ F910A4012D3FF23A002FF3BB /* View+Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4002D3FF22E002FF3BB /* View+Modifier.swift */; };
F910A4312D4A1B41002FF3BB /* InAppPurchaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4302D4A1B3B002FF3BB /* InAppPurchaseCoordinator.swift */; };
F910A43A2D4A283D002FF3BB /* InAppPurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4392D4A2839002FF3BB /* InAppPurchaseViewController.swift */; };
F910A8572D523812002FF3BB /* TunnelSettingsV7.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */; };
- F95C1C252D3E5E8E00EBE769 /* UIAlertController+InAppPurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95C1C242D3E5E7A00EBE769 /* UIAlertController+InAppPurchase.swift */; };
- F910A4012D3FF23A002FF3BB /* View+Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4002D3FF22E002FF3BB /* View+Modifier.swift */; };
F998EFF82D359C4600D88D01 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; };
F998EFFA2D3656BA00D88D01 /* SKProduct+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = F998EFF92D3656B100D88D01 /* SKProduct+Sorting.swift */; };
/* End PBXBuildFile section */
@@ -2024,7 +2029,7 @@
7A2960FC2A964BB700389B82 /* AlertPresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresentation.swift; sourceTree = "<group>"; };
7A307AD82A8CD8DA0017618B /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = "<group>"; };
7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duration+Extensions.swift"; sourceTree = "<group>"; };
- 7A3215732D3E5A7B005DF395 /* DAITASettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITASettingsCoordinator.swift; sourceTree = "<group>"; };
+ 7A3215702D392F0B005DF395 /* MullvadApiCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadApiCompletion.swift; sourceTree = "<group>"; };
7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderManager.swift; sourceTree = "<group>"; };
7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorVPNConnection.swift; sourceTree = "<group>"; };
7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelInfo.swift; sourceTree = "<group>"; };
@@ -2107,7 +2112,10 @@
7A8A19232CF4C9B8000BCB5B /* MultihopPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPage.swift; sourceTree = "<group>"; };
7A8A19252CF4D373000BCB5B /* DAITAPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITAPage.swift; sourceTree = "<group>"; };
7A8A19272CF603E3000BCB5B /* SettingsViewControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewControllerFactory.swift; sourceTree = "<group>"; };
- 7A99D36C2D54FCC400891FF7 /* relays.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = relays.json; sourceTree = "<group>"; };
+ 7A95B6782D5F729300687524 /* DAITASettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITASettingsCoordinator.swift; sourceTree = "<group>"; };
+ 7A95B67A2D5F758300687524 /* relays.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = relays.json; sourceTree = "<group>"; };
+ 7A99D36E2D5606F900891FF7 /* MullvadApiRequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadApiRequestFactory.swift; sourceTree = "<group>"; };
+ 7A99D3702D56220E00891FF7 /* MullvadApiCancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadApiCancellable.swift; sourceTree = "<group>"; };
7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationNodeTests.swift; sourceTree = "<group>"; };
7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsDataSourceTests.swift; sourceTree = "<group>"; };
7A9BE5A82B90806800E2A7D0 /* CustomListsRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsRepositoryStub.swift; sourceTree = "<group>"; };
@@ -2145,6 +2153,9 @@
7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewControllerWrapper.swift; sourceTree = "<group>"; };
7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideTests.swift; sourceTree = "<group>"; };
7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideInteractor.swift; sourceTree = "<group>"; };
+ 7AB931232D43C2C2005FCEBA /* MullvadApiContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadApiContext.swift; sourceTree = "<group>"; };
+ 7AB931252D43D222005FCEBA /* MullvadApiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadApiResponse.swift; sourceTree = "<group>"; };
+ 7AB9312D2D4A5D0A005FCEBA /* RESTRustNetworkOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRustNetworkOperation.swift; sourceTree = "<group>"; };
7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Weight.swift"; sourceTree = "<group>"; };
7ABFB09D2BA316220074A49E /* RelayConstraintsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraintsTests.swift; sourceTree = "<group>"; };
7AC8A3AD2ABC6FBB00DC4939 /* SettingsHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = "<group>"; };
@@ -2450,11 +2461,10 @@
F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorResult.swift; sourceTree = "<group>"; };
F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoRelaysSatisfyingConstraintsError.swift; sourceTree = "<group>"; };
F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyExchangingResultStub.swift; sourceTree = "<group>"; };
- F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV7.swift; sourceTree = "<group>"; };
F910A4002D3FF22E002FF3BB /* View+Modifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Modifier.swift"; sourceTree = "<group>"; };
- F95C1C242D3E5E7A00EBE769 /* UIAlertController+InAppPurchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+InAppPurchase.swift"; sourceTree = "<group>"; };
F910A4302D4A1B3B002FF3BB /* InAppPurchaseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseCoordinator.swift; sourceTree = "<group>"; };
F910A4392D4A2839002FF3BB /* InAppPurchaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseViewController.swift; sourceTree = "<group>"; };
+ F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV7.swift; sourceTree = "<group>"; };
F998EFF92D3656B100D88D01 /* SKProduct+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+Sorting.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -2643,7 +2653,7 @@
isa = PBXGroup;
children = (
06799AB428F98CE700ACD94E /* le_root_cert.cer */,
- 7A99D36C2D54FCC400891FF7 /* relays.json */,
+ 7A95B67A2D5F758300687524 /* relays.json */,
);
path = Assets;
sourceTree = "<group>";
@@ -4232,7 +4242,7 @@
7A8A19082CE5FFD7000BCB5B /* DAITA */ = {
isa = PBXGroup;
children = (
- 7A3215732D3E5A7B005DF395 /* DAITASettingsCoordinator.swift */,
+ 7A95B6782D5F729300687524 /* DAITASettingsCoordinator.swift */,
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */,
7A8A19132CEF2527000BCB5B /* DAITATunnelSettingsViewModel.swift */,
7A8A19092CE5FFDF000BCB5B /* SettingsDAITAView.swift */,
@@ -4438,12 +4448,16 @@
children = (
A9D9A4D32C36E1EA004088DD /* mullvad_rust_runtime.h */,
A992DA1F2C24709F00DE7CE5 /* MullvadRustRuntime.h */,
- A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */,
+ 014449942CA293B100C0C2F2 /* EncryptedDNSProxy.swift */,
A948809A2BC9308D0090A44C /* EphemeralPeerExchangeActor.swift */,
A9EB4F9C2B7FAB21002A2D7A /* EphemeralPeerNegotiator.swift */,
+ A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */,
+ 7A99D3702D56220E00891FF7 /* MullvadApiCancellable.swift */,
+ 7A3215702D392F0B005DF395 /* MullvadApiCompletion.swift */,
+ 7AB931232D43C2C2005FCEBA /* MullvadApiContext.swift */,
+ 7AB931252D43D222005FCEBA /* MullvadApiResponse.swift */,
F0DDE40F2B220458006B57A7 /* ShadowSocksProxy.swift */,
584023212A406BF5007B27AC /* TunnelObfuscator.swift */,
- 014449942CA293B100C0C2F2 /* EncryptedDNSProxy.swift */,
);
path = MullvadRustRuntime;
sourceTree = "<group>";
@@ -4507,8 +4521,10 @@
06FAE66A28F83CA30033DD93 /* RESTRequestFactory.swift */,
06FAE67428F83CA40033DD93 /* RESTRequestHandler.swift */,
06FAE66628F83CA30033DD93 /* RESTResponseHandler.swift */,
+ 7AB9312D2D4A5D0A005FCEBA /* RESTRustNetworkOperation.swift */,
06FAE67528F83CA40033DD93 /* RESTTaskIdentifier.swift */,
06FAE66528F83CA30033DD93 /* RESTURLSession.swift */,
+ 7A99D36E2D5606F900891FF7 /* MullvadApiRequestFactory.swift */,
06FAE67728F83CA40033DD93 /* ServerRelaysResponse.swift */,
06FAE66B28F83CA30033DD93 /* SSLPinningURLSessionDelegate.swift */,
);
@@ -5390,7 +5406,7 @@
buildActionMask = 2147483647;
files = (
062B45A328FD4CA700746E77 /* le_root_cert.cer in Resources */,
- 7A99D36D2D54FCC400891FF7 /* relays.json in Resources */,
+ 7A95B67B2D5F758300687524 /* relays.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5694,6 +5710,7 @@
F0B894EF2BF751C500817A42 /* RelayWithLocation.swift in Sources */,
F0DDE42C2B220A15006B57A7 /* Midpoint.swift in Sources */,
A90763C72B2858DC0045ADF0 /* CancellableChain.swift in Sources */,
+ 7AB9312F2D4A5D0A005FCEBA /* RESTRustNetworkOperation.swift in Sources */,
06799AF128F98E4800ACD94E /* RESTAPIProxy.swift in Sources */,
F0DDE42A2B220A15006B57A7 /* Haversine.swift in Sources */,
589E76C02A9378F100E502F3 /* RESTRequestExecutor.swift in Sources */,
@@ -5702,6 +5719,7 @@
F0164ED12B4F2DCB0020268D /* AccessMethodIterator.swift in Sources */,
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */,
A90763BB2B2857D50045ADF0 /* Socks5AddressType.swift in Sources */,
+ 7A99D36F2D56070400891FF7 /* MullvadApiRequestFactory.swift in Sources */,
F0F3161B2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift in Sources */,
06799AE028F98E4800ACD94E /* RESTCoding.swift in Sources */,
A90763B72B2857D50045ADF0 /* Socks5DataStreamHandler.swift in Sources */,
@@ -6055,6 +6073,7 @@
44075DFB2CDA4F7400F61139 /* UDPOverTCPObfuscationSettingsViewModel.swift in Sources */,
7A6389DC2B7E3BD6008E77E1 /* CustomListViewModel.swift in Sources */,
4422C0712CCFF6790001A385 /* UDPOverTCPObfuscationSettingsView.swift in Sources */,
+ 7A95B6792D5F729300687524 /* DAITASettingsCoordinator.swift in Sources */,
7A9CCCC42A96302800DD6A34 /* TunnelCoordinator.swift in Sources */,
5827B0A42B0F38FD00CCBBA1 /* EditAccessMethodInteractorProtocol.swift in Sources */,
586C0D852B03D31E00E7CDD7 /* SocksSectionHandler.swift in Sources */,
@@ -6157,7 +6176,6 @@
588D7EDC2AF3A55E005DF40A /* ListAccessMethodInteractorProtocol.swift in Sources */,
588D7ED62AF3903F005DF40A /* ListAccessMethodViewController.swift in Sources */,
7A8A190E2CEB77C1000BCB5B /* SettingsRowViewFooter.swift in Sources */,
- 7A3215742D3E5A85005DF395 /* DAITASettingsCoordinator.swift in Sources */,
7A6000FC2B628DF6001CF0D9 /* ListCellContentConfiguration.swift in Sources */,
582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */,
7A9FA1442A2E3FE5000B728D /* CheckableSettingsCell.swift in Sources */,
@@ -6690,11 +6708,15 @@
files = (
A9D9A4B12C36D10E004088DD /* ShadowSocksProxy.swift in Sources */,
014449952CA293B100C0C2F2 /* EncryptedDNSProxy.swift in Sources */,
+ 7AB931242D43C2CA005FCEBA /* MullvadApiContext.swift in Sources */,
A9D9A4BB2C36D397004088DD /* EphemeralPeerNegotiator.swift in Sources */,
A9D9A4B22C36D12D004088DD /* TunnelObfuscator.swift in Sources */,
+ 7AB931262D43D22F005FCEBA /* MullvadApiResponse.swift in Sources */,
A9173C322C36CCDD00F6A08C /* EphemeralPeerReceiver.swift in Sources */,
+ 7A99D3712D56222000891FF7 /* MullvadApiCancellable.swift in Sources */,
A93969812CE606190032A7A0 /* Maybenot.swift in Sources */,
F05919802C45515200C301F3 /* EphemeralPeerExchangeActor.swift in Sources */,
+ 7A3215722D3934E6005DF395 /* MullvadApiCompletion.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
index 5e52a9d61f..917cd9ed29 100644
--- a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
+++ b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
@@ -93,7 +93,6 @@ final class AddressCacheTracker: @unchecked Sendable {
return self.apiProxy.getAddressList(retryStrategy: .default) { result in
self.setEndpoints(from: result)
-
finish(result.map { _ in true })
}
}
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 2642309f76..5e30f2fae9 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -190,14 +190,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
transportProvider: REST.AnyTransportProvider { [weak self] in
return self?.transportMonitor.makeTransport()
},
- addressCache: addressCache
+ addressCache: addressCache,
+ apiContext: REST.apiContext
)
} else {
proxyFactory = REST.ProxyFactory.makeProxyFactory(
transportProvider: REST.AnyTransportProvider { [weak self] in
return self?.transportMonitor.makeTransport()
},
- addressCache: addressCache
+ addressCache: addressCache,
+ apiContext: REST.apiContext
)
}
apiProxy = proxyFactory.createAPIProxy()
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index 93520bd97b..228a6f9d9e 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -77,7 +77,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
let proxyFactory = REST.ProxyFactory.makeProxyFactory(
transportProvider: transportProvider,
- addressCache: addressCache
+ addressCache: addressCache,
+ apiContext: REST.apiContext
)
let accountsProxy = proxyFactory.createAccountsProxy()
let devicesProxy = proxyFactory.createDevicesProxy()