From 5e1b257cf9a4f53dcc248fd3c1798280e97e77ee Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 27 Sep 2023 17:42:31 +0200 Subject: [PATCH] Add setup for writing tests for TunnelManager --- ios/MullvadREST/RESTAPIProxy.swift | 50 ++- ios/MullvadREST/RESTAccessTokenManager.swift | 15 +- ios/MullvadREST/RESTAccountsProxy.swift | 29 +- ios/MullvadREST/RESTAuthenticationProxy.swift | 4 +- ios/MullvadREST/RESTDevicesProxy.swift | 49 ++- ios/MullvadREST/RESTProxy.swift | 12 +- ios/MullvadREST/RESTProxyFactory.swift | 6 +- ios/MullvadTransport/TransportProvider.swift | 4 +- ios/MullvadVPN.xcodeproj/project.pbxproj | 330 +++++++++++++----- .../AddressCacheTracker.swift | 4 +- ios/MullvadVPN/AppDelegate.swift | 6 +- ios/MullvadVPN/Classes/ObserverList.swift | 8 +- .../Coordinators/ApplicationCoordinator.swift | 12 +- .../Coordinators/LoginCoordinator.swift | 4 +- .../Coordinators/WelcomeCoordinator.swift | 4 +- .../RelayCacheTracker/RelayCacheTracker.swift | 18 +- .../SendStoreReceiptOperation.swift | 4 +- .../StorePaymentManager.swift | 8 +- .../PacketTunnelTransport.swift | 4 +- .../TransportMonitor/TransportMonitor.swift | 2 +- .../DeleteAccountOperation.swift | 8 +- .../LoadTunnelConfigurationOperation.swift | 2 +- .../MapConnectionStatusOperation.swift | 4 +- .../RedeemVoucherOperation.swift | 4 +- .../TunnelManager/RotateKeyOperation.swift | 4 +- .../SendTunnelProviderMessageOperation.swift | 8 +- .../TunnelManager/SetAccountOperation.swift | 12 +- .../TunnelManager/StartTunnelOperation.swift | 4 +- .../TunnelManager/Tunnel+Messaging.swift | 2 +- ios/MullvadVPN/TunnelManager/Tunnel.swift | 61 ++-- .../TunnelManager/TunnelInteractor.swift | 8 +- .../TunnelManager/TunnelManager.swift | 44 +-- .../TunnelStatusBlockObserver.swift | 40 +++ .../TunnelManager/TunnelStore.swift | 27 +- .../UIApplication+Extensions.swift | 25 ++ .../UpdateAccountDataOperation.swift | 4 +- .../UpdateDeviceDataOperation.swift | 4 +- .../Account/AccountInteractor.swift | 4 +- .../DeviceManagementInteractor.swift | 4 +- .../ProblemReportInteractor.swift | 4 +- .../RedeemVoucherInteractor.swift | 4 +- .../Settings/SettingsInteractorFactory.swift | 4 +- ios/MullvadVPNTests/APIProxy+Stubs.swift | 64 ++++ .../AccessTokenManager+Stubs.swift | 22 ++ ios/MullvadVPNTests/AccountsProxy+Stubs.swift | 42 +++ ios/MullvadVPNTests/DevicesProxy+Stubs.swift | 59 ++++ .../RESTRequestExecutor+Stubs.swift | 38 ++ .../RelayCacheTracker+Stubs.swift | 33 ++ ios/MullvadVPNTests/RelaySelectorTests.swift | 198 +---------- .../ServerRelaysResponse+Stubs.swift | 210 +++++++++++ ios/MullvadVPNTests/TunnelManagerTests.swift | 33 ++ ios/MullvadVPNTests/TunnelStore+Stubs.swift | 69 ++++ ios/MullvadVPNTests/UIApplication+Stubs.swift | 23 ++ ios/Operations/BackgroundObserver.swift | 5 +- .../DeviceCheckRemoteService.swift | 6 +- .../PacketTunnelProvider/DeviceChecker.swift | 6 +- ios/RelayCache/RelayCache.swift | 7 +- 57 files changed, 1210 insertions(+), 459 deletions(-) create mode 100644 ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift create mode 100644 ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift create mode 100644 ios/MullvadVPNTests/APIProxy+Stubs.swift create mode 100644 ios/MullvadVPNTests/AccessTokenManager+Stubs.swift create mode 100644 ios/MullvadVPNTests/AccountsProxy+Stubs.swift create mode 100644 ios/MullvadVPNTests/DevicesProxy+Stubs.swift create mode 100644 ios/MullvadVPNTests/RESTRequestExecutor+Stubs.swift create mode 100644 ios/MullvadVPNTests/RelayCacheTracker+Stubs.swift create mode 100644 ios/MullvadVPNTests/ServerRelaysResponse+Stubs.swift create mode 100644 ios/MullvadVPNTests/TunnelManagerTests.swift create mode 100644 ios/MullvadVPNTests/TunnelStore+Stubs.swift create mode 100644 ios/MullvadVPNTests/UIApplication+Stubs.swift diff --git a/ios/MullvadREST/RESTAPIProxy.swift b/ios/MullvadREST/RESTAPIProxy.swift index b547d92e45d6..70522f8b03e0 100644 --- a/ios/MullvadREST/RESTAPIProxy.swift +++ b/ios/MullvadREST/RESTAPIProxy.swift @@ -11,8 +11,46 @@ import MullvadTypes import struct WireGuardKitTypes.IPAddressRange import class WireGuardKitTypes.PublicKey +public protocol APIQuerying { + func getAddressList( + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]> + ) -> Cancellable + + func getRelays( + etag: String?, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable + + func createApplePayment( + accountNumber: String, + receiptString: Data + ) -> any RESTRequestExecutor + + func createApplePayment( + accountNumber: String, + receiptString: Data, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable + + func sendProblemReport( + _ body: REST.ProblemReportRequest, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable + + func submitVoucher( + voucherCode: String, + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable +} + extension REST { - public final class APIProxy: Proxy { + public final class APIProxy: Proxy, APIQuerying { public init(configuration: AuthProxyConfiguration) { super.init( name: "APIProxy", @@ -27,7 +65,7 @@ extension REST { public func getAddressList( retryStrategy: REST.RetryStrategy, - completionHandler: @escaping CompletionHandler<[AnyIPEndpoint]> + completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]> ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in try self.requestFactory.createRequest( @@ -54,7 +92,7 @@ extension REST { public func getRelays( etag: String?, retryStrategy: REST.RetryStrategy, - completionHandler: @escaping CompletionHandler + completionHandler: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in var requestBuilder = try self.requestFactory.createRequestBuilder( @@ -170,7 +208,7 @@ extension REST { accountNumber: String, receiptString: Data, retryStrategy: REST.RetryStrategy, - completionHandler: @escaping CompletionHandler + completionHandler: @escaping ProxyCompletionHandler ) -> Cancellable { return createApplePayment(accountNumber: accountNumber, receiptString: receiptString).execute( retryStrategy: retryStrategy, @@ -181,7 +219,7 @@ extension REST { public func sendProblemReport( _ body: ProblemReportRequest, retryStrategy: REST.RetryStrategy, - completionHandler: @escaping CompletionHandler + completionHandler: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in var requestBuilder = try self.requestFactory.createRequestBuilder( @@ -222,7 +260,7 @@ extension REST { voucherCode: String, accountNumber: String, retryStrategy: REST.RetryStrategy, - completionHandler: @escaping CompletionHandler + completionHandler: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in diff --git a/ios/MullvadREST/RESTAccessTokenManager.swift b/ios/MullvadREST/RESTAccessTokenManager.swift index 17c812020d34..79944f907e37 100644 --- a/ios/MullvadREST/RESTAccessTokenManager.swift +++ b/ios/MullvadREST/RESTAccessTokenManager.swift @@ -11,8 +11,17 @@ import MullvadLogging import MullvadTypes import Operations +public protocol AccessTokenManagement { + func getAccessToken( + accountNumber: String, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable + + func invalidateAllTokens() +} + extension REST { - public final class AccessTokenManager { + public final class AccessTokenManager: AccessTokenManagement { private let logger = Logger(label: "REST.AccessTokenManager") private let operationQueue = AsyncOperationQueue.makeSerial() private let dispatchQueue = DispatchQueue(label: "REST.AccessTokenManager.dispatchQueue") @@ -23,9 +32,9 @@ extension REST { proxy = authenticationProxy } - func getAccessToken( + public func getAccessToken( accountNumber: String, - completionHandler: @escaping (Result) -> Void + completionHandler: @escaping ProxyCompletionHandler ) -> Cancellable { let operation = ResultBlockOperation(dispatchQueue: dispatchQueue) { finish -> Cancellable in diff --git a/ios/MullvadREST/RESTAccountsProxy.swift b/ios/MullvadREST/RESTAccountsProxy.swift index 29e41e027e15..78db01a10e3f 100644 --- a/ios/MullvadREST/RESTAccountsProxy.swift +++ b/ios/MullvadREST/RESTAccountsProxy.swift @@ -9,8 +9,29 @@ import Foundation import MullvadTypes +public protocol AccountHandling { + func createAccount( + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable + + func getAccountData(accountNumber: String) -> any RESTRequestExecutor + + func getAccountData( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable + + func deleteAccount( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable +} + extension REST { - public final class AccountsProxy: Proxy { + public final class AccountsProxy: Proxy, AccountHandling { public init(configuration: AuthProxyConfiguration) { super.init( name: "AccountsProxy", @@ -25,7 +46,7 @@ extension REST { public func createAccount( retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in try self.requestFactory.createRequest( @@ -81,7 +102,7 @@ extension REST { public func getAccountData( accountNumber: String, retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { return getAccountData(accountNumber: accountNumber).execute( retryStrategy: retryStrategy, @@ -92,7 +113,7 @@ extension REST { public func deleteAccount( accountNumber: String, retryStrategy: RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler(createURLRequest: { endpoint, authorization in var requestBuilder = try self.requestFactory.createRequestBuilder( diff --git a/ios/MullvadREST/RESTAuthenticationProxy.swift b/ios/MullvadREST/RESTAuthenticationProxy.swift index fb899aa2c146..20b879ca83af 100644 --- a/ios/MullvadREST/RESTAuthenticationProxy.swift +++ b/ios/MullvadREST/RESTAuthenticationProxy.swift @@ -26,7 +26,7 @@ extension REST { func getAccessToken( accountNumber: String, retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in var requestBuilder = try self.requestFactory.createRequestBuilder( @@ -57,7 +57,7 @@ extension REST { } } - struct AccessTokenData: Decodable { + public struct AccessTokenData: Decodable { let accessToken: String let expiry: Date } diff --git a/ios/MullvadREST/RESTDevicesProxy.swift b/ios/MullvadREST/RESTDevicesProxy.swift index f273fac7d5b7..6ec8e299e5a3 100644 --- a/ios/MullvadREST/RESTDevicesProxy.swift +++ b/ios/MullvadREST/RESTDevicesProxy.swift @@ -11,8 +11,45 @@ import MullvadTypes import struct WireGuardKitTypes.IPAddressRange import class WireGuardKitTypes.PublicKey +public protocol DeviceHandling { + func getDevice( + accountNumber: String, + identifier: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable + + func getDevices( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler<[Device]> + ) -> Cancellable + + func createDevice( + accountNumber: String, + request: REST.CreateDeviceRequest, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable + + func deleteDevice( + accountNumber: String, + identifier: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable + + func rotateDeviceKey( + accountNumber: String, + identifier: String, + publicKey: PublicKey, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable +} + extension REST { - public final class DevicesProxy: Proxy { + public final class DevicesProxy: Proxy, DeviceHandling { public init(configuration: AuthProxyConfiguration) { super.init( name: "DevicesProxy", @@ -31,7 +68,7 @@ extension REST { accountNumber: String, identifier: String, retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in @@ -87,7 +124,7 @@ extension REST { public func getDevices( accountNumber: String, retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler<[Device]> + completion: @escaping ProxyCompletionHandler<[Device]> ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in @@ -125,7 +162,7 @@ extension REST { accountNumber: String, request: CreateDeviceRequest, retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in @@ -164,7 +201,7 @@ extension REST { accountNumber: String, identifier: String, retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in @@ -226,7 +263,7 @@ extension REST { identifier: String, publicKey: PublicKey, retryStrategy: REST.RetryStrategy, - completion: @escaping CompletionHandler + completion: @escaping ProxyCompletionHandler ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in diff --git a/ios/MullvadREST/RESTProxy.swift b/ios/MullvadREST/RESTProxy.swift index 4c6d138fd51b..6ca64d85ee46 100644 --- a/ios/MullvadREST/RESTProxy.swift +++ b/ios/MullvadREST/RESTProxy.swift @@ -10,10 +10,10 @@ import Foundation import MullvadTypes import Operations +public typealias ProxyCompletionHandler = (Result) -> Void + extension REST { public class Proxy { - public typealias CompletionHandler = (Result) -> Void - /// Synchronization queue used by network operations. let dispatchQueue: DispatchQueue @@ -49,7 +49,7 @@ extension REST { retryStrategy: REST.RetryStrategy, requestHandler: RESTRequestHandler, responseHandler: some RESTResponseHandler, - completionHandler: @escaping CompletionHandler + completionHandler: @escaping ProxyCompletionHandler ) -> Cancellable { let executor = makeRequestExecutor( name: name, @@ -89,7 +89,7 @@ extension REST { /// Creates new network operation but does not schedule it for execution. func makeOperation( retryStrategy: REST.RetryStrategy, - completionHandler: ((Result) -> Void)? = nil + completionHandler: ProxyCompletionHandler? = nil ) -> NetworkOperation { return NetworkOperation( name: getTaskIdentifier(name: name), @@ -110,7 +110,7 @@ extension REST { func execute( retryStrategy: REST.RetryStrategy, - completionHandler: @escaping (Result) -> Void + completionHandler: @escaping ProxyCompletionHandler ) -> Cancellable { let operation = operationFactory.makeOperation( retryStrategy: retryStrategy, @@ -137,7 +137,7 @@ extension REST { } } - func execute(completionHandler: @escaping (Result) -> Void) -> Cancellable { + func execute(completionHandler: @escaping ProxyCompletionHandler) -> Cancellable { return execute(retryStrategy: .noRetry, completionHandler: completionHandler) } diff --git a/ios/MullvadREST/RESTProxyFactory.swift b/ios/MullvadREST/RESTProxyFactory.swift index 2436cbf3e87d..397bd767ce5d 100644 --- a/ios/MullvadREST/RESTProxyFactory.swift +++ b/ios/MullvadREST/RESTProxyFactory.swift @@ -40,15 +40,15 @@ extension REST { self.configuration = configuration } - public func createAPIProxy() -> REST.APIProxy { + public func createAPIProxy() -> APIQuerying { REST.APIProxy(configuration: configuration) } - public func createAccountsProxy() -> REST.AccountsProxy { + public func createAccountsProxy() -> AccountHandling { REST.AccountsProxy(configuration: configuration) } - public func createDevicesProxy() -> REST.DevicesProxy { + public func createDevicesProxy() -> DeviceHandling { REST.DevicesProxy(configuration: configuration) } } diff --git a/ios/MullvadTransport/TransportProvider.swift b/ios/MullvadTransport/TransportProvider.swift index c2c825f00090..b915bb47a30a 100644 --- a/ios/MullvadTransport/TransportProvider.swift +++ b/ios/MullvadTransport/TransportProvider.swift @@ -15,7 +15,7 @@ import RelaySelector public final class TransportProvider: RESTTransportProvider { private let urlSessionTransport: URLSessionTransport - private let relayCache: RelayCache + private let relayCache: RelayCacheProtocol private let logger = Logger(label: "TransportProvider") private let addressCache: REST.AddressCache private let shadowsocksCache: ShadowsocksConfigurationCache @@ -28,7 +28,7 @@ public final class TransportProvider: RESTTransportProvider { public init( urlSessionTransport: URLSessionTransport, - relayCache: RelayCache, + relayCache: RelayCacheProtocol, addressCache: REST.AddressCache, shadowsocksCache: ShadowsocksConfigurationCache, transportStrategy: TransportStrategy = .init(), diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index c2000d755b68..3a29487fdc49 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -48,12 +48,8 @@ 5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; }; 5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; }; 5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; }; - 5807E2C2243203D000F5FF30 /* StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2C1243203D000F5FF30 /* StringTests.swift */; }; - 5807E2C3243203E700F5FF30 /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; }; 580810E52A30E13A00B74552 /* DeviceStateAccessorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580810E42A30E13A00B74552 /* DeviceStateAccessorProtocol.swift */; }; - 580810E62A30E13D00B74552 /* DeviceStateAccessorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580810E42A30E13A00B74552 /* DeviceStateAccessorProtocol.swift */; }; 580810E82A30E15500B74552 /* DeviceCheckRemoteServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580810E72A30E15500B74552 /* DeviceCheckRemoteServiceProtocol.swift */; }; - 580810E92A30E17300B74552 /* DeviceCheckRemoteServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580810E72A30E15500B74552 /* DeviceCheckRemoteServiceProtocol.swift */; }; 580909D32876D09A0078138D /* RevokedDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580909D22876D09A0078138D /* RevokedDeviceViewController.swift */; }; 58092E542A8B832E00C3CC72 /* TunnelMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58092E532A8B832E00C3CC72 /* TunnelMonitorTests.swift */; }; 580D6B8A2AB31AB400B2D6E0 /* NetworkPath+NetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580D6B892AB31AB400B2D6E0 /* NetworkPath+NetworkReachability.swift */; }; @@ -63,7 +59,6 @@ 5811DE50239014550011EB53 /* NEVPNStatus+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */; }; 58138E61294871C600684F0C /* DeviceDataThrottling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58138E60294871C600684F0C /* DeviceDataThrottling.swift */; }; 58153071294CBE8B00D1702E /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; }; - 58165EBE2A262CBB00688EAD /* WgKeyRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */; }; 5819C2172729595500D6EC38 /* SettingsAddDNSEntryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */; }; 581DA2732A1E227D0046ED47 /* RESTTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581DA2722A1E227D0046ED47 /* RESTTypes.swift */; }; 581DA2752A1E283E0046ED47 /* WgKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581DA2742A1E283E0046ED47 /* WgKeyRotation.swift */; }; @@ -81,7 +76,6 @@ 58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */; }; 58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB025124117005D0BB5 /* CustomTextField.swift */; }; 58293FB3251241B4005D0BB5 /* CustomTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB2251241B3005D0BB5 /* CustomTextView.swift */; }; - 582A8A3A28BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */; }; 582AE3102440A6CA00E6733A /* InputTextFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* InputTextFormatter.swift */; }; 582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1AE229566420055B6EF /* SettingsCell.swift */; }; 582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B0229569620055B6EF /* UINavigationBar+Appearance.swift */; }; @@ -188,9 +182,7 @@ 588E4EAE28FEEDD8008046E3 /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; }; 58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */; }; 58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */; }; - 58915D632A25F8400066445B /* DeviceCheckOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */; }; 58915D682A25FA080066445B /* DeviceCheckRemoteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58915D672A25FA080066445B /* DeviceCheckRemoteService.swift */; }; - 58915D692A2601FB0066445B /* WgKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581DA2742A1E283E0046ED47 /* WgKeyRotation.swift */; }; 58915D6E2A26037A0066445B /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58915D6D2A26037A0066445B /* WireGuardKitTypes */; }; 5891BF1C25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */; }; 5891BF5125E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF5025E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift */; }; @@ -198,7 +190,6 @@ 5893716A28817A45004EE76C /* DeviceManagementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5893716928817A45004EE76C /* DeviceManagementViewController.swift */; }; 58968FAE28743E2000B799DC /* TunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58968FAD28743E2000B799DC /* TunnelInteractor.swift */; }; 5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */; }; - 5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */; }; 5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896CEF126972DEB00B0FAE8 /* AccountContentView.swift */; }; 5897F1742913EAF800AF5695 /* ExponentialBackoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5897F1732913EAF800AF5695 /* ExponentialBackoff.swift */; }; 5898D29F29017DD000EB5EBA /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; }; @@ -222,7 +213,6 @@ 58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */; }; 58AFC99529F96F7B000829DE /* AsyncBlockOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AFC99429F96F7B000829DE /* AsyncBlockOperationTests.swift */; }; 58AFC99729F9753D000829DE /* AsyncResultBlockOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AFC99629F9753D000829DE /* AsyncResultBlockOperationTests.swift */; }; - 58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584B26F3237434D00073B10E /* RelaySelectorTests.swift */; }; 58B26E1E2943514300D5980C /* InAppNotificationDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */; }; 58B26E22294351EA00D5980C /* InAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E21294351EA00D5980C /* InAppNotificationProvider.swift */; }; 58B26E242943520C00D5980C /* NotificationProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E232943520C00D5980C /* NotificationProviderProtocol.swift */; }; @@ -249,7 +239,6 @@ 58B2FDEF2AA720C4003EB5C6 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; }; 58B43C1925F77DB60002C8C3 /* TunnelControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */; }; 58B465702A98C53300467203 /* RequestExecutorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B4656F2A98C53300467203 /* RequestExecutorTests.swift */; }; - 58B8644629C7972F005E107C /* CustomDateComponentsFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */; }; 58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B93A1226C3F13600A55733 /* TunnelState.swift */; }; 58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B993B02608A34500BA7811 /* LoginContentView.swift */; }; 58B9EB152489139B00095626 /* RESTError+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B9EB142489139B00095626 /* RESTError+Display.swift */; }; @@ -261,8 +250,6 @@ 58BFA5CC22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; }; 58C3A4B222456F1B00340BDB /* AccountInputGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */; }; 58C3F4F92964B08300D72515 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3F4F82964B08300D72515 /* MapViewController.swift */; }; - 58C3FA662A38549D006A450A /* MockFileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3FA652A38549D006A450A /* MockFileCache.swift */; }; - 58C3FA682A385C89006A450A /* FileCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3FA672A385C89006A450A /* FileCacheTests.swift */; }; 58C76A082A33850E00100D75 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; }; 58C76A092A33850E00100D75 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; }; 58C76A0B2A338E4300100D75 /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A0A2A338E4300100D75 /* BackgroundTask.swift */; }; @@ -293,10 +280,6 @@ 58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5898D2AD290185D200EB5EBA /* ProxyURLResponse.swift */; }; 58C8191829FAA2C400DEB1B4 /* NotificationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */; }; 58C9B8CE2ABB252E00040B46 /* DeviceCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE65922AB1CDE000E53CB5 /* DeviceCheck.swift */; }; - 58C9B8D02ABB254000040B46 /* DeviceCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE65922AB1CDE000E53CB5 /* DeviceCheck.swift */; }; - 58C9B8D12ABB255100040B46 /* DeviceCheckOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FDF2D82A0BA11900C2B061 /* DeviceCheckOperation.swift */; }; - 58C9B8D22ABB255100040B46 /* DeviceStateAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */; }; - 58C9B8D32ABB255100040B46 /* DeviceCheckRemoteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58915D672A25FA080066445B /* DeviceCheckRemoteService.swift */; }; 58C9B8DA2ABB271D00040B46 /* MullvadTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; }; 58CAFA002983FF0200BE19F7 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9FF2983FF0200BE19F7 /* LoginInteractor.swift */; }; 58CAFA032985367600BE19F7 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAFA01298530DC00BE19F7 /* Promise.swift */; }; @@ -490,13 +473,15 @@ 7AE044BB2A935726003915D8 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A88DCD02A8FABBE00D2FF0E /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AE47E522A17972A000418DA /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE47E512A17972A000418DA /* AlertViewController.swift */; }; 7AF6E5F02A95051E00F2679D /* RouterBlockDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF6E5EF2A95051E00F2679D /* RouterBlockDelegate.swift */; }; - 7AF6E5F12A95F4A500F2679D /* DurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBF0291630700020E046 /* DurationTests.swift */; }; 7AF9BE992A4E0FE900DBFEDB /* MarkdownStylingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */; }; + A900E9B82ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */; }; + A900E9BA2ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */; }; + A900E9BC2ACC609200C95F67 /* DevicesProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */; }; + A900E9BE2ACC654100C95F67 /* APIProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */; }; + A900E9C02ACC661900C95F67 /* AccessTokenManager+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */; }; A917351F29FAA9C400D5DCFD /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */; }; A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; }; A93D13782A1F60A6001EB0B1 /* shadowsocks.h in Headers */ = {isa = PBXBuildFile; fileRef = 586F2BE129F6916F009E6924 /* shadowsocks.h */; settings = {ATTRIBUTES = (Private, ); }; }; - A9467E7F2A29DEFE000DC21F /* RelayCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */; }; - A9467E802A29E0A6000DC21F /* AddressCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */; }; A94D691A2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E22AA72AE9003D1918 /* WireGuardKitTypes */; }; A94D691B2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E72AA7399D003D1918 /* WireGuardKitTypes */; }; A95F86B72A1F53BA00245DAC /* URLSessionTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67C28F83CA50033DD93 /* URLSessionTransport.swift */; }; @@ -505,17 +490,121 @@ A97F1F472A1F4E1A00ECEFDE /* MullvadTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; }; A97F1F482A1F4E1A00ECEFDE /* MullvadTransport.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */; }; + A9A5F9E12ACB05160083449F /* AddressCacheTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC114028F841390037AF9A /* AddressCacheTracker.swift */; }; + A9A5F9E22ACB05160083449F /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A0A2A338E4300100D75 /* BackgroundTask.swift */; }; + A9A5F9E32ACB05160083449F /* AccountDataThrottling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */; }; + A9A5F9E42ACB05160083449F /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04FBE602A8379EE009278D7 /* AppPreferences.swift */; }; + A9A5F9E52ACB05160083449F /* CustomDateComponentsFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */; }; + A9A5F9E62ACB05160083449F /* DeviceDataThrottling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58138E60294871C600684F0C /* DeviceDataThrottling.swift */; }; + A9A5F9E72ACB05160083449F /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; }; + A9A5F9E82ACB05160083449F /* MarkdownStylingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */; }; + A9A5F9E92ACB05160083449F /* ObserverList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CC40EE24A601900019D96E /* ObserverList.swift */; }; + A9A5F9EA2ACB05160083449F /* Bundle+ProductVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */; }; + A9A5F9EB2ACB05160083449F /* CharacterSet+IPAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587EB669270EFACB00123C75 /* CharacterSet+IPAddress.swift */; }; + A9A5F9EC2ACB05160083449F /* CodingErrors+CustomErrorDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E511E528DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift */; }; + A9A5F9ED2ACB05160083449F /* NSRegularExpression+IPAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */; }; + A9A5F9EE2ACB05160083449F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67828F83CA50033DD93 /* RESTCreateApplePaymentResponse+Localization.swift */; }; + A9A5F9EF2ACB05160083449F /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; }; + A9A5F9F02ACB05160083449F /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; }; + A9A5F9F12ACB05160083449F /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; }; + A9A5F9F22ACB05160083449F /* NotificationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */; }; + A9A5F9F32ACB05160083449F /* AccountExpirySystemNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B75402668FD7700DEF7E9 /* AccountExpirySystemNotificationProvider.swift */; }; + A9A5F9F42ACB05160083449F /* AccountExpiryInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */; }; + A9A5F9F52ACB05160083449F /* RegisteredDeviceInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */; }; + A9A5F9F62ACB05160083449F /* TunnelStatusNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */; }; + A9A5F9F72ACB05160083449F /* NotificationProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E232943520C00D5980C /* NotificationProviderProtocol.swift */; }; + A9A5F9F82ACB05160083449F /* NotificationProviderIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */; }; + A9A5F9F92ACB05160083449F /* NotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E252943522400D5980C /* NotificationProvider.swift */; }; + A9A5F9FA2ACB05160083449F /* InAppNotificationDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */; }; + A9A5F9FB2ACB05160083449F /* InAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E21294351EA00D5980C /* InAppNotificationProvider.swift */; }; + A9A5F9FC2ACB05160083449F /* SystemNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E272943527300D5980C /* SystemNotificationProvider.swift */; }; + A9A5F9FD2ACB05160083449F /* NotificationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5877F94D2A0A59AA0052D9E9 /* NotificationResponse.swift */; }; + A9A5F9FE2ACB05160083449F /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B7535266528A200DEF7E9 /* NotificationManager.swift */; }; + A9A5F9FF2ACB05160083449F /* NotificationManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */; }; + A9A5FA002ACB05160083449F /* ProductsRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846226426E0D9630035F7C2 /* ProductsRequestOperation.swift */; }; + A9A5FA012ACB05160083449F /* RelayCacheTrackerObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865926EA214400F188BC /* RelayCacheTrackerObserver.swift */; }; + A9A5FA022ACB05160083449F /* RelayCacheTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5C522A7C97F00A6173D /* RelayCacheTracker.swift */; }; + A9A5FA032ACB05160083449F /* SimulatorTunnelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */; }; + A9A5FA042ACB05160083449F /* SimulatorTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */; }; + A9A5FA052ACB05160083449F /* SimulatorTunnelProviderHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */; }; + A9A5FA062ACB05160083449F /* SimulatorTunnelProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */; }; + A9A5FA072ACB05160083449F /* SimulatorVPNConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */; }; + A9A5FA082ACB05160083449F /* StorePaymentBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27629093A4F0096FC88 /* StorePaymentBlockObserver.swift */; }; + A9A5FA092ACB05160083449F /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; }; + A9A5FA0A2ACB05160083449F /* StorePaymentEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27429093A310096FC88 /* StorePaymentEvent.swift */; }; + A9A5FA0B2ACB05160083449F /* StorePaymentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF28A42417CB4B00E836B0 /* StorePaymentManager.swift */; }; + A9A5FA0C2ACB05160083449F /* StorePaymentManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227626E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift */; }; + A9A5FA0D2ACB05160083449F /* StorePaymentManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */; }; + A9A5FA0E2ACB05160083449F /* StorePaymentObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227226E22A160035F7C2 /* StorePaymentObserver.swift */; }; + A9A5FA0F2ACB05160083449F /* StoreSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227026E229F20035F7C2 /* StoreSubscription.swift */; }; + A9A5FA102ACB05160083449F /* PacketTunnelTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063687B928EB234F00BE7161 /* PacketTunnelTransport.swift */; }; + A9A5FA112ACB05160083449F /* TransportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0697D6E628F01513007A9E99 /* TransportMonitor.swift */; }; + A9A5FA122ACB05160083449F /* DeleteAccountOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C6FA802A66E23300F521F0 /* DeleteAccountOperation.swift */; }; + A9A5FA132ACB05160083449F /* LoadTunnelConfigurationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588527B1276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift */; }; + A9A5FA142ACB05160083449F /* MapConnectionStatusOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2E147276A307400A79513 /* MapConnectionStatusOperation.swift */; }; + A9A5FA152ACB05160083449F /* RedeemVoucherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */; }; + A9A5FA162ACB05160083449F /* RotateKeyOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2E14B276A61C000A79513 /* RotateKeyOperation.swift */; }; + A9A5FA172ACB05160083449F /* SendTunnelProviderMessageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */; }; + A9A5FA182ACB05160083449F /* SetAccountOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588527B3276B4F2F00BAA373 /* SetAccountOperation.swift */; }; + A9A5FA192ACB05160083449F /* StartTunnelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2E143276A13F300A79513 /* StartTunnelOperation.swift */; }; + A9A5FA1A2ACB05160083449F /* StopTunnelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F2E145276A2C9900A79513 /* StopTunnelOperation.swift */; }; + A9A5FA1B2ACB05160083449F /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0A98727C8F46300FE6BDD /* Tunnel.swift */; }; + A9A5FA1C2ACB05160083449F /* Tunnel+Messaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */; }; + A9A5FA1D2ACB05160083449F /* TunnelBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27229091D6D0096FC88 /* TunnelBlockObserver.swift */; }; + A9A5FA1E2ACB05160083449F /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; }; + A9A5FA1F2ACB05160083449F /* TunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58968FAD28743E2000B799DC /* TunnelInteractor.swift */; }; + A9A5FA202ACB05160083449F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; }; + A9A5FA212ACB05160083449F /* TunnelManagerErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820676326E771DB00655B05 /* TunnelManagerErrors.swift */; }; + A9A5FA222ACB05160083449F /* TunnelObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA5326CE49F600283BF8 /* TunnelObserver.swift */; }; + A9A5FA232ACB05160083449F /* TunnelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B93A1226C3F13600A55733 /* TunnelState.swift */; }; + A9A5FA242ACB05160083449F /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; }; + A9A5FA252ACB05160083449F /* UpdateAccountDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5842102F282D8A3C00F24E46 /* UpdateAccountDataOperation.swift */; }; + A9A5FA262ACB05160083449F /* UpdateDeviceDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */; }; + A9A5FA272ACB05160083449F /* VPNConnectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */; }; + A9A5FA282ACB05160083449F /* WgKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581DA2742A1E283E0046ED47 /* WgKeyRotation.swift */; }; + A9A5FA292ACB05160083449F /* AddressCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */; }; + A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */; }; + A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */; }; + A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */; }; + A9A5FA2D2ACB05160083449F /* DurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBF0291630700020E046 /* DurationTests.swift */; }; + A9A5FA2E2ACB05160083449F /* FileCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3FA672A385C89006A450A /* FileCacheTests.swift */; }; + A9A5FA2F2ACB05160083449F /* FixedWidthIntegerArithmeticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */; }; + A9A5FA302ACB05160083449F /* InputTextFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */; }; + A9A5FA312ACB05160083449F /* MockFileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3FA652A38549D006A450A /* MockFileCache.swift */; }; + A9A5FA322ACB05160083449F /* RelayCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */; }; + A9A5FA332ACB05160083449F /* RelaySelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584B26F3237434D00073B10E /* RelaySelectorTests.swift */; }; + A9A5FA342ACB05160083449F /* StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2C1243203D000F5FF30 /* StringTests.swift */; }; + A9A5FA352ACB05160083449F /* WgKeyRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */; }; + A9A5FA362ACB05160083449F /* TunnelManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */; }; + A9A5FA372ACB052D0083449F /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; }; + A9A5FA382ACB05600083449F /* InputTextFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* InputTextFormatter.swift */; }; + A9A5FA392ACB05910083449F /* UIColor+Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CCA0152242560B004F3011 /* UIColor+Palette.swift */; }; + A9A5FA3A2ACB05910083449F /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */; }; + A9A5FA3B2ACB05910083449F /* UIMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585CA70E25F8C44600B47C62 /* UIMetrics.swift */; }; + A9A5FA3C2ACB05B20083449F /* NSAttributedString+Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584EBDBC2747C98F00A0C9FD /* NSAttributedString+Markdown.swift */; }; + A9A5FA3D2ACB05D90083449F /* DeviceCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE65922AB1CDE000E53CB5 /* DeviceCheck.swift */; }; + A9A5FA3E2ACB05D90083449F /* DeviceCheckOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FDF2D82A0BA11900C2B061 /* DeviceCheckOperation.swift */; }; + A9A5FA3F2ACB05D90083449F /* DeviceCheckRemoteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58915D672A25FA080066445B /* DeviceCheckRemoteService.swift */; }; + A9A5FA402ACB05D90083449F /* DeviceCheckRemoteServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580810E72A30E15500B74552 /* DeviceCheckRemoteServiceProtocol.swift */; }; + A9A5FA412ACB05D90083449F /* DeviceStateAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */; }; + A9A5FA422ACB05D90083449F /* DeviceStateAccessorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580810E42A30E13A00B74552 /* DeviceStateAccessorProtocol.swift */; }; + A9A5FA432ACB05F20083449F /* UIColor+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */; }; A9A8A8EB2A262AB30086D569 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A8A8EA2A262AB30086D569 /* FileCache.swift */; }; - A9AD31D72A6AB68B00141BE8 /* InputTextFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* InputTextFormatter.swift */; }; A9B2CF722A1F64CD0013CC6C /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; }; + A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */; }; + A9C342C32ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */; }; + A9C342C52ACC42130045F00E /* ServerRelaysResponse+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */; }; A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */; }; A9D99BA02A1F7F3A00DE27D3 /* TransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */; }; A9D99BA52A1F808900DE27D3 /* RelayCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 063F02732902B63F001FA09F /* RelayCache.framework */; }; A9D99BA62A1F809C00DE27D3 /* libRelaySelector.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5898D29829017DAC00EB5EBA /* libRelaySelector.a */; }; A9D99BA92A1F81B700DE27D3 /* MullvadTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; }; + A9E031782ACB09930095D843 /* UIApplication+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E031762ACB08950095D843 /* UIApplication+Extensions.swift */; }; + A9E0317A2ACB0AE70095D843 /* UIApplication+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */; }; + A9E0317C2ACBFC7E0095D843 /* TunnelStore+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */; }; + A9E0317F2ACC331C0095D843 /* TunnelStatusBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */; }; A9E034642ABB302000E59A5A /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */; }; A9EC20E62A5C488D0040D56E /* Haversine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EC20E52A5C488D0040D56E /* Haversine.swift */; }; - A9EC20E82A5D3A8C0040D56E /* CoordinatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */; }; A9EC20F02A5D79ED0040D56E /* TunnelObfuscation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A9EC20F42A5D96030040D56E /* Midpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EC20F32A5D96030040D56E /* Midpoint.swift */; }; E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; }; @@ -526,7 +615,6 @@ F028A56C2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */; }; F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */; }; F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04FBE602A8379EE009278D7 /* AppPreferences.swift */; }; - F07BF2582A26112D00042943 /* InputTextFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */; }; F07BF2622A26279100042943 /* RedeemVoucherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */; }; F07CFF2029F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */; }; F09A297B2A9F8A9B00EA3B6F /* LogoutDialogueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29782A9F8A9B00EA3B6F /* LogoutDialogueView.swift */; }; @@ -603,13 +691,6 @@ remoteGlobalIDString = 5840231E2A406BF5007B27AC; remoteInfo = TunnelObfuscation; }; - 586A0DCD2A20E359006C731C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 58CE5E58224146200008646E /* Project object */; - proxyType = 1; - remoteGlobalIDString = 58D223D4294C8E5E0029F5F8; - remoteInfo = MullvadTypes; - }; 586A0DD62A20E4A9006C731C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 58CE5E58224146200008646E /* Project object */; @@ -687,13 +768,6 @@ remoteGlobalIDString = 06799ABB28F98E1D00ACD94E; remoteInfo = MullvadREST; }; - 58D2239F294C89B50029F5F8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 58CE5E58224146200008646E /* Project object */; - proxyType = 1; - remoteGlobalIDString = 06799ABB28F98E1D00ACD94E; - remoteInfo = MullvadREST; - }; 58D223C1294C8AE90029F5F8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 58CE5E58224146200008646E /* Project object */; @@ -1460,6 +1534,11 @@ 7AE47E512A17972A000418DA /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = ""; }; 7AF6E5EF2A95051E00F2679D /* RouterBlockDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterBlockDelegate.swift; sourceTree = ""; }; 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownStylingOptions.swift; sourceTree = ""; }; + A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountsProxy+Stubs.swift"; sourceTree = ""; }; + A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RESTRequestExecutor+Stubs.swift"; sourceTree = ""; }; + A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevicesProxy+Stubs.swift"; sourceTree = ""; }; + A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIProxy+Stubs.swift"; sourceTree = ""; }; + A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessTokenManager+Stubs.swift"; sourceTree = ""; }; A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportStrategy.swift; sourceTree = ""; }; A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportStrategyTests.swift; sourceTree = ""; }; A92ECC202A77FFAF0052F1B1 /* TunnelSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettings.swift; sourceTree = ""; }; @@ -1472,10 +1551,17 @@ A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadTransport.h; sourceTree = ""; }; A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFileCoordinator+Extensions.swift"; sourceTree = ""; }; + A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManagerTests.swift; sourceTree = ""; }; A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = ""; }; + A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelayCacheTracker+Stubs.swift"; sourceTree = ""; }; + A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerRelaysResponse+Stubs.swift"; sourceTree = ""; }; A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = ""; }; A9D96B192A8247C100A5C673 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportProvider.swift; sourceTree = ""; }; + A9E031762ACB08950095D843 /* UIApplication+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extensions.swift"; sourceTree = ""; }; + A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Stubs.swift"; sourceTree = ""; }; + A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelStore+Stubs.swift"; sourceTree = ""; }; + A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusBlockObserver.swift; sourceTree = ""; }; A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = ""; }; A9EC20E52A5C488D0040D56E /* Haversine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Haversine.swift; sourceTree = ""; }; A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = ""; }; @@ -1848,6 +1934,8 @@ 58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */, A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */, 581DA2742A1E283E0046ED47 /* WgKeyRotation.swift */, + A9E031762ACB08950095D843 /* UIApplication+Extensions.swift */, + A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */, ); path = TunnelManager; sourceTree = ""; @@ -2324,19 +2412,29 @@ 58B0A2A1238EE67E00BC001D /* MullvadVPNTests */ = { isa = PBXGroup; children = ( - 58B0A2A4238EE67E00BC001D /* Info.plist */, + A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */, + A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */, A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */, + A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */, A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */, 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */, 58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */, + A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */, 58FBFBF0291630700020E046 /* DurationTests.swift */, 58C3FA672A385C89006A450A /* FileCacheTests.swift */, 582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */, + 58B0A2A4238EE67E00BC001D /* Info.plist */, F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */, 58C3FA652A38549D006A450A /* MockFileCache.swift */, A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */, + A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */, 584B26F3237434D00073B10E /* RelaySelectorTests.swift */, + A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */, + A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */, 5807E2C1243203D000F5FF30 /* StringTests.swift */, + A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */, + A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */, + A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */, 58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */, ); path = MullvadVPNTests; @@ -3113,8 +3211,6 @@ buildRules = ( ); dependencies = ( - 58915D6C2A2603700066445B /* PBXTargetDependency */, - 062B45BF28FDA85D00746E77 /* PBXTargetDependency */, 06410DFA292C4ABC00AFC18C /* PBXTargetDependency */, ); name = MullvadVPNTests; @@ -3293,7 +3389,6 @@ ); dependencies = ( 58EED36E29FBEF040000CBAF /* PBXTargetDependency */, - 586A0DCE2A20E359006C731C /* PBXTargetDependency */, ); name = Operations; productName = Operations; @@ -3876,29 +3971,115 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 58C9B8D22ABB255100040B46 /* DeviceStateAccessor.swift in Sources */, - A9AD31D72A6AB68B00141BE8 /* InputTextFormatter.swift in Sources */, - 58915D692A2601FB0066445B /* WgKeyRotation.swift in Sources */, - 580810E62A30E13D00B74552 /* DeviceStateAccessorProtocol.swift in Sources */, - 58C3FA662A38549D006A450A /* MockFileCache.swift in Sources */, - 7AF6E5F12A95F4A500F2679D /* DurationTests.swift in Sources */, - A9467E7F2A29DEFE000DC21F /* RelayCacheTests.swift in Sources */, - 582A8A3A28BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift in Sources */, - 58915D632A25F8400066445B /* DeviceCheckOperationTests.swift in Sources */, - 5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */, - 58B8644629C7972F005E107C /* CustomDateComponentsFormatting.swift in Sources */, - 5807E2C2243203D000F5FF30 /* StringTests.swift in Sources */, - 58C3FA682A385C89006A450A /* FileCacheTests.swift in Sources */, - 58165EBE2A262CBB00688EAD /* WgKeyRotationTests.swift in Sources */, - 5807E2C3243203E700F5FF30 /* String+Split.swift in Sources */, - 58C9B8D02ABB254000040B46 /* DeviceCheck.swift in Sources */, - 580810E92A30E17300B74552 /* DeviceCheckRemoteServiceProtocol.swift in Sources */, - F07BF2582A26112D00042943 /* InputTextFormatterTests.swift in Sources */, - A9EC20E82A5D3A8C0040D56E /* CoordinatesTests.swift in Sources */, - 58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */, - A9467E802A29E0A6000DC21F /* AddressCacheTests.swift in Sources */, - 58C9B8D32ABB255100040B46 /* DeviceCheckRemoteService.swift in Sources */, - 58C9B8D12ABB255100040B46 /* DeviceCheckOperation.swift in Sources */, + A9A5FA432ACB05F20083449F /* UIColor+Helpers.swift in Sources */, + A9A5FA3D2ACB05D90083449F /* DeviceCheck.swift in Sources */, + A900E9B82ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift in Sources */, + A9A5FA3E2ACB05D90083449F /* DeviceCheckOperation.swift in Sources */, + A9A5FA3F2ACB05D90083449F /* DeviceCheckRemoteService.swift in Sources */, + A9A5FA402ACB05D90083449F /* DeviceCheckRemoteServiceProtocol.swift in Sources */, + A9A5FA412ACB05D90083449F /* DeviceStateAccessor.swift in Sources */, + A9A5FA422ACB05D90083449F /* DeviceStateAccessorProtocol.swift in Sources */, + A9A5FA3C2ACB05B20083449F /* NSAttributedString+Markdown.swift in Sources */, + A9A5FA392ACB05910083449F /* UIColor+Palette.swift in Sources */, + A9A5FA3A2ACB05910083449F /* UIEdgeInsets+Extensions.swift in Sources */, + A9C342C52ACC42130045F00E /* ServerRelaysResponse+Stubs.swift in Sources */, + A9A5FA3B2ACB05910083449F /* UIMetrics.swift in Sources */, + A9A5FA382ACB05600083449F /* InputTextFormatter.swift in Sources */, + A9A5FA372ACB052D0083449F /* ApplicationTarget.swift in Sources */, + A9A5F9E12ACB05160083449F /* AddressCacheTracker.swift in Sources */, + A900E9BC2ACC609200C95F67 /* DevicesProxy+Stubs.swift in Sources */, + A9A5F9E22ACB05160083449F /* BackgroundTask.swift in Sources */, + A9A5F9E32ACB05160083449F /* AccountDataThrottling.swift in Sources */, + A9A5F9E42ACB05160083449F /* AppPreferences.swift in Sources */, + A9A5F9E52ACB05160083449F /* CustomDateComponentsFormatting.swift in Sources */, + A9A5F9E62ACB05160083449F /* DeviceDataThrottling.swift in Sources */, + A9A5F9E72ACB05160083449F /* FirstTimeLaunch.swift in Sources */, + A9A5F9E82ACB05160083449F /* MarkdownStylingOptions.swift in Sources */, + A9A5F9E92ACB05160083449F /* ObserverList.swift in Sources */, + A9A5F9EA2ACB05160083449F /* Bundle+ProductVersion.swift in Sources */, + A9A5F9EB2ACB05160083449F /* CharacterSet+IPAddress.swift in Sources */, + A9A5F9EC2ACB05160083449F /* CodingErrors+CustomErrorDescription.swift in Sources */, + A9A5F9ED2ACB05160083449F /* NSRegularExpression+IPAddress.swift in Sources */, + A9A5F9EE2ACB05160083449F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */, + A9A5F9EF2ACB05160083449F /* String+AccountFormatting.swift in Sources */, + A9A5F9F02ACB05160083449F /* String+FuzzyMatch.swift in Sources */, + A9A5F9F12ACB05160083449F /* String+Split.swift in Sources */, + A9A5F9F22ACB05160083449F /* NotificationConfiguration.swift in Sources */, + A9A5F9F32ACB05160083449F /* AccountExpirySystemNotificationProvider.swift in Sources */, + A9A5F9F42ACB05160083449F /* AccountExpiryInAppNotificationProvider.swift in Sources */, + A9A5F9F52ACB05160083449F /* RegisteredDeviceInAppNotificationProvider.swift in Sources */, + A9A5F9F62ACB05160083449F /* TunnelStatusNotificationProvider.swift in Sources */, + A9A5F9F72ACB05160083449F /* NotificationProviderProtocol.swift in Sources */, + A9A5F9F82ACB05160083449F /* NotificationProviderIdentifier.swift in Sources */, + A9A5F9F92ACB05160083449F /* NotificationProvider.swift in Sources */, + A9A5F9FA2ACB05160083449F /* InAppNotificationDescriptor.swift in Sources */, + A9A5F9FB2ACB05160083449F /* InAppNotificationProvider.swift in Sources */, + A9A5F9FC2ACB05160083449F /* SystemNotificationProvider.swift in Sources */, + A9A5F9FD2ACB05160083449F /* NotificationResponse.swift in Sources */, + A9A5F9FE2ACB05160083449F /* NotificationManager.swift in Sources */, + A9A5F9FF2ACB05160083449F /* NotificationManagerDelegate.swift in Sources */, + A900E9BE2ACC654100C95F67 /* APIProxy+Stubs.swift in Sources */, + A900E9BA2ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift in Sources */, + A9A5FA002ACB05160083449F /* ProductsRequestOperation.swift in Sources */, + A9A5FA012ACB05160083449F /* RelayCacheTrackerObserver.swift in Sources */, + A9A5FA022ACB05160083449F /* RelayCacheTracker.swift in Sources */, + A9A5FA032ACB05160083449F /* SimulatorTunnelInfo.swift in Sources */, + A9A5FA042ACB05160083449F /* SimulatorTunnelProvider.swift in Sources */, + A9A5FA052ACB05160083449F /* SimulatorTunnelProviderHost.swift in Sources */, + A900E9C02ACC661900C95F67 /* AccessTokenManager+Stubs.swift in Sources */, + A9E0317A2ACB0AE70095D843 /* UIApplication+Stubs.swift in Sources */, + A9A5FA062ACB05160083449F /* SimulatorTunnelProviderManager.swift in Sources */, + A9A5FA072ACB05160083449F /* SimulatorVPNConnection.swift in Sources */, + A9A5FA082ACB05160083449F /* StorePaymentBlockObserver.swift in Sources */, + A9E0317C2ACBFC7E0095D843 /* TunnelStore+Stubs.swift in Sources */, + A9A5FA092ACB05160083449F /* SendStoreReceiptOperation.swift in Sources */, + A9A5FA0A2ACB05160083449F /* StorePaymentEvent.swift in Sources */, + A9A5FA0B2ACB05160083449F /* StorePaymentManager.swift in Sources */, + A9A5FA0C2ACB05160083449F /* StorePaymentManagerDelegate.swift in Sources */, + A9A5FA0D2ACB05160083449F /* StorePaymentManagerError.swift in Sources */, + A9A5FA0E2ACB05160083449F /* StorePaymentObserver.swift in Sources */, + A9A5FA0F2ACB05160083449F /* StoreSubscription.swift in Sources */, + A9A5FA102ACB05160083449F /* PacketTunnelTransport.swift in Sources */, + A9A5FA112ACB05160083449F /* TransportMonitor.swift in Sources */, + A9A5FA122ACB05160083449F /* DeleteAccountOperation.swift in Sources */, + A9A5FA132ACB05160083449F /* LoadTunnelConfigurationOperation.swift in Sources */, + A9A5FA142ACB05160083449F /* MapConnectionStatusOperation.swift in Sources */, + A9A5FA152ACB05160083449F /* RedeemVoucherOperation.swift in Sources */, + A9A5FA162ACB05160083449F /* RotateKeyOperation.swift in Sources */, + A9A5FA172ACB05160083449F /* SendTunnelProviderMessageOperation.swift in Sources */, + A9A5FA182ACB05160083449F /* SetAccountOperation.swift in Sources */, + A9A5FA192ACB05160083449F /* StartTunnelOperation.swift in Sources */, + A9A5FA1A2ACB05160083449F /* StopTunnelOperation.swift in Sources */, + A9A5FA1B2ACB05160083449F /* Tunnel.swift in Sources */, + A9A5FA1C2ACB05160083449F /* Tunnel+Messaging.swift in Sources */, + A9A5FA1D2ACB05160083449F /* TunnelBlockObserver.swift in Sources */, + A9A5FA1E2ACB05160083449F /* TunnelConfiguration.swift in Sources */, + A9A5FA1F2ACB05160083449F /* TunnelInteractor.swift in Sources */, + A9A5FA202ACB05160083449F /* TunnelManager.swift in Sources */, + A9A5FA212ACB05160083449F /* TunnelManagerErrors.swift in Sources */, + A9C342C32ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift in Sources */, + A9A5FA222ACB05160083449F /* TunnelObserver.swift in Sources */, + A9E0317F2ACC331C0095D843 /* TunnelStatusBlockObserver.swift in Sources */, + A9A5FA232ACB05160083449F /* TunnelState.swift in Sources */, + A9A5FA242ACB05160083449F /* TunnelStore.swift in Sources */, + A9A5FA252ACB05160083449F /* UpdateAccountDataOperation.swift in Sources */, + A9A5FA262ACB05160083449F /* UpdateDeviceDataOperation.swift in Sources */, + A9A5FA272ACB05160083449F /* VPNConnectionProtocol.swift in Sources */, + A9A5FA282ACB05160083449F /* WgKeyRotation.swift in Sources */, + A9A5FA292ACB05160083449F /* AddressCacheTests.swift in Sources */, + A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */, + A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */, + A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */, + A9A5FA2D2ACB05160083449F /* DurationTests.swift in Sources */, + A9A5FA2E2ACB05160083449F /* FileCacheTests.swift in Sources */, + A9A5FA2F2ACB05160083449F /* FixedWidthIntegerArithmeticsTests.swift in Sources */, + A9A5FA302ACB05160083449F /* InputTextFormatterTests.swift in Sources */, + A9A5FA312ACB05160083449F /* MockFileCache.swift in Sources */, + A9A5FA322ACB05160083449F /* RelayCacheTests.swift in Sources */, + A9A5FA332ACB05160083449F /* RelaySelectorTests.swift in Sources */, + A9A5FA342ACB05160083449F /* StringTests.swift in Sources */, + A9A5FA352ACB05160083449F /* WgKeyRotationTests.swift in Sources */, + A9A5FA362ACB05160083449F /* TunnelManagerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4196,6 +4377,7 @@ 5875960A26F371FC00BF6711 /* Tunnel+Messaging.swift in Sources */, 7A2960F62A963F7500389B82 /* AlertCoordinator.swift in Sources */, 063687BA28EB234F00BE7161 /* PacketTunnelTransport.swift in Sources */, + A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */, 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */, 5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */, 5871167F2910035700D41AAC /* PreferencesInteractor.swift in Sources */, @@ -4301,6 +4483,7 @@ 7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */, 58D2240A294C90210029F5F8 /* IPAddress+Codable.swift in Sources */, 58E45A5729F12C5100281ECF /* Result+Extensions.swift in Sources */, + A9E031782ACB09930095D843 /* UIApplication+Extensions.swift in Sources */, 58D2240B294C90210029F5F8 /* Cancellable.swift in Sources */, 58D2240C294C90210029F5F8 /* WrappingError.swift in Sources */, A9A8A8EB2A262AB30086D569 /* FileCache.swift in Sources */, @@ -4390,11 +4573,6 @@ target = 06799ABB28F98E1D00ACD94E /* MullvadREST */; targetProxy = 58D2239E294C89B50029F5F8 /* PBXContainerItemProxy */; }; - 062B45BF28FDA85D00746E77 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 06799ABB28F98E1D00ACD94E /* MullvadREST */; - targetProxy = 58D2239F294C89B50029F5F8 /* PBXContainerItemProxy */; - }; 063F02782902B63F001FA09F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 063F02722902B63F001FA09F /* RelayCache */; @@ -4435,20 +4613,11 @@ target = 5840231E2A406BF5007B27AC /* TunnelObfuscation */; targetProxy = 58695AA22A4ADA9200328DB3 /* PBXContainerItemProxy */; }; - 586A0DCE2A20E359006C731C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */; - targetProxy = 586A0DCD2A20E359006C731C /* PBXContainerItemProxy */; - }; 586A0DD72A20E4A9006C731C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 06799ABB28F98E1D00ACD94E /* MullvadREST */; targetProxy = 586A0DD62A20E4A9006C731C /* PBXContainerItemProxy */; }; - 58915D6C2A2603700066445B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = 58915D6B2A2603700066445B /* WireGuardKitTypes */; - }; 58B2FDD82AA71D2A003EB5C6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 58B2FDD22AA71D2A003EB5C6 /* MullvadSettings */; @@ -6188,11 +6357,6 @@ package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; productName = WireGuardKitTypes; }; - 58915D6B2A2603700066445B /* WireGuardKitTypes */ = { - isa = XCSwiftPackageProductDependency; - package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; - productName = WireGuardKitTypes; - }; 58915D6D2A26037A0066445B /* WireGuardKitTypes */ = { isa = XCSwiftPackageProductDependency; package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; diff --git a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift index 8c1dc2b25894..2734729c46a4 100644 --- a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift +++ b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift @@ -24,7 +24,7 @@ final class AddressCacheTracker { private let application: UIApplication /// REST API proxy. - private let apiProxy: REST.APIProxy + private let apiProxy: APIQuerying /// Address cache. private let store: REST.AddressCache @@ -45,7 +45,7 @@ final class AddressCacheTracker { private let nslock = NSLock() /// Designated initializer - init(application: UIApplication, apiProxy: REST.APIProxy, store: REST.AddressCache) { + init(application: UIApplication, apiProxy: APIQuerying, store: REST.AddressCache) { self.application = application self.apiProxy = apiProxy self.store = store diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 4bbbff665765..b33ba9f997de 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -33,9 +33,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD private(set) var addressCache: REST.AddressCache! private var proxyFactory: REST.ProxyFactory! - private(set) var apiProxy: REST.APIProxy! - private(set) var accountsProxy: REST.AccountsProxy! - private(set) var devicesProxy: REST.DevicesProxy! + private(set) var apiProxy: APIQuerying! + private(set) var accountsProxy: AccountHandling! + private(set) var devicesProxy: DeviceHandling! private(set) var addressCacheTracker: AddressCacheTracker! private(set) var relayCacheTracker: RelayCacheTracker! diff --git a/ios/MullvadVPN/Classes/ObserverList.swift b/ios/MullvadVPN/Classes/ObserverList.swift index 70ace90b69bc..45123d39a4a9 100644 --- a/ios/MullvadVPN/Classes/ObserverList.swift +++ b/ios/MullvadVPN/Classes/ObserverList.swift @@ -23,8 +23,8 @@ struct WeakBox { } } - static func == (lhs: WeakBox, rhs: T) -> Bool { - (lhs.value as AnyObject) === (rhs as AnyObject) + static func == (lhs: WeakBox, rhs: WeakBox) -> Bool { + (lhs.value as AnyObject) === (rhs.value as AnyObject) } } @@ -36,7 +36,7 @@ final class ObserverList { lock.lock() let hasObserver = observers.contains { box in - box == observer + box == WeakBox(observer) } if !hasObserver { @@ -50,7 +50,7 @@ final class ObserverList { lock.lock() let index = observers.firstIndex { box in - box == observer + box == WeakBox(observer) } if let index { diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift index 12c4e00478ee..3243b5d9bb56 100644 --- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift @@ -72,9 +72,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo private let storePaymentManager: StorePaymentManager private let relayCacheTracker: RelayCacheTracker - private let apiProxy: REST.APIProxy - private let devicesProxy: REST.DevicesProxy - private let accountsProxy: REST.AccountsProxy + private let apiProxy: APIQuerying + private let devicesProxy: DeviceHandling + private let accountsProxy: AccountHandling private var tunnelObserver: TunnelObserver? private var appPreferences: AppPreferencesDataSource @@ -88,9 +88,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo tunnelManager: TunnelManager, storePaymentManager: StorePaymentManager, relayCacheTracker: RelayCacheTracker, - apiProxy: REST.APIProxy, - devicesProxy: REST.DevicesProxy, - accountsProxy: REST.AccountsProxy, + apiProxy: APIQuerying, + devicesProxy: DeviceHandling, + accountsProxy: AccountHandling, appPreferences: AppPreferencesDataSource ) { self.tunnelManager = tunnelManager diff --git a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift index f62869f27177..42ddd0e2a7c7 100644 --- a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift @@ -15,7 +15,7 @@ import UIKit final class LoginCoordinator: Coordinator, Presenting, DeviceManagementViewControllerDelegate { private let tunnelManager: TunnelManager - private let devicesProxy: REST.DevicesProxy + private let devicesProxy: DeviceHandling private var loginController: LoginViewController? private var lastLoginAction: LoginAction? @@ -34,7 +34,7 @@ final class LoginCoordinator: Coordinator, Presenting, DeviceManagementViewContr init( navigationController: RootContainerViewController, tunnelManager: TunnelManager, - devicesProxy: REST.DevicesProxy + devicesProxy: DeviceHandling ) { self.navigationController = navigationController self.tunnelManager = tunnelManager diff --git a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift index a614dd780f53..d9ff6305c8ed 100644 --- a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift @@ -17,7 +17,7 @@ final class WelcomeCoordinator: Coordinator, Poppable, Presenting { private let storePaymentManager: StorePaymentManager private let tunnelManager: TunnelManager private let inAppPurchaseInteractor: InAppPurchaseInteractor - private let accountsProxy: REST.AccountsProxy + private let accountsProxy: AccountHandling private var viewController: WelcomeViewController? @@ -32,7 +32,7 @@ final class WelcomeCoordinator: Coordinator, Poppable, Presenting { navigationController: RootContainerViewController, storePaymentManager: StorePaymentManager, tunnelManager: TunnelManager, - accountsProxy: REST.AccountsProxy + accountsProxy: AccountHandling ) { self.navigationController = navigationController self.storePaymentManager = storePaymentManager diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift index 5a16d473a16a..7f17a5b8da9d 100644 --- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift +++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift @@ -14,7 +14,17 @@ import Operations import RelayCache import UIKit -final class RelayCacheTracker { +protocol RelayCacheTrackerProtocol { + func startPeriodicUpdates() + func stopPeriodicUpdates() + func updateRelays(completionHandler: ((Result) -> Void)?) -> Cancellable + func getCachedRelays() throws -> CachedRelays + func getNextUpdateDate() -> Date + func addObserver(_ observer: RelayCacheTrackerObserver) + func removeObserver(_ observer: RelayCacheTrackerObserver) +} + +final class RelayCacheTracker: RelayCacheTrackerProtocol { /// Relay update interval. static let relayUpdateInterval: Duration = .hours(1) @@ -22,7 +32,7 @@ final class RelayCacheTracker { private let logger = Logger(label: "RelayCacheTracker") /// Relay cache. - private let cache: RelayCache + private let cache: RelayCacheProtocol private let application: UIApplication @@ -39,7 +49,7 @@ final class RelayCacheTracker { private var isPeriodicUpdatesEnabled = false /// API proxy. - private let apiProxy: REST.APIProxy + private let apiProxy: APIQuerying /// Observers. private let observerList = ObserverList() @@ -47,7 +57,7 @@ final class RelayCacheTracker { /// Memory cache. private var cachedRelays: CachedRelays? - init(relayCache: RelayCache, application: UIApplication, apiProxy: REST.APIProxy) { + init(relayCache: RelayCacheProtocol, application: UIApplication, apiProxy: APIQuerying) { self.application = application self.apiProxy = apiProxy cache = relayCache diff --git a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift index f2822ec79e20..d29bde3d9db4 100644 --- a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift +++ b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift @@ -14,7 +14,7 @@ import Operations import StoreKit class SendStoreReceiptOperation: ResultOperation, SKRequestDelegate { - private let apiProxy: REST.APIProxy + private let apiProxy: APIQuerying private let accountNumber: String private let forceRefresh: Bool @@ -26,7 +26,7 @@ class SendStoreReceiptOperation: ResultOperation() private weak var classDelegate: StorePaymentManagerDelegate? @@ -66,8 +66,8 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver { init( application: UIApplication, queue: SKPaymentQueue, - apiProxy: REST.APIProxy, - accountsProxy: REST.AccountsProxy + apiProxy: APIQuerying, + accountsProxy: AccountHandling ) { self.application = application paymentQueue = queue diff --git a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift index c9621be8ac2f..15af753a6a3d 100644 --- a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift +++ b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift @@ -18,9 +18,9 @@ struct PacketTunnelTransport: RESTTransport { "packet-tunnel" } - let tunnel: Tunnel + let tunnel: any TunnelProtocol - init(tunnel: Tunnel) { + init(tunnel: any TunnelProtocol) { self.tunnel = tunnel } diff --git a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift index 99a59cc1165c..a89a02977ca7 100644 --- a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift +++ b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift @@ -47,7 +47,7 @@ final class TransportMonitor: RESTTransportProvider { } } - private func shouldByPassVPN(tunnel: Tunnel) -> Bool { + private func shouldByPassVPN(tunnel: any TunnelProtocol) -> Bool { switch tunnel.status { case .connected: return tunnelManager.isConfigurationLoaded && tunnelManager.deviceState == .revoked diff --git a/ios/MullvadVPN/TunnelManager/DeleteAccountOperation.swift b/ios/MullvadVPN/TunnelManager/DeleteAccountOperation.swift index ad4463d5622a..9e4ff4a27daf 100644 --- a/ios/MullvadVPN/TunnelManager/DeleteAccountOperation.swift +++ b/ios/MullvadVPN/TunnelManager/DeleteAccountOperation.swift @@ -16,14 +16,14 @@ class DeleteAccountOperation: ResultOperation { private let logger = Logger(label: "\(DeleteAccountOperation.self)") private let accountNumber: String - private let accountsProxy: REST.AccountsProxy - private let accessTokenManager: REST.AccessTokenManager + private let accountsProxy: AccountHandling + private let accessTokenManager: AccessTokenManagement private var task: Cancellable? init( dispatchQueue: DispatchQueue, - accountsProxy: REST.AccountsProxy, - accessTokenManager: REST.AccessTokenManager, + accountsProxy: AccountHandling, + accessTokenManager: AccessTokenManagement, accountNumber: String ) { self.accountNumber = accountNumber diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift index 74881d49694d..2eb1c89f3b88 100644 --- a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift +++ b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift @@ -51,7 +51,7 @@ class LoadTunnelConfigurationOperation: ResultOperation { } } - private func finishOperation(tunnel: Tunnel?) { + private func finishOperation(tunnel: (any TunnelProtocol)?) { interactor.setTunnel(tunnel, shouldRefreshTunnelState: true) interactor.setConfigurationLoaded() diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift index 78541be833b7..5282a2a7d57a 100644 --- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift @@ -94,7 +94,7 @@ class MapConnectionStatusOperation: AsyncOperation { request?.cancel() } - private func handleConnectingState(_ tunnelState: TunnelState, _ tunnel: Tunnel) { + private func handleConnectingState(_ tunnelState: TunnelState, _ tunnel: any TunnelProtocol) { switch tunnelState { case .connecting: break @@ -160,7 +160,7 @@ class MapConnectionStatusOperation: AsyncOperation { } private func fetchTunnelStatus( - tunnel: Tunnel, + tunnel: any TunnelProtocol, mapToState: @escaping (PacketTunnelStatus) -> TunnelState? ) { request = tunnel.getTunnelStatus { [weak self] completion in diff --git a/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift b/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift index 7fab35b2e4be..99d77a1204a0 100644 --- a/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift +++ b/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift @@ -18,14 +18,14 @@ class RedeemVoucherOperation: ResultOperation { private let interactor: TunnelInteractor private let voucherCode: String - private let apiProxy: REST.APIProxy + private let apiProxy: APIQuerying private var task: Cancellable? init( dispatchQueue: DispatchQueue, interactor: TunnelInteractor, voucherCode: String, - apiProxy: REST.APIProxy + apiProxy: APIQuerying ) { self.interactor = interactor self.voucherCode = voucherCode diff --git a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift index b40968d380c5..46b340ec9d5e 100644 --- a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift +++ b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift @@ -17,10 +17,10 @@ import class WireGuardKitTypes.PrivateKey class RotateKeyOperation: ResultOperation { private let logger = Logger(label: "RotateKeyOperation") private let interactor: TunnelInteractor - private let devicesProxy: REST.DevicesProxy + private let devicesProxy: DeviceHandling private var task: Cancellable? - init(dispatchQueue: DispatchQueue, interactor: TunnelInteractor, devicesProxy: REST.DevicesProxy) { + init(dispatchQueue: DispatchQueue, interactor: TunnelInteractor, devicesProxy: DeviceHandling) { self.interactor = interactor self.devicesProxy = devicesProxy diff --git a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift index d34e51bdbfd6..cb9afb10dd01 100644 --- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift @@ -24,7 +24,7 @@ final class SendTunnelProviderMessageOperation: ResultOperation typealias DecoderHandler = (Data?) throws -> Output private let application: UIApplication - private let tunnel: Tunnel + private let tunnel: any TunnelProtocol private let message: TunnelProviderMessage private let timeout: Duration @@ -39,7 +39,7 @@ final class SendTunnelProviderMessageOperation: ResultOperation init( dispatchQueue: DispatchQueue, application: UIApplication, - tunnel: Tunnel, + tunnel: any TunnelProtocol, message: TunnelProviderMessage, timeout: Duration? = nil, decoderHandler: @escaping DecoderHandler, @@ -219,7 +219,7 @@ extension SendTunnelProviderMessageOperation where Output: Codable { convenience init( dispatchQueue: DispatchQueue, application: UIApplication, - tunnel: Tunnel, + tunnel: any TunnelProtocol, message: TunnelProviderMessage, timeout: Duration? = nil, completionHandler: @escaping CompletionHandler @@ -246,7 +246,7 @@ extension SendTunnelProviderMessageOperation where Output == Void { convenience init( dispatchQueue: DispatchQueue, application: UIApplication, - tunnel: Tunnel, + tunnel: any TunnelProtocol, message: TunnelProviderMessage, timeout: Duration? = nil, completionHandler: CompletionHandler? diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift index ee805c9d92c9..a39f1f517d10 100644 --- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift @@ -39,10 +39,10 @@ enum SetAccountAction { class SetAccountOperation: ResultOperation { private let interactor: TunnelInteractor - private let accountsProxy: REST.AccountsProxy - private let devicesProxy: REST.DevicesProxy + private let accountsProxy: AccountHandling + private let devicesProxy: DeviceHandling private let action: SetAccountAction - private let accessTokenManager: REST.AccessTokenManager + private let accessTokenManager: AccessTokenManagement private let logger = Logger(label: "SetAccountOperation") private var tasks: [Cancellable] = [] @@ -50,9 +50,9 @@ class SetAccountOperation: ResultOperation { init( dispatchQueue: DispatchQueue, interactor: TunnelInteractor, - accountsProxy: REST.AccountsProxy, - devicesProxy: REST.DevicesProxy, - accessTokenManager: REST.AccessTokenManager, + accountsProxy: AccountHandling, + devicesProxy: DeviceHandling, + accessTokenManager: AccessTokenManagement, action: SetAccountAction ) { self.interactor = interactor diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift index 9404d02cde4f..ac0dcc5843b9 100644 --- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift @@ -85,7 +85,7 @@ class StartTunnelOperation: ResultOperation { } } - private func startTunnel(tunnel: Tunnel, selectorResult: RelaySelectorResult) throws { + private func startTunnel(tunnel: any TunnelProtocol, selectorResult: RelaySelectorResult) throws { var tunnelOptions = PacketTunnelOptions() do { @@ -108,7 +108,7 @@ class StartTunnelOperation: ResultOperation { try tunnel.start(options: tunnelOptions.rawOptions()) } - private func makeTunnelProvider(completionHandler: @escaping (Result) -> Void) { + private func makeTunnelProvider(completionHandler: @escaping (Result) -> Void) { let persistentTunnels = interactor.getPersistentTunnels() let tunnel = persistentTunnels.first ?? interactor.createNewTunnel() let configuration = Self.makeTunnelConfiguration() diff --git a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift index ff5704428b13..0181b1985529 100644 --- a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift +++ b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift @@ -22,7 +22,7 @@ private let dispatchQueue = DispatchQueue(label: "Tunnel.dispatchQueue") /// Timeout for proxy requests. private let proxyRequestTimeout = REST.defaultAPINetworkTimeout + 2 -extension Tunnel { +extension TunnelProtocol { /// Request packet tunnel process to reconnect the tunnel with the given relay selector result. /// Packet tunnel will reconnect to the current relay if relay selector result is not provided. func reconnectTunnel( diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift index 5a9990c8862d..161dc06913b9 100644 --- a/ios/MullvadVPN/TunnelManager/Tunnel.swift +++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift @@ -17,11 +17,34 @@ typealias TunnelProviderManagerType = NETunnelProviderManager #endif protocol TunnelStatusObserver { - func tunnel(_ tunnel: Tunnel, didReceiveStatus status: NEVPNStatus) + func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus) +} + +protocol TunnelProtocol: AnyObject, Equatable { + func addObserver(_ observer: TunnelStatusObserver) + func removeObserver(_ observer: TunnelStatusObserver) + var status: NEVPNStatus { get } + var isOnDemandEnabled: Bool { get set } + var startDate: Date? { get } + + func addBlockObserver( + queue: DispatchQueue?, + handler: @escaping (any TunnelProtocol, NEVPNStatus) -> Void + ) -> TunnelStatusBlockObserver + + func logFormat() -> String + + func saveToPreferences(_ completion: @escaping (Error?) -> Void) + func removeFromPreferences(completion: @escaping (Error?) -> Void) + + func setConfiguration(_ configuration: TunnelConfiguration) + func start(options: [String: NSObject]?) throws + func stop() + func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws } /// Tunnel wrapper class. -final class Tunnel: Equatable { +final class Tunnel: TunnelProtocol, Equatable { /// Unique identifier assigned to instance at the time of creation. let identifier = UUID() @@ -85,7 +108,7 @@ final class Tunnel: Equatable { private var observerList = ObserverList() private var _startDate: Date? - private let tunnelProvider: TunnelProviderManagerType + internal let tunnelProvider: TunnelProviderManagerType init(tunnelProvider: TunnelProviderManagerType) { self.tunnelProvider = tunnelProvider @@ -137,7 +160,7 @@ final class Tunnel: Equatable { func addBlockObserver( queue: DispatchQueue? = nil, - handler: @escaping (Tunnel, NEVPNStatus) -> Void + handler: @escaping (any TunnelProtocol, NEVPNStatus) -> Void ) -> TunnelStatusBlockObserver { let observer = TunnelStatusBlockObserver(tunnel: self, queue: queue, handler: handler) @@ -197,33 +220,3 @@ final class Tunnel: Equatable { lhs.tunnelProvider == rhs.tunnelProvider } } - -final class TunnelStatusBlockObserver: TunnelStatusObserver { - typealias Handler = (Tunnel, NEVPNStatus) -> Void - - private weak var tunnel: Tunnel? - private let queue: DispatchQueue? - private let handler: Handler - - fileprivate init(tunnel: Tunnel, queue: DispatchQueue?, handler: @escaping Handler) { - self.tunnel = tunnel - self.queue = queue - self.handler = handler - } - - func invalidate() { - tunnel?.removeObserver(self) - } - - func tunnel(_ tunnel: Tunnel, didReceiveStatus status: NEVPNStatus) { - let block = { - self.handler(tunnel, status) - } - - if let queue { - queue.async(execute: block) - } else { - block() - } - } -} diff --git a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift index d5f9d9ae8bce..a21413960d97 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift @@ -14,11 +14,11 @@ import RelaySelector protocol TunnelInteractor { // MARK: - Tunnel manipulation - var tunnel: Tunnel? { get } + var tunnel: (any TunnelProtocol)? { get } - func getPersistentTunnels() -> [Tunnel] - func createNewTunnel() -> Tunnel - func setTunnel(_ tunnel: Tunnel?, shouldRefreshTunnelState: Bool) + func getPersistentTunnels() -> [any TunnelProtocol] + func createNewTunnel() -> any TunnelProtocol + func setTunnel(_ tunnel: (any TunnelProtocol)?, shouldRefreshTunnelState: Bool) // MARK: - Tunnel status diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index 1f27c66395de..9b36bba5c5ac 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -44,13 +44,13 @@ final class TunnelManager: StorePaymentObserver { // MARK: - Internal variables - private let application: UIApplication - fileprivate let tunnelStore: TunnelStore - private let relayCacheTracker: RelayCacheTracker - private let accountsProxy: REST.AccountsProxy - private let devicesProxy: REST.DevicesProxy - private let apiProxy: REST.APIProxy - private let accessTokenManager: REST.AccessTokenManager + private let application: UIApplicationProtocol + fileprivate let tunnelStore: TunnelStoreProtocol + private let relayCacheTracker: RelayCacheTrackerProtocol + private let accountsProxy: AccountHandling + private let devicesProxy: DeviceHandling + private let apiProxy: APIQuerying + private let accessTokenManager: AccessTokenManagement private let logger = Logger(label: "TunnelManager") private var nslock = NSRecursiveLock() @@ -72,7 +72,7 @@ final class TunnelManager: StorePaymentObserver { private var _deviceState: DeviceState = .loggedOut private var _tunnelSettings = LatestTunnelSettings() - private var _tunnel: Tunnel? + private var _tunnel: (any TunnelProtocol)? private var _tunnelStatus = TunnelStatus() /// Last processed device check. @@ -81,13 +81,13 @@ final class TunnelManager: StorePaymentObserver { // MARK: - Initialization init( - application: UIApplication, - tunnelStore: TunnelStore, - relayCacheTracker: RelayCacheTracker, - accountsProxy: REST.AccountsProxy, - devicesProxy: REST.DevicesProxy, - apiProxy: REST.APIProxy, - accessTokenManager: REST.AccessTokenManager + application: UIApplicationProtocol, + tunnelStore: TunnelStoreProtocol, + relayCacheTracker: RelayCacheTrackerProtocol, + accountsProxy: AccountHandling, + devicesProxy: DeviceHandling, + apiProxy: APIQuerying, + accessTokenManager: AccessTokenManagement ) { self.application = application self.tunnelStore = tunnelStore @@ -623,7 +623,7 @@ final class TunnelManager: StorePaymentObserver { return _isConfigurationLoaded } - fileprivate var tunnel: Tunnel? { + fileprivate var tunnel: (any TunnelProtocol)? { nslock.lock() defer { nslock.unlock() } @@ -668,7 +668,7 @@ final class TunnelManager: StorePaymentObserver { } } - fileprivate func setTunnel(_ tunnel: Tunnel?, shouldRefreshTunnelState: Bool) { + fileprivate func setTunnel(_ tunnel: (any TunnelProtocol)?, shouldRefreshTunnelState: Bool) { nslock.lock() defer { nslock.unlock() } @@ -862,7 +862,7 @@ final class TunnelManager: StorePaymentObserver { } } - private func subscribeVPNStatusObserver(tunnel: Tunnel) { + private func subscribeVPNStatusObserver(tunnel: any TunnelProtocol) { nslock.lock() defer { nslock.unlock() } @@ -1250,19 +1250,19 @@ private struct TunnelInteractorProxy: TunnelInteractor { self.tunnelManager = tunnelManager } - var tunnel: Tunnel? { + var tunnel: (any TunnelProtocol)? { tunnelManager.tunnel } - func getPersistentTunnels() -> [Tunnel] { + func getPersistentTunnels() -> [any TunnelProtocol] { tunnelManager.tunnelStore.getPersistentTunnels() } - func createNewTunnel() -> Tunnel { + func createNewTunnel() -> any TunnelProtocol { tunnelManager.tunnelStore.createNewTunnel() } - func setTunnel(_ tunnel: Tunnel?, shouldRefreshTunnelState: Bool) { + func setTunnel(_ tunnel: (any TunnelProtocol)?, shouldRefreshTunnelState: Bool) { tunnelManager.setTunnel(tunnel, shouldRefreshTunnelState: shouldRefreshTunnelState) } diff --git a/ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift b/ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift new file mode 100644 index 000000000000..135e79220c7a --- /dev/null +++ b/ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift @@ -0,0 +1,40 @@ +// +// TunnelStatusBlockObserver.swift +// MullvadVPN +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import NetworkExtension + +final class TunnelStatusBlockObserver: TunnelStatusObserver { + typealias Handler = (any TunnelProtocol, NEVPNStatus) -> Void + + private weak var tunnel: (any TunnelProtocol)? + private let queue: DispatchQueue? + private let handler: Handler + + init(tunnel: any TunnelProtocol, queue: DispatchQueue?, handler: @escaping Handler) { + self.tunnel = tunnel + self.queue = queue + self.handler = handler + } + + func invalidate() { + tunnel?.removeObserver(self) + } + + func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus) { + let block = { + self.handler(tunnel, status) + } + + if let queue { + queue.async(execute: block) + } else { + block() + } + } +} diff --git a/ios/MullvadVPN/TunnelManager/TunnelStore.swift b/ios/MullvadVPN/TunnelManager/TunnelStore.swift index 07266bcf170d..f750ea9c4f8e 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelStore.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelStore.swift @@ -11,16 +11,21 @@ import MullvadLogging import NetworkExtension import UIKit +protocol TunnelStoreProtocol { + func getPersistentTunnels() -> [any TunnelProtocol] + func createNewTunnel() -> any TunnelProtocol +} + /// Wrapper around system VPN tunnels. -final class TunnelStore: TunnelStatusObserver { +final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver { private let logger = Logger(label: "TunnelStore") private let lock = NSLock() /// Persistent tunnels registered with the system. - private var persistentTunnels: [Tunnel] = [] + private var persistentTunnels: [any TunnelProtocol] = [] /// Newly created tunnels, stored as collection of weak boxes. - private var newTunnels: [WeakBox] = [] + private var newTunnels: [WeakBox] = [] init(application: UIApplication) { NotificationCenter.default.addObserver( @@ -31,7 +36,7 @@ final class TunnelStore: TunnelStatusObserver { ) } - func getPersistentTunnels() -> [Tunnel] { + func getPersistentTunnels() -> [any TunnelProtocol] { lock.lock() defer { lock.unlock() } @@ -66,7 +71,7 @@ final class TunnelStore: TunnelStatusObserver { } } - func createNewTunnel() -> Tunnel { + func createNewTunnel() -> any TunnelProtocol { lock.lock() defer { lock.unlock() } @@ -82,20 +87,24 @@ final class TunnelStore: TunnelStatusObserver { return tunnel } - func tunnel(_ tunnel: Tunnel, didReceiveStatus status: NEVPNStatus) { + func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus) { lock.lock() defer { lock.unlock() } handleTunnelStatus(tunnel: tunnel, status: status) } - private func handleTunnelStatus(tunnel: Tunnel, status: NEVPNStatus) { - if status == .invalid, let index = persistentTunnels.firstIndex(of: tunnel) { + private func handleTunnelStatus(tunnel: any TunnelProtocol, status: NEVPNStatus) { + guard let tunnel = tunnel as? Tunnel else { return } + + if status == .invalid, + let index = persistentTunnels.map({ $0 as? Tunnel }).firstIndex(of: tunnel) { persistentTunnels.remove(at: index) logger.debug("Persistent tunnel was removed: \(tunnel.logFormat()).") } - if status != .invalid, let index = newTunnels.firstIndex(where: { $0.value == tunnel }) { + if status != .invalid, + let index = newTunnels.map({ $0.value as? Tunnel }).firstIndex(where: { $0 == tunnel }) { newTunnels.remove(at: index) persistentTunnels.append(tunnel) logger.debug("New tunnel became persistent: \(tunnel.logFormat()).") diff --git a/ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift b/ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift new file mode 100644 index 000000000000..945a6c1ff675 --- /dev/null +++ b/ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift @@ -0,0 +1,25 @@ +// +// UIApplication+Extensions.swift +// MullvadVPN +// +// Created by Marco Nikic on 2023-10-02. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +#if canImport(UIKit) + +import Foundation +import UIKit + +public protocol UIApplicationProtocol { + func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) + + func beginBackgroundTask( + withName taskName: String?, + expirationHandler handler: (() -> Void)? + ) -> UIBackgroundTaskIdentifier +} + +extension UIApplication: UIApplicationProtocol {} + +#endif diff --git a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift index 2b880ab1c915..468da64e42df 100644 --- a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift +++ b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift @@ -16,13 +16,13 @@ import Operations class UpdateAccountDataOperation: ResultOperation { private let logger = Logger(label: "UpdateAccountDataOperation") private let interactor: TunnelInteractor - private let accountsProxy: REST.AccountsProxy + private let accountsProxy: AccountHandling private var task: Cancellable? init( dispatchQueue: DispatchQueue, interactor: TunnelInteractor, - accountsProxy: REST.AccountsProxy + accountsProxy: AccountHandling ) { self.interactor = interactor self.accountsProxy = accountsProxy diff --git a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift index 85700d8dca01..6803dd7d87c4 100644 --- a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift +++ b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift @@ -16,14 +16,14 @@ import class WireGuardKitTypes.PublicKey class UpdateDeviceDataOperation: ResultOperation { private let interactor: TunnelInteractor - private let devicesProxy: REST.DevicesProxy + private let devicesProxy: DeviceHandling private var task: Cancellable? init( dispatchQueue: DispatchQueue, interactor: TunnelInteractor, - devicesProxy: REST.DevicesProxy + devicesProxy: DeviceHandling ) { self.interactor = interactor self.devicesProxy = devicesProxy diff --git a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift index a08bd10064e1..eccf03659967 100644 --- a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift +++ b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift @@ -16,7 +16,7 @@ import StoreKit final class AccountInteractor { private let storePaymentManager: StorePaymentManager let tunnelManager: TunnelManager - let accountsProxy: REST.AccountsProxy + let accountsProxy: AccountHandling var didReceivePaymentEvent: ((StorePaymentEvent) -> Void)? var didReceiveDeviceState: ((DeviceState) -> Void)? @@ -27,7 +27,7 @@ final class AccountInteractor { init( storePaymentManager: StorePaymentManager, tunnelManager: TunnelManager, - accountsProxy: REST.AccountsProxy + accountsProxy: AccountHandling ) { self.storePaymentManager = storePaymentManager self.tunnelManager = tunnelManager diff --git a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift index 5f9065a00696..581b5e0d0a11 100644 --- a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift +++ b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift @@ -12,10 +12,10 @@ import MullvadTypes import Operations class DeviceManagementInteractor { - private let devicesProxy: REST.DevicesProxy + private let devicesProxy: DeviceHandling private let accountNumber: String - init(accountNumber: String, devicesProxy: REST.DevicesProxy) { + init(accountNumber: String, devicesProxy: DeviceHandling) { self.accountNumber = accountNumber self.devicesProxy = devicesProxy } diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift index b1bb49ccb2b8..9dbf3a5b6b3f 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift @@ -12,7 +12,7 @@ import MullvadTypes import Operations final class ProblemReportInteractor { - private let apiProxy: REST.APIProxy + private let apiProxy: APIQuerying private let tunnelManager: TunnelManager private lazy var consolidatedLog: ConsolidatedApplicationLog = { @@ -31,7 +31,7 @@ final class ProblemReportInteractor { return report }() - init(apiProxy: REST.APIProxy, tunnelManager: TunnelManager) { + init(apiProxy: APIQuerying, tunnelManager: TunnelManager) { self.apiProxy = apiProxy self.tunnelManager = tunnelManager } diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift index fa7639122718..adf42f970348 100644 --- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift +++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift @@ -12,7 +12,7 @@ import MullvadTypes final class RedeemVoucherInteractor { private let tunnelManager: TunnelManager - private let accountsProxy: REST.AccountsProxy + private let accountsProxy: AccountHandling private let shouldVerifyVoucherAsAccount: Bool private var tasks: [Cancellable] = [] @@ -23,7 +23,7 @@ final class RedeemVoucherInteractor { init( tunnelManager: TunnelManager, - accountsProxy: REST.AccountsProxy, + accountsProxy: AccountHandling, verifyVoucherAsAccount: Bool ) { self.tunnelManager = tunnelManager diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift index 2af17ec14748..3454516808fc 100644 --- a/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift +++ b/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift @@ -13,13 +13,13 @@ import RelayCache final class SettingsInteractorFactory { private let storePaymentManager: StorePaymentManager private let tunnelManager: TunnelManager - private let apiProxy: REST.APIProxy + private let apiProxy: APIQuerying private let relayCacheTracker: RelayCacheTracker init( storePaymentManager: StorePaymentManager, tunnelManager: TunnelManager, - apiProxy: REST.APIProxy, + apiProxy: APIQuerying, relayCacheTracker: RelayCacheTracker ) { self.storePaymentManager = storePaymentManager diff --git a/ios/MullvadVPNTests/APIProxy+Stubs.swift b/ios/MullvadVPNTests/APIProxy+Stubs.swift new file mode 100644 index 000000000000..7602a1fe7aa2 --- /dev/null +++ b/ios/MullvadVPNTests/APIProxy+Stubs.swift @@ -0,0 +1,64 @@ +// +// APIProxy+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +@testable import MullvadREST +@testable import MullvadTypes +@testable import WireGuardKitTypes + +struct APIProxyStub: APIQuerying { + func getAddressList( + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]> + ) -> Cancellable { + AnyCancellable() + } + + func getRelays( + etag: String?, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func createApplePayment( + accountNumber: String, + receiptString: Data + ) -> any RESTRequestExecutor { + RESTRequestExecutorStub(success: { + .timeAdded(42, .distantFuture) + }) + } + + func createApplePayment( + accountNumber: String, + receiptString: Data, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func sendProblemReport( + _ body: REST.ProblemReportRequest, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func submitVoucher( + voucherCode: String, + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } +} diff --git a/ios/MullvadVPNTests/AccessTokenManager+Stubs.swift b/ios/MullvadVPNTests/AccessTokenManager+Stubs.swift new file mode 100644 index 000000000000..74c71435c62e --- /dev/null +++ b/ios/MullvadVPNTests/AccessTokenManager+Stubs.swift @@ -0,0 +1,22 @@ +// +// AccessTokenManager+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +@testable import MullvadREST +@testable import MullvadTypes + +struct AccessTokenManagerStub: AccessTokenManagement { + func getAccessToken( + accountNumber: String, + completionHandler: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func invalidateAllTokens() {} +} diff --git a/ios/MullvadVPNTests/AccountsProxy+Stubs.swift b/ios/MullvadVPNTests/AccountsProxy+Stubs.swift new file mode 100644 index 000000000000..dc2174168a2b --- /dev/null +++ b/ios/MullvadVPNTests/AccountsProxy+Stubs.swift @@ -0,0 +1,42 @@ +// +// AccountsProxy+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +@testable import MullvadREST +@testable import MullvadTypes + +struct AccountsProxyStub: AccountHandling { + func createAccount( + retryStrategy: REST.RetryStrategy, + completion: @escaping MullvadREST.ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func getAccountData(accountNumber: String) -> any RESTRequestExecutor { + RESTRequestExecutorStub(success: { + Account(id: accountNumber, expiry: .distantFuture, maxDevices: 1, canAddDevices: true) + }) + } + + func getAccountData( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func deleteAccount( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } +} diff --git a/ios/MullvadVPNTests/DevicesProxy+Stubs.swift b/ios/MullvadVPNTests/DevicesProxy+Stubs.swift new file mode 100644 index 000000000000..3ec0f3faff59 --- /dev/null +++ b/ios/MullvadVPNTests/DevicesProxy+Stubs.swift @@ -0,0 +1,59 @@ +// +// DevicesProxy+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +@testable import MullvadREST +@testable import MullvadTypes +@testable import WireGuardKitTypes + +struct DevicesProxyStub: DeviceHandling { + func getDevice( + accountNumber: String, + identifier: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func getDevices( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler<[Device]> + ) -> Cancellable { + AnyCancellable() + } + + func createDevice( + accountNumber: String, + request: REST.CreateDeviceRequest, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func deleteDevice( + accountNumber: String, + identifier: String, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } + + func rotateDeviceKey( + accountNumber: String, + identifier: String, + publicKey: PublicKey, + retryStrategy: REST.RetryStrategy, + completion: @escaping ProxyCompletionHandler + ) -> Cancellable { + AnyCancellable() + } +} diff --git a/ios/MullvadVPNTests/RESTRequestExecutor+Stubs.swift b/ios/MullvadVPNTests/RESTRequestExecutor+Stubs.swift new file mode 100644 index 000000000000..cd7fad6c76aa --- /dev/null +++ b/ios/MullvadVPNTests/RESTRequestExecutor+Stubs.swift @@ -0,0 +1,38 @@ +// +// RESTRequestExecutor+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +@testable import MullvadREST +@testable import MullvadTypes + +struct RESTRequestExecutorStub: RESTRequestExecutor { + typealias Success = Success + + var success: (() -> Success)? + + func execute(completionHandler: @escaping (Result) -> Void) -> Cancellable { + AnyCancellable() + } + + func execute( + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping (Result) -> Void + ) -> Cancellable { + AnyCancellable() + } + + func execute() async throws -> Success { + try await execute(retryStrategy: .noRetry) + } + + func execute(retryStrategy: REST.RetryStrategy) async throws -> Success { + guard let success = success else { throw POSIXError(.EINVAL) } + + return success() + } +} diff --git a/ios/MullvadVPNTests/RelayCacheTracker+Stubs.swift b/ios/MullvadVPNTests/RelayCacheTracker+Stubs.swift new file mode 100644 index 000000000000..ef6fcd9ab6b2 --- /dev/null +++ b/ios/MullvadVPNTests/RelayCacheTracker+Stubs.swift @@ -0,0 +1,33 @@ +// +// RelayCacheTracker+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +@testable import MullvadTypes +@testable import RelayCache + +struct RelayCacheTrackerStub: RelayCacheTrackerProtocol { + func startPeriodicUpdates() {} + + func stopPeriodicUpdates() {} + + func updateRelays(completionHandler: ((Result) -> Void)?) -> Cancellable { + AnyCancellable() + } + + func getCachedRelays() throws -> CachedRelays { + CachedRelays(relays: ServerRelaysResponseStubs.sampleRelays, updatedAt: Date()) + } + + func getNextUpdateDate() -> Date { + Date() + } + + func addObserver(_ observer: RelayCacheTrackerObserver) {} + + func removeObserver(_ observer: RelayCacheTrackerObserver) {} +} diff --git a/ios/MullvadVPNTests/RelaySelectorTests.swift b/ios/MullvadVPNTests/RelaySelectorTests.swift index fa8fc6659bcb..b390592b365a 100644 --- a/ios/MullvadVPNTests/RelaySelectorTests.swift +++ b/ios/MullvadVPNTests/RelaySelectorTests.swift @@ -16,6 +16,8 @@ private let portRanges: [[UInt16]] = [[4000, 4001], [5000, 5001]] private let defaultPort: UInt16 = 53 class RelaySelectorTests: XCTestCase { + let sampleRelays = ServerRelaysResponseStubs.sampleRelays + func testCountryConstraint() throws { let constraints = RelayConstraints(location: .only(.country("es"))) @@ -106,199 +108,3 @@ class RelaySelectorTests: XCTestCase { XCTAssertTrue(sampleRelays.bridge.relays.contains(selectedRelay)) } } - -private let sampleRelays = REST.ServerRelaysResponse( - locations: [ - "es-mad": REST.ServerLocation( - country: "Spain", - city: "Madrid", - latitude: 40.408566, - longitude: -3.69222 - ), - "se-got": REST.ServerLocation( - country: "Sweden", - city: "Gothenburg", - latitude: 57.70887, - longitude: 11.97456 - ), - "se-sto": REST.ServerLocation( - country: "Sweden", - city: "Stockholm", - latitude: 59.3289, - longitude: 18.0649 - ), - "ae-dxb": REST.ServerLocation( - country: "United Arab Emirates", - city: "Dubai", - latitude: 25.276987, - longitude: 55.296249 - ), - "jp-tyo": REST.ServerLocation( - country: "Japan", - city: "Tokyo", - latitude: 35.685, - longitude: 139.751389 - ), - "ca-tor": REST.ServerLocation( - country: "Canada", - city: "Toronto", - latitude: 43.666667, - longitude: -79.416667 - ), - "us-atl": REST.ServerLocation( - country: "USA", - city: "Atlanta, GA", - latitude: 40.73061, - longitude: -73.935242 - ), - "us-dal": REST.ServerLocation( - country: "USA", - city: "Dallas, TX", - latitude: 32.89748, - longitude: -97.040443 - ), - ], - wireguard: REST.ServerWireguardTunnels( - ipv4Gateway: .loopback, - ipv6Gateway: .loopback, - portRanges: portRanges, - relays: [ - REST.ServerRelay( - hostname: "es1-wireguard", - active: true, - owned: true, - location: "es-mad", - provider: "", - weight: 500, - ipv4AddrIn: .loopback, - ipv6AddrIn: .loopback, - publicKey: Data(), - includeInCountry: true - ), - REST.ServerRelay( - hostname: "se10-wireguard", - active: true, - owned: true, - location: "se-got", - provider: "", - weight: 1000, - ipv4AddrIn: .loopback, - ipv6AddrIn: .loopback, - publicKey: Data(), - includeInCountry: true - ), - REST.ServerRelay( - hostname: "se2-wireguard", - active: true, - owned: true, - location: "se-sto", - provider: "", - weight: 50, - ipv4AddrIn: .loopback, - ipv6AddrIn: .loopback, - publicKey: Data(), - includeInCountry: true - ), - REST.ServerRelay( - hostname: "se6-wireguard", - active: true, - owned: true, - location: "se-sto", - provider: "", - weight: 100, - ipv4AddrIn: .loopback, - ipv6AddrIn: .loopback, - publicKey: Data(), - includeInCountry: true - ), - REST.ServerRelay( - hostname: "us-dal-wg-001", - active: true, - owned: true, - location: "us-dal", - provider: "", - weight: 100, - ipv4AddrIn: .loopback, - ipv6AddrIn: .loopback, - publicKey: Data(), - includeInCountry: true - ), - REST.ServerRelay( - hostname: "us-nyc-wg-301", - active: true, - owned: true, - location: "us-nyc", - provider: "", - weight: 100, - ipv4AddrIn: .loopback, - ipv6AddrIn: .loopback, - publicKey: Data(), - includeInCountry: true - ), - ] - ), - bridge: REST.ServerBridges(shadowsocks: [ - REST.ServerShadowsocks(protocol: "tcp", port: 443, cipher: "aes-256-gcm", password: "mullvad"), - ], relays: [ - REST.BridgeRelay( - hostname: "se-sto-br-001", - active: true, - owned: true, - location: "se-sto", - provider: "31173", - ipv4AddrIn: .loopback, - weight: 100, - includeInCountry: true - ), - REST.BridgeRelay( - hostname: "jp-tyo-br-101", - active: true, - owned: true, - location: "jp-tyo", - provider: "M247", - ipv4AddrIn: .loopback, - weight: 1, - includeInCountry: true - ), - REST.BridgeRelay( - hostname: "ca-tor-ovpn-001", - active: false, - owned: false, - location: "ca-tor", - provider: "M247", - ipv4AddrIn: .loopback, - weight: 1, - includeInCountry: true - ), - REST.BridgeRelay( - hostname: "ae-dxb-ovpn-001", - active: true, - owned: false, - location: "ae-dxb", - provider: "M247", - ipv4AddrIn: .loopback, - weight: 100, - includeInCountry: true - ), - REST.BridgeRelay( - hostname: "us-atl-br-101", - active: true, - owned: false, - location: "us-atl", - provider: "100TB", - ipv4AddrIn: .loopback, - weight: 100, - includeInCountry: true - ), - REST.BridgeRelay( - hostname: "us-dal-br-101", - active: true, - owned: false, - location: "us-dal", - provider: "100TB", - ipv4AddrIn: .loopback, - weight: 100, - includeInCountry: true - ), - ]) -) diff --git a/ios/MullvadVPNTests/ServerRelaysResponse+Stubs.swift b/ios/MullvadVPNTests/ServerRelaysResponse+Stubs.swift new file mode 100644 index 000000000000..bf6615a05205 --- /dev/null +++ b/ios/MullvadVPNTests/ServerRelaysResponse+Stubs.swift @@ -0,0 +1,210 @@ +// +// ServerRelaysResponse+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +@testable import MullvadREST + +enum ServerRelaysResponseStubs { + static let portRanges: [[UInt16]] = [[4000, 4001], [5000, 5001]] + + static let sampleRelays = REST.ServerRelaysResponse( + locations: [ + "es-mad": REST.ServerLocation( + country: "Spain", + city: "Madrid", + latitude: 40.408566, + longitude: -3.69222 + ), + "se-got": REST.ServerLocation( + country: "Sweden", + city: "Gothenburg", + latitude: 57.70887, + longitude: 11.97456 + ), + "se-sto": REST.ServerLocation( + country: "Sweden", + city: "Stockholm", + latitude: 59.3289, + longitude: 18.0649 + ), + "ae-dxb": REST.ServerLocation( + country: "United Arab Emirates", + city: "Dubai", + latitude: 25.276987, + longitude: 55.296249 + ), + "jp-tyo": REST.ServerLocation( + country: "Japan", + city: "Tokyo", + latitude: 35.685, + longitude: 139.751389 + ), + "ca-tor": REST.ServerLocation( + country: "Canada", + city: "Toronto", + latitude: 43.666667, + longitude: -79.416667 + ), + "us-atl": REST.ServerLocation( + country: "USA", + city: "Atlanta, GA", + latitude: 40.73061, + longitude: -73.935242 + ), + "us-dal": REST.ServerLocation( + country: "USA", + city: "Dallas, TX", + latitude: 32.89748, + longitude: -97.040443 + ), + ], + wireguard: REST.ServerWireguardTunnels( + ipv4Gateway: .loopback, + ipv6Gateway: .loopback, + portRanges: portRanges, + relays: [ + REST.ServerRelay( + hostname: "es1-wireguard", + active: true, + owned: true, + location: "es-mad", + provider: "", + weight: 500, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: Data(), + includeInCountry: true + ), + REST.ServerRelay( + hostname: "se10-wireguard", + active: true, + owned: true, + location: "se-got", + provider: "", + weight: 1000, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: Data(), + includeInCountry: true + ), + REST.ServerRelay( + hostname: "se2-wireguard", + active: true, + owned: true, + location: "se-sto", + provider: "", + weight: 50, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: Data(), + includeInCountry: true + ), + REST.ServerRelay( + hostname: "se6-wireguard", + active: true, + owned: true, + location: "se-sto", + provider: "", + weight: 100, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: Data(), + includeInCountry: true + ), + REST.ServerRelay( + hostname: "us-dal-wg-001", + active: true, + owned: true, + location: "us-dal", + provider: "", + weight: 100, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: Data(), + includeInCountry: true + ), + REST.ServerRelay( + hostname: "us-nyc-wg-301", + active: true, + owned: true, + location: "us-nyc", + provider: "", + weight: 100, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: Data(), + includeInCountry: true + ), + ] + ), + bridge: REST.ServerBridges(shadowsocks: [ + REST.ServerShadowsocks(protocol: "tcp", port: 443, cipher: "aes-256-gcm", password: "mullvad"), + ], relays: [ + REST.BridgeRelay( + hostname: "se-sto-br-001", + active: true, + owned: true, + location: "se-sto", + provider: "31173", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "jp-tyo-br-101", + active: true, + owned: true, + location: "jp-tyo", + provider: "M247", + ipv4AddrIn: .loopback, + weight: 1, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "ca-tor-ovpn-001", + active: false, + owned: false, + location: "ca-tor", + provider: "M247", + ipv4AddrIn: .loopback, + weight: 1, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "ae-dxb-ovpn-001", + active: true, + owned: false, + location: "ae-dxb", + provider: "M247", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "us-atl-br-101", + active: true, + owned: false, + location: "us-atl", + provider: "100TB", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "us-dal-br-101", + active: true, + owned: false, + location: "us-dal", + provider: "100TB", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + ]) + ) +} diff --git a/ios/MullvadVPNTests/TunnelManagerTests.swift b/ios/MullvadVPNTests/TunnelManagerTests.swift new file mode 100644 index 000000000000..958c50e25275 --- /dev/null +++ b/ios/MullvadVPNTests/TunnelManagerTests.swift @@ -0,0 +1,33 @@ +// +// TunnelManagerTests.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-02. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import XCTest + +final class TunnelManagerTests: XCTestCase { + func testTunnelManager() { + let application = UIApplicationStub() + let tunnelStore = TunnelStoreStub() + let relayCacheTracker = RelayCacheTrackerStub() + let accountProxy = AccountsProxyStub() + let devicesProxy = DevicesProxyStub() + let apiProxy = APIProxyStub() + let accessTokenManager = AccessTokenManagerStub() + + let tunnelManager = TunnelManager( + application: application, + tunnelStore: tunnelStore, + relayCacheTracker: relayCacheTracker, + accountsProxy: accountProxy, + devicesProxy: devicesProxy, + apiProxy: apiProxy, + accessTokenManager: accessTokenManager + ) + + XCTAssertNotNil(tunnelManager) + } +} diff --git a/ios/MullvadVPNTests/TunnelStore+Stubs.swift b/ios/MullvadVPNTests/TunnelStore+Stubs.swift new file mode 100644 index 000000000000..d9a15f68146b --- /dev/null +++ b/ios/MullvadVPNTests/TunnelStore+Stubs.swift @@ -0,0 +1,69 @@ +// +// TunnelStore+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-03. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import NetworkExtension + +struct TunnelStoreStub: TunnelStoreProtocol { + func getPersistentTunnels() -> [any TunnelProtocol] { + [] + } + + func createNewTunnel() -> any TunnelProtocol { + TunnelStub(status: .invalid, isOnDemandEnabled: false) + } +} + +class DummyTunnelStatusObserver: TunnelStatusObserver { + func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus) {} +} + +class TunnelStub: TunnelProtocol { + static func == (lhs: TunnelStub, rhs: TunnelStub) -> Bool { + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + } + + init(status: NEVPNStatus, isOnDemandEnabled: Bool, startDate: Date? = nil) { + self.status = status + self.isOnDemandEnabled = isOnDemandEnabled + self.startDate = startDate + } + + func addObserver(_ observer: TunnelStatusObserver) {} + + func removeObserver(_ observer: TunnelStatusObserver) {} + + var status: NEVPNStatus + + var isOnDemandEnabled: Bool + + var startDate: Date? + + func addBlockObserver( + queue: DispatchQueue?, + handler: @escaping (any TunnelProtocol, NEVPNStatus) -> Void + ) -> TunnelStatusBlockObserver { + TunnelStatusBlockObserver(tunnel: self, queue: queue, handler: handler) + } + + func logFormat() -> String { + "" + } + + func saveToPreferences(_ completion: @escaping (Error?) -> Void) {} + + func removeFromPreferences(completion: @escaping (Error?) -> Void) {} + + func setConfiguration(_ configuration: TunnelConfiguration) {} + + func start(options: [String: NSObject]?) throws {} + + func stop() {} + + func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws {} +} diff --git a/ios/MullvadVPNTests/UIApplication+Stubs.swift b/ios/MullvadVPNTests/UIApplication+Stubs.swift new file mode 100644 index 000000000000..4dc4805ad0ce --- /dev/null +++ b/ios/MullvadVPNTests/UIApplication+Stubs.swift @@ -0,0 +1,23 @@ +// +// UIApplication+Stubs.swift +// MullvadVPNTests +// +// Created by Marco Nikic on 2023-10-02. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import UIKit + +@testable import MullvadTypes + +struct UIApplicationStub: UIApplicationProtocol { + func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) {} + + func beginBackgroundTask( + withName taskName: String?, + expirationHandler handler: (() -> Void)? + ) -> UIBackgroundTaskIdentifier { + .invalid + } +} diff --git a/ios/Operations/BackgroundObserver.swift b/ios/Operations/BackgroundObserver.swift index 53889cecd99b..6df3074e7664 100644 --- a/ios/Operations/BackgroundObserver.swift +++ b/ios/Operations/BackgroundObserver.swift @@ -8,17 +8,18 @@ #if canImport(UIKit) +import MullvadTypes import UIKit @available(iOSApplicationExtension, unavailable) public final class BackgroundObserver: OperationObserver { public let name: String - public let application: UIApplication + public let application: UIApplicationProtocol public let cancelUponExpiration: Bool private var taskIdentifier: UIBackgroundTaskIdentifier? - public init(application: UIApplication, name: String, cancelUponExpiration: Bool) { + public init(application: UIApplicationProtocol, name: String, cancelUponExpiration: Bool) { self.application = application self.name = name self.cancelUponExpiration = cancelUponExpiration diff --git a/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift b/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift index 9ad8bcbc75bf..11151e922464 100644 --- a/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift +++ b/ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift @@ -13,10 +13,10 @@ import class WireGuardKitTypes.PublicKey /// An object that implements remote service used by `DeviceCheckOperation`. struct DeviceCheckRemoteService: DeviceCheckRemoteServiceProtocol { - private let accountsProxy: REST.AccountsProxy - private let devicesProxy: REST.DevicesProxy + private let accountsProxy: AccountHandling + private let devicesProxy: DeviceHandling - init(accountsProxy: REST.AccountsProxy, devicesProxy: REST.DevicesProxy) { + init(accountsProxy: AccountHandling, devicesProxy: DeviceHandling) { self.accountsProxy = accountsProxy self.devicesProxy = devicesProxy } diff --git a/ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift b/ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift index e4fa756b25c0..fa352fd87d87 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift @@ -16,10 +16,10 @@ final class DeviceChecker { private let dispatchQueue = DispatchQueue(label: "DeviceCheckerQueue") private let operationQueue = AsyncOperationQueue.makeSerial() - private let accountsProxy: REST.AccountsProxy - private let devicesProxy: REST.DevicesProxy + private let accountsProxy: AccountHandling + private let devicesProxy: DeviceHandling - init(accountsProxy: REST.AccountsProxy, devicesProxy: REST.DevicesProxy) { + init(accountsProxy: AccountHandling, devicesProxy: DeviceHandling) { self.accountsProxy = accountsProxy self.devicesProxy = devicesProxy } diff --git a/ios/RelayCache/RelayCache.swift b/ios/RelayCache/RelayCache.swift index abce82378601..ba7ed31cf1cd 100644 --- a/ios/RelayCache/RelayCache.swift +++ b/ios/RelayCache/RelayCache.swift @@ -10,7 +10,12 @@ import Foundation import MullvadREST import MullvadTypes -public final class RelayCache { +public protocol RelayCacheProtocol { + func read() throws -> CachedRelays + func write(record: CachedRelays) throws +} + +public final class RelayCache: RelayCacheProtocol { private let fileCache: any FileCacheProtocol /// Designated initializer