Skip to content

Commit 90e29ee

Browse files
committed
Send API requests across app/packet tunnel boundary
1 parent dc1469c commit 90e29ee

23 files changed

+522
-174
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)

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(())

ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift

+18-93
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,24 @@ extension REST {
1616
class MullvadApiNetworkOperation<Success: Sendable>: ResultOperation<Success>, @unchecked Sendable {
1717
private let logger: Logger
1818

19-
private let requestHandler: MullvadApiRequestHandler
19+
private let request: APIRequest
20+
private let transportProvider: APITransportProviderProtocol
2021
private var responseDecoder: JSONDecoder
2122
private let responseHandler: any RESTRustResponseHandler<Success>
22-
private var networkTask: MullvadApiCancellable?
23-
24-
private let retryStrategy: RetryStrategy
25-
private var retryDelayIterator: AnyIterator<Duration>
26-
private var retryTimer: DispatchSourceTimer?
27-
private var retryCount = 0
23+
private var networkTask: Cancellable?
2824

2925
init(
3026
name: String,
3127
dispatchQueue: DispatchQueue,
32-
retryStrategy: RetryStrategy,
33-
requestHandler: @escaping MullvadApiRequestHandler,
28+
request: APIRequest,
29+
transportProvider: APITransportProviderProtocol,
3430
responseDecoder: JSONDecoder,
3531
responseHandler: some RESTRustResponseHandler<Success>,
3632
completionHandler: CompletionHandler? = nil
3733
) {
38-
self.retryStrategy = retryStrategy
39-
retryDelayIterator = retryStrategy.makeDelayIterator()
34+
self.request = request
4035
self.responseDecoder = responseDecoder
41-
self.requestHandler = requestHandler
36+
self.transportProvider = transportProvider
4237
self.responseHandler = responseHandler
4338

4439
var logger = Logger(label: "REST.RustNetworkOperation")
@@ -53,10 +48,7 @@ extension REST {
5348
}
5449

5550
override public func operationDidCancel() {
56-
retryTimer?.cancel()
5751
networkTask?.cancel()
58-
59-
retryTimer = nil
6052
networkTask = nil
6153
}
6254

@@ -72,97 +64,30 @@ extension REST {
7264
return
7365
}
7466

75-
networkTask = requestHandler { [weak self] response in
67+
let transport = transportProvider.makeTransport()
68+
networkTask = transport?.sendRequest(request) { [weak self] response in
7669
guard let self else { return }
7770

78-
if let error = response.restError() {
79-
if response.shouldRetry {
80-
retryRequest(with: error)
81-
} else {
82-
finish(result: .failure(error))
83-
}
84-
71+
if let errorWrapper = response.error, let error = errorWrapper.originalError {
72+
finish(result: .failure(error))
8573
return
8674
}
8775

88-
let decodedResponse = responseHandler.handleResponse(response)
76+
let decodedResponse = responseHandler.handleResponse(response.data)
8977

9078
switch decodedResponse {
9179
case let .success(value):
9280
finish(result: .success(value))
9381
case let .decoding(block):
94-
finish(result: .success(try block()))
82+
do {
83+
finish(result: .success(try block()))
84+
} catch {
85+
finish(result: .failure(REST.Error.unhandledResponse(0, nil)))
86+
}
9587
case let .unhandledResponse(error):
96-
finish(result: .failure(REST.Error.unhandledResponse(Int(response.statusCode), error)))
88+
finish(result: .failure(REST.Error.unhandledResponse(0, error)))
9789
}
9890
}
9991
}
100-
101-
private func retryRequest(with error: REST.Error) {
102-
// Check if retry count is not exceeded.
103-
guard retryCount < retryStrategy.maxRetryCount else {
104-
if retryStrategy.maxRetryCount > 0 {
105-
logger.debug("Ran out of retry attempts (\(retryStrategy.maxRetryCount))")
106-
}
107-
finish(result: .failure(error))
108-
return
109-
}
110-
111-
// Increment retry count.
112-
retryCount += 1
113-
114-
// Retry immediately if retry delay is set to never.
115-
guard retryStrategy.delay != .never else {
116-
startRequest()
117-
return
118-
}
119-
120-
guard let waitDelay = retryDelayIterator.next() else {
121-
logger.debug("Retry delay iterator failed to produce next value.")
122-
123-
finish(result: .failure(error))
124-
return
125-
}
126-
127-
logger.debug("Retry in \(waitDelay.logFormat()).")
128-
129-
// Create timer to delay retry.
130-
let timer = DispatchSource.makeTimerSource(queue: dispatchQueue)
131-
132-
timer.setEventHandler { [weak self] in
133-
self?.startRequest()
134-
}
135-
136-
timer.setCancelHandler { [weak self] in
137-
self?.finish(result: .failure(OperationError.cancelled))
138-
}
139-
140-
timer.schedule(wallDeadline: .now() + waitDelay.timeInterval)
141-
timer.activate()
142-
143-
retryTimer = timer
144-
}
14592
}
14693
}
147-
148-
extension MullvadApiResponse {
149-
public func restError() -> REST.Error? {
150-
guard !success else {
151-
return nil
152-
}
153-
154-
guard let serverResponseCode else {
155-
return .transport(MullvadApiTransportError.connectionFailed(description: errorDescription))
156-
}
157-
158-
let response = REST.ServerErrorResponse(
159-
code: REST.ServerResponseCode(rawValue: serverResponseCode),
160-
detail: errorDescription
161-
)
162-
return .unhandledResponse(Int(statusCode), response)
163-
}
164-
}
165-
166-
enum MullvadApiTransportError: Error {
167-
case connectionFailed(description: String?)
168-
}

0 commit comments

Comments
 (0)