Skip to content

Commit e14bde5

Browse files
committed
Send API requests across app/packet tunnel boundary
1 parent 4dd8c35 commit e14bde5

29 files changed

+714
-269
lines changed

ios/MullvadMockData/MullvadREST/MockProxyFactory.swift

+5-6
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ public struct MockProxyFactory: ProxyFactoryProtocol {
2929

3030
public static func makeProxyFactory(
3131
transportProvider: any RESTTransportProvider,
32-
addressCache: REST.AddressCache,
33-
apiContext: MullvadApiContext
32+
apiTransportProvider: any APITransportProviderProtocol,
33+
addressCache: REST.AddressCache
3434
) -> any ProxyFactoryProtocol {
3535
let basicConfiguration = REST.ProxyConfiguration(
3636
transportProvider: transportProvider,
37-
addressCacheStore: addressCache,
38-
apiContext: apiContext
37+
apiTransportProvider: apiTransportProvider,
38+
addressCacheStore: addressCache
3939
)
4040

4141
let authenticationProxy = REST.AuthenticationProxy(
@@ -47,8 +47,7 @@ public struct MockProxyFactory: ProxyFactoryProtocol {
4747

4848
let authConfiguration = REST.AuthProxyConfiguration(
4949
proxyConfiguration: basicConfiguration,
50-
accessTokenManager: accessTokenManager,
51-
apiContext: apiContext
50+
accessTokenManager: accessTokenManager
5251
)
5352

5453
return MockProxyFactory(configuration: authConfiguration)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//
2+
// MullvadApiNetworkOperation.swift
3+
// MullvadREST
4+
//
5+
// Created by Jon Petersson on 2025-01-29.
6+
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import MullvadLogging
11+
import MullvadRustRuntime
12+
import MullvadTypes
13+
import Operations
14+
15+
private enum MullvadApiTransportError: Error {
16+
case connectionFailed(description: String?)
17+
}
18+
19+
extension REST {
20+
class MullvadApiNetworkOperation<Success: Sendable>: ResultOperation<Success>, @unchecked Sendable {
21+
private let logger: Logger
22+
23+
private let request: APIRequest
24+
private let transportProvider: APITransportProviderProtocol
25+
private var responseDecoder: JSONDecoder
26+
private let responseHandler: any RESTRustResponseHandler<Success>
27+
private var networkTask: Cancellable?
28+
29+
init(
30+
name: String,
31+
dispatchQueue: DispatchQueue,
32+
request: APIRequest,
33+
transportProvider: APITransportProviderProtocol,
34+
responseDecoder: JSONDecoder,
35+
responseHandler: some RESTRustResponseHandler<Success>,
36+
completionHandler: CompletionHandler? = nil
37+
) {
38+
self.request = request
39+
self.transportProvider = transportProvider
40+
self.responseDecoder = responseDecoder
41+
self.responseHandler = responseHandler
42+
43+
var logger = Logger(label: "REST.RustNetworkOperation")
44+
45+
logger[metadataKey: "name"] = .string(name)
46+
self.logger = logger
47+
48+
super.init(
49+
dispatchQueue: dispatchQueue,
50+
completionQueue: .main,
51+
completionHandler: completionHandler
52+
)
53+
}
54+
55+
override public func operationDidCancel() {
56+
networkTask?.cancel()
57+
networkTask = nil
58+
}
59+
60+
override public func main() {
61+
startRequest()
62+
}
63+
64+
func startRequest() {
65+
dispatchPrecondition(condition: .onQueue(dispatchQueue))
66+
67+
guard !isCancelled else {
68+
finish(result: .failure(OperationError.cancelled))
69+
return
70+
}
71+
72+
let transport = transportProvider.makeTransport()
73+
networkTask = transport?.sendRequest(request) { [weak self] response in
74+
guard let self else { return }
75+
76+
if let apiError = response.error {
77+
finish(result: .failure(restError(apiError: apiError)))
78+
return
79+
}
80+
81+
let decodedResponse = responseHandler.handleResponse(response.data)
82+
83+
switch decodedResponse {
84+
case let .success(value):
85+
finish(result: .success(value))
86+
case let .decoding(block):
87+
do {
88+
finish(result: .success(try block()))
89+
} catch {
90+
finish(result: .failure(REST.Error.unhandledResponse(0, nil)))
91+
}
92+
case let .unhandledResponse(error):
93+
finish(result: .failure(REST.Error.unhandledResponse(0, error)))
94+
}
95+
}
96+
}
97+
98+
private func restError(apiError: APIError) -> Error {
99+
guard let serverResponseCode = apiError.serverResponseCode else {
100+
return .transport(MullvadApiTransportError.connectionFailed(description: apiError.errorDescription))
101+
}
102+
103+
let response = REST.ServerErrorResponse(
104+
code: REST.ServerResponseCode(rawValue: serverResponseCode),
105+
detail: apiError.errorDescription
106+
)
107+
return .unhandledResponse(apiError.statusCode, response)
108+
}
109+
}
110+
}

ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift

+7-7
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
import MullvadRustRuntime
1010
import MullvadTypes
1111

12-
enum MullvadApiRequest {
13-
case getAddressList
14-
}
12+
public struct MullvadApiRequestFactory: Sendable {
13+
public let apiContext: MullvadApiContext
1514

16-
struct MullvadApiRequestFactory {
17-
let apiContext: MullvadApiContext
15+
public init(apiContext: MullvadApiContext) {
16+
self.apiContext = apiContext
17+
}
1818

19-
func makeRequest(_ request: MullvadApiRequest) -> REST.MullvadApiRequestHandler {
19+
public func makeRequest(_ request: APIRequest) -> REST.MullvadApiRequestHandler {
2020
{ completion in
2121
let pointerClass = MullvadApiCompletion { apiResponse in
2222
try? completion?(apiResponse)
@@ -33,5 +33,5 @@ struct MullvadApiRequestFactory {
3333
}
3434

3535
extension REST {
36-
typealias MullvadApiRequestHandler = (((MullvadApiResponse) throws -> Void)?) -> MullvadApiCancellable
36+
public typealias MullvadApiRequestHandler = (((MullvadApiResponse) throws -> Void)?) -> MullvadApiCancellable
3737
}

ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift

+2-4
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ extension REST {
6666
retryStrategy: REST.RetryStrategy,
6767
completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
6868
) -> Cancellable {
69-
let requestHandler = mullvadApiRequestFactory.makeRequest(.getAddressList)
70-
7169
let responseHandler = rustResponseHandler(
7270
decoding: [AnyIPEndpoint].self,
7371
with: responseDecoder
@@ -76,8 +74,8 @@ extension REST {
7674
let networkOperation = MullvadApiNetworkOperation(
7775
name: "get-api-addrs",
7876
dispatchQueue: dispatchQueue,
79-
retryStrategy: retryStrategy,
80-
requestHandler: requestHandler,
77+
request: .getAddressList(retryStrategy: .default),
78+
transportProvider: configuration.apiTransportProvider,
8179
responseDecoder: responseDecoder,
8280
responseHandler: responseHandler,
8381
completionHandler: completionHandler

ios/MullvadREST/ApiHandlers/RESTProxy.swift

+7-11
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ extension REST {
2727
/// URL request factory.
2828
let requestFactory: REST.RequestFactory
2929

30-
let mullvadApiRequestFactory: MullvadApiRequestFactory
31-
3230
/// URL response decoder.
3331
let responseDecoder: JSONDecoder
3432

@@ -43,7 +41,6 @@ extension REST {
4341

4442
self.configuration = configuration
4543
self.requestFactory = requestFactory
46-
self.mullvadApiRequestFactory = MullvadApiRequestFactory(apiContext: configuration.apiContext)
4744
self.responseDecoder = responseDecoder
4845
}
4946

@@ -135,17 +132,17 @@ extension REST {
135132

136133
public class ProxyConfiguration: @unchecked Sendable {
137134
public let transportProvider: RESTTransportProvider
135+
public let apiTransportProvider: APITransportProviderProtocol
138136
public let addressCacheStore: AddressCache
139-
public let apiContext: MullvadApiContext
140137

141138
public init(
142139
transportProvider: RESTTransportProvider,
143-
addressCacheStore: AddressCache,
144-
apiContext: MullvadApiContext
140+
apiTransportProvider: APITransportProviderProtocol,
141+
addressCacheStore: AddressCache
145142
) {
146143
self.transportProvider = transportProvider
144+
self.apiTransportProvider = apiTransportProvider
147145
self.addressCacheStore = addressCacheStore
148-
self.apiContext = apiContext
149146
}
150147
}
151148

@@ -154,15 +151,14 @@ extension REST {
154151

155152
public init(
156153
proxyConfiguration: ProxyConfiguration,
157-
accessTokenManager: RESTAccessTokenManagement,
158-
apiContext: MullvadApiContext
154+
accessTokenManager: RESTAccessTokenManagement
159155
) {
160156
self.accessTokenManager = accessTokenManager
161157

162158
super.init(
163159
transportProvider: proxyConfiguration.transportProvider,
164-
addressCacheStore: proxyConfiguration.addressCacheStore,
165-
apiContext: apiContext
160+
apiTransportProvider: proxyConfiguration.apiTransportProvider,
161+
addressCacheStore: proxyConfiguration.addressCacheStore
166162
)
167163
}
168164
}

ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift

+7-8
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public protocol ProxyFactoryProtocol {
1818

1919
static func makeProxyFactory(
2020
transportProvider: RESTTransportProvider,
21-
addressCache: REST.AddressCache,
22-
apiContext: MullvadApiContext
21+
apiTransportProvider: APITransportProviderProtocol,
22+
addressCache: REST.AddressCache
2323
) -> ProxyFactoryProtocol
2424
}
2525

@@ -29,13 +29,13 @@ extension REST {
2929

3030
public static func makeProxyFactory(
3131
transportProvider: any RESTTransportProvider,
32-
addressCache: REST.AddressCache,
33-
apiContext: MullvadApiContext
32+
apiTransportProvider: any APITransportProviderProtocol,
33+
addressCache: REST.AddressCache
3434
) -> any ProxyFactoryProtocol {
3535
let basicConfiguration = REST.ProxyConfiguration(
3636
transportProvider: transportProvider,
37-
addressCacheStore: addressCache,
38-
apiContext: apiContext
37+
apiTransportProvider: apiTransportProvider,
38+
addressCacheStore: addressCache
3939
)
4040

4141
let authenticationProxy = REST.AuthenticationProxy(
@@ -47,8 +47,7 @@ extension REST {
4747

4848
let authConfiguration = REST.AuthProxyConfiguration(
4949
proxyConfiguration: basicConfiguration,
50-
accessTokenManager: accessTokenManager,
51-
apiContext: apiContext
50+
accessTokenManager: accessTokenManager
5251
)
5352

5453
return ProxyFactory(configuration: authConfiguration)

ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift

+11-19
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ protocol RESTResponseHandler<Success> {
1919
protocol RESTRustResponseHandler<Success> {
2020
associatedtype Success
2121

22-
func handleResponse(_ response: MullvadApiResponse) -> REST.ResponseHandlerResult<Success>
22+
func handleResponse(_ body: Data?) -> REST.ResponseHandlerResult<Success>
2323
}
2424

2525
extension REST {
@@ -76,16 +76,16 @@ extension REST {
7676
}
7777

7878
final class RustResponseHandler<Success>: RESTRustResponseHandler {
79-
typealias HandlerBlock = (MullvadApiResponse) -> REST.ResponseHandlerResult<Success>
79+
typealias HandlerBlock = (Data?) -> REST.ResponseHandlerResult<Success>
8080

8181
private let handlerBlock: HandlerBlock
8282

8383
init(_ block: @escaping HandlerBlock) {
8484
handlerBlock = block
8585
}
8686

87-
func handleResponse(_ response: MullvadApiResponse) -> REST.ResponseHandlerResult<Success> {
88-
handlerBlock(response)
87+
func handleResponse(_ body: Data?) -> REST.ResponseHandlerResult<Success> {
88+
handlerBlock(body)
8989
}
9090
}
9191

@@ -96,28 +96,20 @@ extension REST {
9696
decoding type: T.Type,
9797
with decoder: JSONDecoder
9898
) -> RustResponseHandler<T> {
99-
RustResponseHandler { response in
100-
guard let body = response.body else {
99+
RustResponseHandler { data in
100+
guard let data else {
101101
return .unhandledResponse(nil)
102102
}
103103

104-
do {
105-
let decoded = try decoder.decode(type, from: body)
106-
return .decoding { decoded }
107-
} catch {
108-
return .unhandledResponse(
109-
try? decoder.decode(
110-
ServerErrorResponse.self,
111-
from: body
112-
)
113-
)
104+
return if let decoded = try? decoder.decode(type, from: data) {
105+
.decoding { decoded }
106+
} else {
107+
.unhandledResponse(nil)
114108
}
115109
}
116110
}
117111

118-
/// Returns default response handler that parses JSON response into the
119-
/// given `Decodable` type if possible, otherwise attempts to decode
120-
/// the server error.
112+
/// Response handler for reponses where the body is empty.
121113
static func rustEmptyResponseHandler() -> RustResponseHandler<Void> {
122114
RustResponseHandler { _ in
123115
.success(())

0 commit comments

Comments
 (0)