summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/APIRequest/APIRequestProxy.swift
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-03-03 15:40:02 +0100
committerJon Petersson <jon.petersson@mullvad.net>2025-03-03 15:40:02 +0100
commitb24faaa96394420935f4e4a282554e10bdc41e2d (patch)
tree161e9cca2f1107034337e4248f66d5993e7b7eae /ios/MullvadREST/APIRequest/APIRequestProxy.swift
parent8ceda7aeb54ed058543e1a665d24cd5c928a872e (diff)
parent9a7866e96a1788494b955764fbe92961eb65a9f5 (diff)
downloadmullvadvpn-b24faaa96394420935f4e4a282554e10bdc41e2d.tar.xz
mullvadvpn-b24faaa96394420935f4e4a282554e10bdc41e2d.zip
Merge branch 'transport-api-requests-across-apppacket-tunnel-boundary-ios-1041'
Diffstat (limited to 'ios/MullvadREST/APIRequest/APIRequestProxy.swift')
-rw-r--r--ios/MullvadREST/APIRequest/APIRequestProxy.swift89
1 files changed, 89 insertions, 0 deletions
diff --git a/ios/MullvadREST/APIRequest/APIRequestProxy.swift b/ios/MullvadREST/APIRequest/APIRequestProxy.swift
new file mode 100644
index 0000000000..8e2ac4fad2
--- /dev/null
+++ b/ios/MullvadREST/APIRequest/APIRequestProxy.swift
@@ -0,0 +1,89 @@
+//
+// APIRequestProxy.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2025-02-13.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadRustRuntime
+import MullvadTypes
+
+public protocol APIRequestProxyProtocol {
+ func sendRequest(_ proxyRequest: ProxyAPIRequest, completion: @escaping @Sendable (ProxyAPIResponse) -> Void)
+ func sendRequest(_ proxyRequest: ProxyAPIRequest) async -> ProxyAPIResponse
+ func cancelRequest(identifier: UUID)
+}
+
+/// Network request proxy capable of passing serializable requests and responses over the given transport provider.
+public final class APIRequestProxy: APIRequestProxyProtocol, @unchecked Sendable {
+ /// Serial queue used for synchronizing access to class members.
+ private let dispatchQueue: DispatchQueue
+
+ private let transportProvider: APITransportProviderProtocol
+
+ /// List of all proxied network requests bypassing VPN.
+ private var proxiedRequests: [UUID: Cancellable] = [:]
+
+ public init(
+ dispatchQueue: DispatchQueue,
+ transportProvider: APITransportProviderProtocol
+ ) {
+ self.dispatchQueue = dispatchQueue
+ self.transportProvider = transportProvider
+ }
+
+ public func sendRequest(
+ _ proxyRequest: ProxyAPIRequest,
+ completion: @escaping @Sendable (ProxyAPIResponse) -> Void
+ ) {
+ dispatchQueue.async {
+ guard let transport = self.transportProvider.makeTransport() else {
+ // Cancel old task, if there's one scheduled.
+ self.cancelRequest(identifier: proxyRequest.id)
+
+ completion(ProxyAPIResponse(data: nil, error: nil))
+ return
+ }
+
+ let cancellable = transport.sendRequest(proxyRequest.request) { [weak self] response in
+ guard let self else { return }
+
+ // Use `dispatchQueue` to guarantee thread safe access to `proxiedRequests`
+ dispatchQueue.async {
+ _ = self.removeRequest(identifier: proxyRequest.id)
+ completion(response)
+ }
+ }
+
+ // Cancel old task, if there's one scheduled.
+ let oldTask = self.addRequest(identifier: proxyRequest.id, task: cancellable)
+ oldTask?.cancel()
+ }
+ }
+
+ public func sendRequest(_ proxyRequest: ProxyAPIRequest) async -> ProxyAPIResponse {
+ return await withCheckedContinuation { continuation in
+ sendRequest(proxyRequest) { proxyResponse in
+ continuation.resume(returning: proxyResponse)
+ }
+ }
+ }
+
+ public func cancelRequest(identifier: UUID) {
+ dispatchQueue.async {
+ let task = self.removeRequest(identifier: identifier)
+ task?.cancel()
+ }
+ }
+
+ private func addRequest(identifier: UUID, task: Cancellable) -> Cancellable? {
+ dispatchPrecondition(condition: .onQueue(dispatchQueue))
+ return proxiedRequests.updateValue(task, forKey: identifier)
+ }
+
+ private func removeRequest(identifier: UUID) -> Cancellable? {
+ dispatchPrecondition(condition: .onQueue(dispatchQueue))
+ return proxiedRequests.removeValue(forKey: identifier)
+ }
+}