diff options
| author | Jon Petersson <jon.petersson@mullvad.net> | 2025-03-13 13:17:16 +0100 |
|---|---|---|
| committer | Jon Petersson <jon.petersson@mullvad.net> | 2025-03-31 11:39:29 +0200 |
| commit | eb72686c74607872ee510b432a442ea10baa1b86 (patch) | |
| tree | 40b51bd8e94c88633ec4c7332241986465355bf6 | |
| parent | efbb2c3c0c95f7e7a195c03e9d2483ec731a578e (diff) | |
| download | mullvadvpn-eb72686c74607872ee510b432a442ea10baa1b86.tar.xz mullvadvpn-eb72686c74607872ee510b432a442ea10baa1b86.zip | |
Tie rust and Swift side together
15 files changed, 152 insertions, 37 deletions
diff --git a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift index 3c8350b8fb..6f0e60a4ad 100644 --- a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift +++ b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift @@ -19,6 +19,14 @@ struct APIProxyStub: APIQuerying { AnyCancellable() } + func mullvadApiGetRelayList( + retryStrategy: REST.RetryStrategy, + etag: String?, + completionHandler: @escaping ProxyCompletionHandler<REST.ServerRelaysCacheResponse>) + -> Cancellable { + AnyCancellable() + } + func getAddressList( retryStrategy: REST.RetryStrategy, completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]> diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift index 908e0bddd4..38486d3115 100644 --- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift +++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift @@ -18,6 +18,12 @@ public protocol APIQuerying: Sendable { completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]> ) -> Cancellable + func mullvadApiGetRelayList( + retryStrategy: REST.RetryStrategy, + etag: String?, + completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.ServerRelaysCacheResponse> + ) -> Cancellable + func getAddressList( retryStrategy: REST.RetryStrategy, completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]> @@ -71,10 +77,56 @@ extension REST { with: responseDecoder ) + return createNetworkOperation( + request: .getAddressList(retryStrategy), + responseHandler: responseHandler, + completionHandler: completionHandler + ) + } + + public func mullvadApiGetRelayList( + retryStrategy: REST.RetryStrategy, + etag: String?, + completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.ServerRelaysCacheResponse> + ) -> Cancellable { + if var etag { + // Enforce weak validator to account for some backend caching quirks. + if etag.starts(with: "\"") { + etag.insert(contentsOf: "W/", at: etag.startIndex) + } + } + + let responseHandler = rustCustomResponseHandler { [weak self] (data, responseEtag) in + // Discarding result since we're only interested in knowing that it's parseable. + let canDecodeResponse = (try? self?.responseDecoder.decode(REST.ServerRelaysResponse.self, from: data)) != nil + + return if canDecodeResponse { + if let responseEtag, responseEtag == etag { + REST.ServerRelaysCacheResponse.notModified + } else { + REST.ServerRelaysCacheResponse.newContent(responseEtag, data) + } + } else { + nil + } + } + + return createNetworkOperation( + request: .getRelayList(retryStrategy, etag: etag), + responseHandler: responseHandler, + completionHandler: completionHandler + ) + } + + private func createNetworkOperation<Success: Decodable>( + request: APIRequest, + responseHandler: RustResponseHandler<Success>, + completionHandler: @escaping @Sendable ProxyCompletionHandler<Success> + ) -> MullvadApiNetworkOperation<Success> { let networkOperation = MullvadApiNetworkOperation( - name: "get-api-addrs", + name: request.name, dispatchQueue: dispatchQueue, - request: .getAddressList(retryStrategy), + request: request, transportProvider: configuration.apiTransportProvider, responseDecoder: responseDecoder, responseHandler: responseHandler, @@ -314,7 +366,7 @@ extension REST { // MARK: - Response types - public enum ServerRelaysCacheResponse: Sendable { + public enum ServerRelaysCacheResponse: Sendable, Decodable { case notModified case newContent(_ etag: String?, _ rawData: Data) } diff --git a/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift b/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift index c6197e983e..e4acc1482c 100644 --- a/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift +++ b/ios/MullvadREST/ApiHandlers/RESTResponseHandler.swift @@ -19,7 +19,7 @@ protocol RESTResponseHandler<Success> { protocol RESTRustResponseHandler<Success> { associatedtype Success - func handleResponse(_ body: Data?) -> REST.ResponseHandlerResult<Success> + func handleResponse(_ resonse: ProxyAPIResponse) -> REST.ResponseHandlerResult<Success> } extension REST { @@ -76,7 +76,7 @@ extension REST { } final class RustResponseHandler<Success>: RESTRustResponseHandler { - typealias HandlerBlock = (Data?) -> REST.ResponseHandlerResult<Success> + typealias HandlerBlock = (ProxyAPIResponse) -> REST.ResponseHandlerResult<Success> private let handlerBlock: HandlerBlock @@ -84,8 +84,8 @@ extension REST { handlerBlock = block } - func handleResponse(_ body: Data?) -> REST.ResponseHandlerResult<Success> { - handlerBlock(body) + func handleResponse(_ response: ProxyAPIResponse) -> REST.ResponseHandlerResult<Success> { + handlerBlock(response) } } @@ -96,8 +96,8 @@ extension REST { decoding type: T.Type, with decoder: JSONDecoder ) -> RustResponseHandler<T> { - RustResponseHandler { data in - guard let data else { + RustResponseHandler { (response: ProxyAPIResponse) in + guard let data = response.data else { return .unhandledResponse(nil) } @@ -109,6 +109,22 @@ extension REST { } } + static func rustCustomResponseHandler<T: Decodable>( + conversion: @escaping (_ data: Data, _ etag: String?) -> T? + ) -> RustResponseHandler<T> { + RustResponseHandler { (response: ProxyAPIResponse) in + guard let data = response.data else { + return .unhandledResponse(nil) + } + + return if let convertedResponse = conversion(data, response.etag) { + .decoding { convertedResponse } + } else { + .unhandledResponse(nil) + } + } + } + /// Response handler for reponses where the body is empty. static func rustEmptyResponseHandler() -> RustResponseHandler<Void> { RustResponseHandler { _ in diff --git a/ios/MullvadREST/APIRequest/APIError.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIError.swift index f62fde619a..f62fde619a 100644 --- a/ios/MullvadREST/APIRequest/APIError.swift +++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIError.swift diff --git a/ios/MullvadREST/APIRequest/APIRequest.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift index 4fff7bd32b..2462a998a7 100644 --- a/ios/MullvadREST/APIRequest/APIRequest.swift +++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift @@ -8,11 +8,21 @@ public enum APIRequest: Codable, Sendable { case getAddressList(_ retryStrategy: REST.RetryStrategy) + case getRelayList(_ retryStrategy: REST.RetryStrategy, etag: String?) + + var name: String { + switch self { + case .getAddressList: + "get-address-list" + case .getRelayList: + "get-relay-lisy" + } + } var retryStrategy: REST.RetryStrategy { switch self { - case let .getAddressList(strategy): - return strategy + case .getAddressList(let strategy), .getRelayList(let strategy, _): + strategy } } } @@ -30,9 +40,11 @@ public struct ProxyAPIRequest: Codable, Sendable { public struct ProxyAPIResponse: Codable, Sendable { public let data: Data? public let error: APIError? + public let etag: String? - public init(data: Data?, error: APIError?) { + public init(data: Data?, error: APIError?, etag: String? = nil) { self.data = data self.error = error + self.etag = etag } } diff --git a/ios/MullvadREST/APIRequest/APIRequestProxy.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequestProxy.swift index 8e2ac4fad2..8e2ac4fad2 100644 --- a/ios/MullvadREST/APIRequest/APIRequestProxy.swift +++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequestProxy.swift diff --git a/ios/MullvadREST/ApiHandlers/MullvadApiNetworkOperation.swift b/ios/MullvadREST/MullvadAPI/MullvadApiNetworkOperation.swift index 68d4ecb0c7..12d9c0346d 100644 --- a/ios/MullvadREST/ApiHandlers/MullvadApiNetworkOperation.swift +++ b/ios/MullvadREST/MullvadAPI/MullvadApiNetworkOperation.swift @@ -78,7 +78,7 @@ extension REST { return } - let decodedResponse = responseHandler.handleResponse(response.data) + let decodedResponse = responseHandler.handleResponse(response) switch decodedResponse { case let .success(value): diff --git a/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift index d361beef1b..fd64408e0e 100644 --- a/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift +++ b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift @@ -18,19 +18,27 @@ public struct MullvadApiRequestFactory: Sendable { public func makeRequest(_ request: APIRequest) -> REST.MullvadApiRequestHandler { { completion in - let pointerClass = MullvadApiCompletion { apiResponse in + let completionPointer = MullvadApiCompletion { apiResponse in try? completion?(apiResponse) } - let rawPointer = Unmanaged.passRetained(pointerClass).toOpaque() + let rawCompletionPointer = Unmanaged.passRetained(completionPointer).toOpaque() return switch request { case let .getAddressList(retryStrategy): MullvadApiCancellable(handle: mullvad_api_get_addresses( apiContext.context, - rawPointer, + rawCompletionPointer, retryStrategy.toRustStrategy() )) + + case let .getRelayList(retryStrategy, etag: etag): + MullvadApiCancellable(handle: mullvad_api_get_relays( + apiContext.context, + rawCompletionPointer, + retryStrategy.toRustStrategy(), + etag + )) } } } diff --git a/ios/MullvadREST/Transport/APITransport.swift b/ios/MullvadREST/Transport/APITransport.swift index 811e775a19..84b76a05af 100644 --- a/ios/MullvadREST/Transport/APITransport.swift +++ b/ios/MullvadREST/Transport/APITransport.swift @@ -44,7 +44,8 @@ public final class APITransport: APITransportProtocol { completion(ProxyAPIResponse( data: response.body, - error: error + error: error, + etag: response.etag )) } } diff --git a/ios/MullvadRustRuntime/MullvadApiResponse.swift b/ios/MullvadRustRuntime/MullvadApiResponse.swift index 7836d43971..ddead026b3 100644 --- a/ios/MullvadRustRuntime/MullvadApiResponse.swift +++ b/ios/MullvadRustRuntime/MullvadApiResponse.swift @@ -25,6 +25,14 @@ public class MullvadApiResponse { return Data(UnsafeBufferPointer(start: body, count: Int(response.body_size))) } + public var etag: String? { + return if response.etag == nil { + nil + } else { + String(cString: response.etag) + } + } + public var errorDescription: String? { return if response.error_description == nil { nil diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h index abdc8ea809..8ebdea8863 100644 --- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h +++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h @@ -24,8 +24,6 @@ typedef struct EncryptedDnsProxyState EncryptedDnsProxyState; typedef struct ExchangeCancelToken ExchangeCancelToken; -typedef struct Option______u8 Option______u8; - typedef struct RequestCancelHandle RequestCancelHandle; typedef struct RetryStrategy RetryStrategy; @@ -119,7 +117,7 @@ struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_co struct SwiftCancelHandle mullvad_api_get_relays(struct SwiftApiContext api_context, void *completion_cookie, struct SwiftRetryStrategy retry_strategy, - struct Option______u8 etag); + const uint8_t *etag); /** * Called by the Swift side to signal that a Mullvad API call should be cancelled. diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 1e623f8bc0..25dbbd2c96 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -2681,8 +2681,8 @@ 06799ABD28F98E1D00ACD94E /* MullvadREST */ = { isa = PBXGroup; children = ( + 7A2C0E872D82E450003D8048 /* MullvadAPI */, F06045F02B2324DA00B2D37A /* ApiHandlers */, - 7A2E7B6B2D6C9E45009EF2C3 /* APIRequest */, 062B45A228FD4C0F00746E77 /* Assets */, 7AD63A422CDA661B00445268 /* Extensions */, 582FFA82290A84E700895745 /* Info.plist */, @@ -4161,6 +4161,16 @@ path = Alert; sourceTree = "<group>"; }; + 7A2C0E872D82E450003D8048 /* MullvadAPI */ = { + isa = PBXGroup; + children = ( + 7A2E7B6B2D6C9E45009EF2C3 /* APIRequest */, + 7AB9312D2D4A5D0A005FCEBA /* MullvadApiNetworkOperation.swift */, + 7A99D36E2D5606F900891FF7 /* MullvadApiRequestFactory.swift */, + ); + path = MullvadAPI; + sourceTree = "<group>"; + }; 7A2E7B6B2D6C9E45009EF2C3 /* APIRequest */ = { isa = PBXGroup; children = ( @@ -4540,7 +4550,6 @@ 06AC114128F8413A0037AF9A /* AddressCache.swift */, A935594B2B4C2DA900D5D524 /* APIAvailabilityTestRequest.swift */, 06FAE67128F83CA40033DD93 /* HTTP.swift */, - 7AB9312D2D4A5D0A005FCEBA /* MullvadApiNetworkOperation.swift */, 06FAE67228F83CA40033DD93 /* RESTAccessTokenManager.swift */, 06FAE66828F83CA30033DD93 /* RESTAccountsProxy.swift */, 06FAE67328F83CA40033DD93 /* RESTAPIProxy.swift */, @@ -4559,7 +4568,6 @@ 06FAE66628F83CA30033DD93 /* RESTResponseHandler.swift */, 06FAE67528F83CA40033DD93 /* RESTTaskIdentifier.swift */, 06FAE66528F83CA30033DD93 /* RESTURLSession.swift */, - 7A99D36E2D5606F900891FF7 /* MullvadApiRequestFactory.swift */, 06FAE67728F83CA40033DD93 /* ServerRelaysResponse.swift */, 06FAE66B28F83CA30033DD93 /* SSLPinningURLSessionDelegate.swift */, ); diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift index e1fd209f95..b3db3ab240 100644 --- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift +++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift @@ -26,7 +26,7 @@ protocol RelayCacheTrackerProtocol: Sendable { final class RelayCacheTracker: RelayCacheTrackerProtocol, @unchecked Sendable { /// Relay update interval. - static let relayUpdateInterval: Duration = .hours(1) + static let relayUpdateInterval: Duration = .seconds(30) /// Tracker log. nonisolated(unsafe) private let logger = Logger(label: "RelayCacheTracker") @@ -174,9 +174,14 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol, @unchecked Sendable { return AnyCancellable() } - return self.apiProxy.getRelays(etag: cachedRelays?.etag, retryStrategy: .noRetry) { result in + return self.apiProxy.getRelays(etag: "hello", retryStrategy: .noRetry) { result in + print(result) finish(self.handleResponse(result: result)) } + +// return self.apiProxy.mullvadApiGetRelayList(retryStrategy: .noRetry, etag: cachedRelays.etag) { result in +// finish(self.handleResponse(result: result)) +// } } operation.addObserver( diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs index 2b34996c23..d3cc30108d 100644 --- a/mullvad-api/src/relay_list.rs +++ b/mullvad-api/src/relay_list.rs @@ -2,13 +2,12 @@ use crate::rest; -use hyper::{body::Incoming, header, Error, StatusCode}; +use hyper::{body::Incoming, header, StatusCode}; use mullvad_types::{location, relay_list}; use talpid_types::net::wireguard; use std::{ collections::BTreeMap, - future::Future, net::{IpAddr, Ipv4Addr, Ipv6Addr}, ops::RangeInclusive, time::Duration, diff --git a/mullvad-ios/src/api_client/api.rs b/mullvad-ios/src/api_client/api.rs index d3c521159f..ef7b18af9b 100644 --- a/mullvad-ios/src/api_client/api.rs +++ b/mullvad-ios/src/api_client/api.rs @@ -1,4 +1,4 @@ -use std::{ffi::CStr, ptr}; +use std::{ffi::CStr, ptr::null}; use mullvad_api::{ rest::{self, MullvadRestHandle}, @@ -77,7 +77,7 @@ pub unsafe extern "C" fn mullvad_api_get_relays( api_context: SwiftApiContext, completion_cookie: *mut libc::c_void, retry_strategy: SwiftRetryStrategy, - etag: Option<*const u8>, + etag: *const u8, ) -> SwiftCancelHandle { let completion_handler = SwiftCompletionHandler::new(CompletionCookie(completion_cookie)); @@ -89,17 +89,15 @@ pub unsafe extern "C" fn mullvad_api_get_relays( let api_context = api_context.into_rust_context(); let retry_strategy = unsafe { retry_strategy.into_rust() }; - let etag = match etag { - Some(etag) => { - let unwrapped_tag = unsafe { CStr::from_ptr(etag.cast()) }.to_str().unwrap(); - Some(String::from(unwrapped_tag)) - }, - None => None, - }; + let mut maybe_etag: Option<String> = None; + if etag != null() { + let unwrapped_tag = unsafe { CStr::from_ptr(etag.cast()) }.to_str().unwrap(); + maybe_etag = Some(String::from(unwrapped_tag)); + } let completion = completion_handler.clone(); let task = tokio_handle.clone().spawn(async move { - match mullvad_api_get_relays_inner(api_context.rest_handle(), retry_strategy, etag).await { + match mullvad_api_get_relays_inner(api_context.rest_handle(), retry_strategy, maybe_etag).await { Ok(response) => completion.finish(response), Err(err) => { log::error!("{err:?}"); @@ -125,6 +123,8 @@ async fn mullvad_api_get_relays_inner( Ok(_) => false, }; + + let response = retry_future(future_factory, should_retry, retry_strategy.delays()).await?; SwiftMullvadApiResponse::with_body(response).await |
