summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2023-10-16 14:55:04 +0200
committerBug Magnet <marco.nikic@mullvad.net>2023-10-16 14:55:04 +0200
commit5d4fcb35d7273d074716840751818db690df415a (patch)
tree309723c806247371150565459e8b9b57937e9a0d
parentf7b9c1bb4e5338808f7f7d6ec0c0d3dc3cb4f0a4 (diff)
parentc946ac56c4d591c41cc951ba1958f3d7d2f3affa (diff)
downloadmullvadvpn-5d4fcb35d7273d074716840751818db690df415a.tar.xz
mullvadvpn-5d4fcb35d7273d074716840751818db690df415a.zip
Merge branch 'tunnel-async-actor-tests'
-rw-r--r--ios/MullvadREST/RESTAPIProxy.swift50
-rw-r--r--ios/MullvadREST/RESTAccessTokenManager.swift15
-rw-r--r--ios/MullvadREST/RESTAccountsProxy.swift29
-rw-r--r--ios/MullvadREST/RESTAuthenticationProxy.swift6
-rw-r--r--ios/MullvadREST/RESTDevicesProxy.swift49
-rw-r--r--ios/MullvadREST/RESTProxy.swift12
-rw-r--r--ios/MullvadREST/RESTProxyFactory.swift6
-rw-r--r--ios/MullvadTransport/TransportProvider.swift4
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj479
-rw-r--r--ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift4
-rw-r--r--ios/MullvadVPN/AppDelegate.swift6
-rw-r--r--ios/MullvadVPN/Classes/ObserverList.swift8
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift12
-rw-r--r--ios/MullvadVPN/Coordinators/LoginCoordinator.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift4
-rw-r--r--ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift18
-rw-r--r--ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift4
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift8
-rw-r--r--ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift4
-rw-r--r--ios/MullvadVPN/TransportMonitor/TransportMonitor.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/DeleteAccountOperation.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/SetAccountOperation.swift21
-rw-r--r--ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel.swift69
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelInteractor.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift46
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelStatusBlockObserver.swift40
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelStore.swift32
-rw-r--r--ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift25
-rw-r--r--ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountInteractor.swift4
-rw-r--r--ios/MullvadVPN/View controllers/DeviceList/DeviceManagementInteractor.swift4
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift4
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift4
-rw-r--r--ios/MullvadVPNTests/APIProxy+Stubs.swift64
-rw-r--r--ios/MullvadVPNTests/AccessTokenManager+Stubs.swift22
-rw-r--r--ios/MullvadVPNTests/AccountsProxy+Stubs.swift42
-rw-r--r--ios/MullvadVPNTests/AddressCacheTests.swift6
-rw-r--r--ios/MullvadVPNTests/DevicesProxy+Stubs.swift59
-rw-r--r--ios/MullvadVPNTests/RESTRequestExecutor+Stubs.swift38
-rw-r--r--ios/MullvadVPNTests/RelayCacheTracker+Stubs.swift33
-rw-r--r--ios/MullvadVPNTests/RelaySelectorTests.swift198
-rw-r--r--ios/MullvadVPNTests/ServerRelaysResponse+Stubs.swift211
-rw-r--r--ios/MullvadVPNTests/TunnelManagerTests.swift33
-rw-r--r--ios/MullvadVPNTests/TunnelStore+Stubs.swift74
-rw-r--r--ios/MullvadVPNTests/UIApplication+Stubs.swift23
-rw-r--r--ios/Operations/BackgroundObserver.swift5
-rw-r--r--ios/PacketTunnel/DeviceCheck/DeviceCheckRemoteService.swift6
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift4
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift6
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift57
-rw-r--r--ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift29
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift (renamed from ios/PacketTunnelCore/Actor/Actor+ConnectionMonitoring.swift)0
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift (renamed from ios/PacketTunnelCore/Actor/Actor+ErrorState.swift)0
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift (renamed from ios/PacketTunnelCore/Actor/Actor+Extensions.swift)0
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift (renamed from ios/PacketTunnelCore/Actor/Actor+KeyPolicy.swift)2
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+NetworkReachability.swift (renamed from ios/PacketTunnelCore/Actor/Actor+NetworkReachability.swift)0
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift (renamed from ios/PacketTunnelCore/Actor/Actor+Public.swift)2
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+SleepCycle.swift (renamed from ios/PacketTunnelCore/Actor/Actor+SleepCycle.swift)0
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift (renamed from ios/PacketTunnelCore/Actor/Actor.swift)4
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift16
-rw-r--r--ios/PacketTunnelCore/Actor/State+Extensions.swift49
-rw-r--r--ios/PacketTunnelCore/Actor/State.swift7
-rw-r--r--ios/PacketTunnelCore/IPC/AppMessageHandler.swift (renamed from ios/PacketTunnel/PacketTunnelProvider/AppMessageHandler.swift)13
-rw-r--r--ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift2
-rw-r--r--ios/PacketTunnelCore/URLRequestProxy/URLRequestProxyProtocol.swift15
-rw-r--r--ios/PacketTunnelCoreTests/ActorTests.swift196
-rw-r--r--ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift114
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift6
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift2
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift33
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/URLRequestProxyStub.swift32
-rw-r--r--ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift436
-rw-r--r--ios/RelayCache/RelayCache.swift7
82 files changed, 2070 insertions, 805 deletions
diff --git a/ios/MullvadREST/RESTAPIProxy.swift b/ios/MullvadREST/RESTAPIProxy.swift
index b547d92e45..70522f8b03 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<REST.ServerRelaysCacheResponse>
+ ) -> Cancellable
+
+ func createApplePayment(
+ accountNumber: String,
+ receiptString: Data
+ ) -> any RESTRequestExecutor<REST.CreateApplePaymentResponse>
+
+ func createApplePayment(
+ accountNumber: String,
+ receiptString: Data,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<REST.CreateApplePaymentResponse>
+ ) -> Cancellable
+
+ func sendProblemReport(
+ _ body: REST.ProblemReportRequest,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<Void>
+ ) -> Cancellable
+
+ func submitVoucher(
+ voucherCode: String,
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<REST.SubmitVoucherResponse>
+ ) -> Cancellable
+}
+
extension REST {
- public final class APIProxy: Proxy<AuthProxyConfiguration> {
+ public final class APIProxy: Proxy<AuthProxyConfiguration>, 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<ServerRelaysCacheResponse>
+ completionHandler: @escaping ProxyCompletionHandler<ServerRelaysCacheResponse>
) -> 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<CreateApplePaymentResponse>
+ completionHandler: @escaping ProxyCompletionHandler<CreateApplePaymentResponse>
) -> 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<Void>
+ completionHandler: @escaping ProxyCompletionHandler<Void>
) -> 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<SubmitVoucherResponse>
+ completionHandler: @escaping ProxyCompletionHandler<SubmitVoucherResponse>
) -> Cancellable {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
diff --git a/ios/MullvadREST/RESTAccessTokenManager.swift b/ios/MullvadREST/RESTAccessTokenManager.swift
index 17c812020d..d15bd6b9cb 100644
--- a/ios/MullvadREST/RESTAccessTokenManager.swift
+++ b/ios/MullvadREST/RESTAccessTokenManager.swift
@@ -11,8 +11,17 @@ import MullvadLogging
import MullvadTypes
import Operations
+public protocol RESTAccessTokenManagement {
+ func getAccessToken(
+ accountNumber: String,
+ completionHandler: @escaping ProxyCompletionHandler<REST.AccessTokenData>
+ ) -> Cancellable
+
+ func invalidateAllTokens()
+}
+
extension REST {
- public final class AccessTokenManager {
+ public final class AccessTokenManager: RESTAccessTokenManagement {
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<REST.AccessTokenData, Swift.Error>) -> Void
+ completionHandler: @escaping ProxyCompletionHandler<REST.AccessTokenData>
) -> Cancellable {
let operation =
ResultBlockOperation<REST.AccessTokenData>(dispatchQueue: dispatchQueue) { finish -> Cancellable in
diff --git a/ios/MullvadREST/RESTAccountsProxy.swift b/ios/MullvadREST/RESTAccountsProxy.swift
index 29e41e027e..fad967aac7 100644
--- a/ios/MullvadREST/RESTAccountsProxy.swift
+++ b/ios/MullvadREST/RESTAccountsProxy.swift
@@ -9,8 +9,29 @@
import Foundation
import MullvadTypes
+public protocol RESTAccountHandling {
+ func createAccount(
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<REST.NewAccountData>
+ ) -> Cancellable
+
+ func getAccountData(accountNumber: String) -> any RESTRequestExecutor<Account>
+
+ func getAccountData(
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Account>
+ ) -> Cancellable
+
+ func deleteAccount(
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Void>
+ ) -> Cancellable
+}
+
extension REST {
- public final class AccountsProxy: Proxy<AuthProxyConfiguration> {
+ public final class AccountsProxy: Proxy<AuthProxyConfiguration>, RESTAccountHandling {
public init(configuration: AuthProxyConfiguration) {
super.init(
name: "AccountsProxy",
@@ -25,7 +46,7 @@ extension REST {
public func createAccount(
retryStrategy: REST.RetryStrategy,
- completion: @escaping CompletionHandler<NewAccountData>
+ completion: @escaping ProxyCompletionHandler<NewAccountData>
) -> 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<Account>
+ completion: @escaping ProxyCompletionHandler<Account>
) -> Cancellable {
return getAccountData(accountNumber: accountNumber).execute(
retryStrategy: retryStrategy,
@@ -92,7 +113,7 @@ extension REST {
public func deleteAccount(
accountNumber: String,
retryStrategy: RetryStrategy,
- completion: @escaping CompletionHandler<Void>
+ completion: @escaping ProxyCompletionHandler<Void>
) -> 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 fb899aa2c1..0b5cabe8cb 100644
--- a/ios/MullvadREST/RESTAuthenticationProxy.swift
+++ b/ios/MullvadREST/RESTAuthenticationProxy.swift
@@ -23,10 +23,10 @@ extension REST {
)
}
- func getAccessToken(
+ public func getAccessToken(
accountNumber: String,
retryStrategy: REST.RetryStrategy,
- completion: @escaping CompletionHandler<AccessTokenData>
+ completion: @escaping ProxyCompletionHandler<AccessTokenData>
) -> 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 f273fac7d5..6ec8e299e5 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<Device>
+ ) -> 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<Device>
+ ) -> Cancellable
+
+ func deleteDevice(
+ accountNumber: String,
+ identifier: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Bool>
+ ) -> Cancellable
+
+ func rotateDeviceKey(
+ accountNumber: String,
+ identifier: String,
+ publicKey: PublicKey,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Device>
+ ) -> Cancellable
+}
+
extension REST {
- public final class DevicesProxy: Proxy<AuthProxyConfiguration> {
+ public final class DevicesProxy: Proxy<AuthProxyConfiguration>, 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<Device>
+ completion: @escaping ProxyCompletionHandler<Device>
) -> 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<Device>
+ completion: @escaping ProxyCompletionHandler<Device>
) -> Cancellable {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
@@ -164,7 +201,7 @@ extension REST {
accountNumber: String,
identifier: String,
retryStrategy: REST.RetryStrategy,
- completion: @escaping CompletionHandler<Bool>
+ completion: @escaping ProxyCompletionHandler<Bool>
) -> Cancellable {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
@@ -226,7 +263,7 @@ extension REST {
identifier: String,
publicKey: PublicKey,
retryStrategy: REST.RetryStrategy,
- completion: @escaping CompletionHandler<Device>
+ completion: @escaping ProxyCompletionHandler<Device>
) -> Cancellable {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
diff --git a/ios/MullvadREST/RESTProxy.swift b/ios/MullvadREST/RESTProxy.swift
index 4c6d138fd5..6ca64d85ee 100644
--- a/ios/MullvadREST/RESTProxy.swift
+++ b/ios/MullvadREST/RESTProxy.swift
@@ -10,10 +10,10 @@ import Foundation
import MullvadTypes
import Operations
+public typealias ProxyCompletionHandler<Success> = (Result<Success, Swift.Error>) -> Void
+
extension REST {
public class Proxy<ConfigurationType: ProxyConfiguration> {
- public typealias CompletionHandler<Success> = (Result<Success, Swift.Error>) -> Void
-
/// Synchronization queue used by network operations.
let dispatchQueue: DispatchQueue
@@ -49,7 +49,7 @@ extension REST {
retryStrategy: REST.RetryStrategy,
requestHandler: RESTRequestHandler,
responseHandler: some RESTResponseHandler<Success>,
- completionHandler: @escaping CompletionHandler<Success>
+ completionHandler: @escaping ProxyCompletionHandler<Success>
) -> 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<Success, Swift.Error>) -> Void)? = nil
+ completionHandler: ProxyCompletionHandler<Success>? = nil
) -> NetworkOperation<Success> {
return NetworkOperation(
name: getTaskIdentifier(name: name),
@@ -110,7 +110,7 @@ extension REST {
func execute(
retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping (Result<Success, Swift.Error>) -> Void
+ completionHandler: @escaping ProxyCompletionHandler<Success>
) -> Cancellable {
let operation = operationFactory.makeOperation(
retryStrategy: retryStrategy,
@@ -137,7 +137,7 @@ extension REST {
}
}
- func execute(completionHandler: @escaping (Result<Success, Swift.Error>) -> Void) -> Cancellable {
+ func execute(completionHandler: @escaping ProxyCompletionHandler<Success>) -> Cancellable {
return execute(retryStrategy: .noRetry, completionHandler: completionHandler)
}
diff --git a/ios/MullvadREST/RESTProxyFactory.swift b/ios/MullvadREST/RESTProxyFactory.swift
index 2436cbf3e8..43201890cc 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() -> RESTAccountHandling {
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 7eb6ff4632..88c3e1fc8a 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,
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 7986b4245d..3f6ab488ff 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 */; };
@@ -82,7 +77,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 */; };
@@ -90,11 +84,11 @@
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; };
5838321B2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */; };
5838321D2AC1C54600EA2071 /* TaskSleepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */; };
- 5838321F2AC3160A00EA2071 /* Actor+KeyPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321E2AC3160A00EA2071 /* Actor+KeyPolicy.swift */; };
- 583832212AC3174700EA2071 /* Actor+NetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832202AC3174700EA2071 /* Actor+NetworkReachability.swift */; };
- 583832232AC3181400EA2071 /* Actor+ErrorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832222AC3181400EA2071 /* Actor+ErrorState.swift */; };
- 583832252AC318A100EA2071 /* Actor+ConnectionMonitoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832242AC318A100EA2071 /* Actor+ConnectionMonitoring.swift */; };
- 583832272AC3193600EA2071 /* Actor+SleepCycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832262AC3193600EA2071 /* Actor+SleepCycle.swift */; };
+ 5838321F2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321E2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift */; };
+ 583832212AC3174700EA2071 /* PacketTunnelActor+NetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832202AC3174700EA2071 /* PacketTunnelActor+NetworkReachability.swift */; };
+ 583832232AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832222AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift */; };
+ 583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832242AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift */; };
+ 583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832262AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift */; };
583832292AC3DF1300EA2071 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832282AC3DF1300EA2071 /* Command.swift */; };
5838322B2AC3EF9600EA2071 /* CommandChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838322A2AC3EF9600EA2071 /* CommandChannel.swift */; };
583D86482A2678DC0060D63B /* DeviceStateAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */; };
@@ -119,7 +113,6 @@
585A02EB2A4B285800C6CAFF /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585A02EA2A4B285800C6CAFF /* UDPConnection.swift */; };
585A02ED2A4B28F300C6CAFF /* TCPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585A02EC2A4B28F300C6CAFF /* TCPConnection.swift */; };
585B1FF02AB09F97008AD470 /* VPNConnectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */; };
- 585B1FF22AB0BC69008AD470 /* State+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585B1FF12AB0BC69008AD470 /* State+Extensions.swift */; };
585B4B8726D9098900555C4C /* TunnelStatusNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */; };
585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585CA70E25F8C44600B47C62 /* UIMetrics.swift */; };
585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; };
@@ -147,7 +140,7 @@
586A950E290125F3007BAF2B /* ProductsRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846226426E0D9630035F7C2 /* ProductsRequestOperation.swift */; };
586A950F29012BEE007BAF2B /* AddressCacheTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC114028F841390037AF9A /* AddressCacheTracker.swift */; };
586C14582AC463BB00245C01 /* CommandChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C14572AC463BB00245C01 /* CommandChannelTests.swift */; };
- 586C145A2AC4735F00245C01 /* Actor+Public.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C14592AC4735F00245C01 /* Actor+Public.swift */; };
+ 586C145A2AC4735F00245C01 /* PacketTunnelActor+Public.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C14592AC4735F00245C01 /* PacketTunnelActor+Public.swift */; };
586E54FB27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */; };
586E8DB82AAF4AC4007BF3DA /* Task+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */; };
5871167F2910035700D41AAC /* PreferencesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871167E2910035700D41AAC /* PreferencesInteractor.swift */; };
@@ -189,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 */; };
@@ -199,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 */; };
@@ -223,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 */; };
@@ -250,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 */; };
@@ -262,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 +279,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 */; };
58CAFA002983FF0200BE19F7 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9FF2983FF0200BE19F7 /* LoginInteractor.swift */; };
58CAFA032985367600BE19F7 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAFA01298530DC00BE19F7 /* Promise.swift */; };
58CC40EF24A601900019D96E /* ObserverList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CC40EE24A601900019D96E /* ObserverList.swift */; };
@@ -400,7 +382,6 @@
58F3C0A4249CB069003E76BE /* HeaderBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F3C0A3249CB069003E76BE /* HeaderBarView.swift */; };
58F3F36A2AA08E3C00D3B0A4 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F3F3692AA08E3C00D3B0A4 /* PacketTunnelProvider.swift */; };
58F7753D2AB8473200425B47 /* BlockedStateErrorMapperStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F7753C2AB8473200425B47 /* BlockedStateErrorMapperStub.swift */; };
- 58F775432AB9E3EF00425B47 /* AppMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F775422AB9E3EF00425B47 /* AppMessageHandler.swift */; };
58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */; };
58FB865526E8BF3100F188BC /* StorePaymentManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */; };
58FB865A26EA214400F188BC /* RelayCacheTrackerObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865926EA214400F188BC /* RelayCacheTrackerObserver.swift */; };
@@ -414,11 +395,11 @@
58FE25C22AA72729003D1918 /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
58FE25C62AA72779003D1918 /* PacketTunnelCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; };
58FE25CE2AA72802003D1918 /* MullvadSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */; };
- 58FE25D42AA729B5003D1918 /* ActorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25D32AA729B5003D1918 /* ActorTests.swift */; };
+ 58FE25D42AA729B5003D1918 /* PacketTunnelActorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25D32AA729B5003D1918 /* PacketTunnelActorTests.swift */; };
58FE25D72AA72A8F003D1918 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5824030C2A811B0000163DE8 /* State.swift */; };
58FE25D82AA72A8F003D1918 /* ConfigurationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583E60952A9F6D0800DC61EF /* ConfigurationBuilder.swift */; };
58FE25D92AA72A8F003D1918 /* AutoCancellingTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F3F3652AA086A400D3B0A4 /* AutoCancellingTask.swift */; };
- 58FE25DA2AA72A8F003D1918 /* Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E9C3852A4EF1CB00CFDEAC /* Actor.swift */; };
+ 58FE25DA2AA72A8F003D1918 /* PacketTunnelActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E9C3852A4EF1CB00CFDEAC /* PacketTunnelActor.swift */; };
58FE25DB2AA72A8F003D1918 /* StartOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ED3A132A7C199C0085CE65 /* StartOptions.swift */; };
58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEBA02A9CA14B00F578F2 /* AnyTask.swift */; };
58FE25DF2AA72A9B003D1918 /* RelaySelectorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5824037F2A827DF300163DE8 /* RelaySelectorProtocol.swift */; };
@@ -428,7 +409,7 @@
58FE25EE2AA7764E003D1918 /* TunnelAdapterDummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25ED2AA7764E003D1918 /* TunnelAdapterDummy.swift */; };
58FE25F02AA77664003D1918 /* RelaySelectorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25EF2AA77664003D1918 /* RelaySelectorStub.swift */; };
58FE25F22AA77674003D1918 /* SettingsReaderStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25F12AA77674003D1918 /* SettingsReaderStub.swift */; };
- 58FE25F42AA9D730003D1918 /* Actor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25F32AA9D730003D1918 /* Actor+Extensions.swift */; };
+ 58FE25F42AA9D730003D1918 /* PacketTunnelActor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25F32AA9D730003D1918 /* PacketTunnelActor+Extensions.swift */; };
58FE65952AB1D90600E53CB5 /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; };
58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */; };
58FF23A32AB09BEE003A2AF2 /* DeviceChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF23A22AB09BEE003A2AF2 /* DeviceChecker.swift */; };
@@ -446,6 +427,10 @@
7A3353912AAA014400F0A71C /* SimulatorVPNConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */; };
7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */; };
7A3353972AAA0F8600F0A71C /* OperationBlockObserverSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */; };
+ 7A3FD1B52AD4465A0042BEA6 /* AppMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */; };
+ 7A3FD1B62AD542110042BEA6 /* ServerRelaysResponse+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */; };
+ 7A3FD1B72AD54ABD0042BEA6 /* AnyTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB982A98F4ED00F578F2 /* AnyTransport.swift */; };
+ 7A3FD1B82AD54AE60042BEA6 /* TimeServerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */; };
7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; };
7A42DECD2A09064C00B209BE /* SelectableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */; };
7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; };
@@ -485,15 +470,22 @@
7ABCA5B42A9349F20044A708 /* Routing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7ABCA5B72A9353C60044A708 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9F72983D36800BE19F7 /* Coordinator.swift */; };
7ABE318D2A1CDD4500DF4963 /* UIFont+Weight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */; };
+ 7AD0AA1C2AD6A63F00119E10 /* PacketTunnelActorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD0AA1B2AD6A63F00119E10 /* PacketTunnelActorStub.swift */; };
+ 7AD0AA1D2AD6A86700119E10 /* PacketTunnelActorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD0AA192AD69B6E00119E10 /* PacketTunnelActorProtocol.swift */; };
+ 7AD0AA1F2AD6C8B900119E10 /* URLRequestProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD0AA1E2AD6C8B900119E10 /* URLRequestProxyProtocol.swift */; };
+ 7AD0AA212AD6CB0000119E10 /* URLRequestProxyStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */; };
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 */; };
+ 7AEF7F1A2AD00F52006FE45D /* AppMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEF7F192AD00F52006FE45D /* AppMessageHandler.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 */; };
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 */; };
@@ -502,19 +494,124 @@
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 */; };
+ A988DF212ADD293D00D807EF /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A1DE782AD5708E0073F689 /* RESTTransportStrategy.swift */; };
+ A988DF242ADD307200D807EF /* libRelaySelector.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5898D29829017DAC00EB5EBA /* libRelaySelector.a */; };
A9A1DE792AD5708E0073F689 /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A1DE782AD5708E0073F689 /* RESTTransportStrategy.swift */; };
- A9A1DE7A2AD5709A0073F689 /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A1DE782AD5708E0073F689 /* RESTTransportStrategy.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 */; };
@@ -525,7 +622,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 */; };
@@ -609,13 +705,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 */;
@@ -686,13 +775,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 */;
@@ -868,6 +950,13 @@
remoteGlobalIDString = A97F1F402A1F4E1A00ECEFDE;
remoteInfo = MullvadTransport;
};
+ A988DF222ADD305300D807EF /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 5898D29729017DAC00EB5EBA;
+ remoteInfo = RelaySelector;
+ };
A9B2CF702A1F64B20013CC6C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58CE5E58224146200008646E /* Project object */;
@@ -1123,11 +1212,11 @@
5838318A27C40A3900000571 /* Pinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pinger.swift; sourceTree = "<group>"; };
5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+Mocks.swift"; sourceTree = "<group>"; };
5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSleepTests.swift; sourceTree = "<group>"; };
- 5838321E2AC3160A00EA2071 /* Actor+KeyPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actor+KeyPolicy.swift"; sourceTree = "<group>"; };
- 583832202AC3174700EA2071 /* Actor+NetworkReachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actor+NetworkReachability.swift"; sourceTree = "<group>"; };
- 583832222AC3181400EA2071 /* Actor+ErrorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actor+ErrorState.swift"; sourceTree = "<group>"; };
- 583832242AC318A100EA2071 /* Actor+ConnectionMonitoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actor+ConnectionMonitoring.swift"; sourceTree = "<group>"; };
- 583832262AC3193600EA2071 /* Actor+SleepCycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actor+SleepCycle.swift"; sourceTree = "<group>"; };
+ 5838321E2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+KeyPolicy.swift"; sourceTree = "<group>"; };
+ 583832202AC3174700EA2071 /* PacketTunnelActor+NetworkReachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+NetworkReachability.swift"; sourceTree = "<group>"; };
+ 583832222AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+ErrorState.swift"; sourceTree = "<group>"; };
+ 583832242AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+ConnectionMonitoring.swift"; sourceTree = "<group>"; };
+ 583832262AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+SleepCycle.swift"; sourceTree = "<group>"; };
583832282AC3DF1300EA2071 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
5838322A2AC3EF9600EA2071 /* CommandChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandChannel.swift; sourceTree = "<group>"; };
583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStateAccessor.swift; sourceTree = "<group>"; };
@@ -1163,7 +1252,6 @@
585A02E82A4B283000C6CAFF /* TCPUnsafeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPUnsafeListener.swift; sourceTree = "<group>"; };
585A02EA2A4B285800C6CAFF /* UDPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnection.swift; sourceTree = "<group>"; };
585A02EC2A4B28F300C6CAFF /* TCPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPConnection.swift; sourceTree = "<group>"; };
- 585B1FF12AB0BC69008AD470 /* State+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "State+Extensions.swift"; sourceTree = "<group>"; };
585CA70E25F8C44600B47C62 /* UIMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIMetrics.swift; sourceTree = "<group>"; };
585DA87626B024A600B8C587 /* CachedRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRelays.swift; sourceTree = "<group>"; };
585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelProviderMessage.swift; sourceTree = "<group>"; };
@@ -1190,7 +1278,7 @@
586A95112901321B007BAF2B /* IPv6Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv6Endpoint.swift; sourceTree = "<group>"; };
586A951329013235007BAF2B /* AnyIPEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyIPEndpoint.swift; sourceTree = "<group>"; };
586C14572AC463BB00245C01 /* CommandChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandChannelTests.swift; sourceTree = "<group>"; };
- 586C14592AC4735F00245C01 /* Actor+Public.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actor+Public.swift"; sourceTree = "<group>"; };
+ 586C14592AC4735F00245C01 /* PacketTunnelActor+Public.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+Public.swift"; sourceTree = "<group>"; };
586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTunnelProviderMessageOperation.swift; sourceTree = "<group>"; };
586E7A2C2A987689006DAB1B /* SettingsReaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsReaderProtocol.swift; sourceTree = "<group>"; };
586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Task+Duration.swift"; sourceTree = "<group>"; };
@@ -1361,7 +1449,7 @@
58E7BA182A975DF70068EC3A /* RESTTransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportProvider.swift; sourceTree = "<group>"; };
58E973DD24850EB600096F90 /* AsyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = "<group>"; };
58E9C3832A4EF15300CFDEAC /* WireGuardAdapter+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WireGuardAdapter+Async.swift"; sourceTree = "<group>"; };
- 58E9C3852A4EF1CB00CFDEAC /* Actor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actor.swift; sourceTree = "<group>"; };
+ 58E9C3852A4EF1CB00CFDEAC /* PacketTunnelActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelActor.swift; sourceTree = "<group>"; };
58EC06792A8D208D00BEB973 /* TunnelDeviceInfoStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDeviceInfoStub.swift; sourceTree = "<group>"; };
58EC067B2A8D2A0B00BEB973 /* NetworkCounters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkCounters.swift; sourceTree = "<group>"; };
58ECD29123F178FD004298B6 /* Screenshots.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Screenshots.xcconfig; sourceTree = "<group>"; };
@@ -1381,7 +1469,6 @@
58F3F3652AA086A400D3B0A4 /* AutoCancellingTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCancellingTask.swift; sourceTree = "<group>"; };
58F3F3692AA08E3C00D3B0A4 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
58F7753C2AB8473200425B47 /* BlockedStateErrorMapperStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedStateErrorMapperStub.swift; sourceTree = "<group>"; };
- 58F775422AB9E3EF00425B47 /* AppMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMessageHandler.swift; sourceTree = "<group>"; };
58F7D26427EB50A300E4D821 /* ResultOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultOperation.swift; sourceTree = "<group>"; };
58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportReviewViewController.swift; sourceTree = "<group>"; };
58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentManagerError.swift; sourceTree = "<group>"; };
@@ -1393,12 +1480,12 @@
58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+Formatting.swift"; sourceTree = "<group>"; };
58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseButton.swift; sourceTree = "<group>"; };
58FDF2D82A0BA11900C2B061 /* DeviceCheckOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceCheckOperation.swift; sourceTree = "<group>"; };
- 58FE25D32AA729B5003D1918 /* ActorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActorTests.swift; sourceTree = "<group>"; };
+ 58FE25D32AA729B5003D1918 /* PacketTunnelActorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorTests.swift; sourceTree = "<group>"; };
58FE25EB2AA77638003D1918 /* TunnelMonitorStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorStub.swift; sourceTree = "<group>"; };
58FE25ED2AA7764E003D1918 /* TunnelAdapterDummy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelAdapterDummy.swift; sourceTree = "<group>"; };
58FE25EF2AA77664003D1918 /* RelaySelectorStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorStub.swift; sourceTree = "<group>"; };
58FE25F12AA77674003D1918 /* SettingsReaderStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsReaderStub.swift; sourceTree = "<group>"; };
- 58FE25F32AA9D730003D1918 /* Actor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actor+Extensions.swift"; sourceTree = "<group>"; };
+ 58FE25F32AA9D730003D1918 /* PacketTunnelActor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+Extensions.swift"; sourceTree = "<group>"; };
58FE65922AB1CDE000E53CB5 /* DeviceCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceCheck.swift; sourceTree = "<group>"; };
58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticKeyboardResponder.swift; sourceTree = "<group>"; };
58FF23A22AB09BEE003A2AF2 /* DeviceChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceChecker.swift; sourceTree = "<group>"; };
@@ -1416,6 +1503,7 @@
7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorVPNConnection.swift; sourceTree = "<group>"; };
7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelInfo.swift; sourceTree = "<group>"; };
7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationBlockObserverSupport.swift; sourceTree = "<group>"; };
+ 7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageHandlerTests.swift; sourceTree = "<group>"; };
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsCell.swift; sourceTree = "<group>"; };
7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorTimings.swift; sourceTree = "<group>"; };
@@ -1448,9 +1536,19 @@
7A9CCCB12A96302800DD6A34 /* ApplicationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationCoordinator.swift; sourceTree = "<group>"; };
7A9CCCB22A96302800DD6A34 /* TunnelCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelCoordinator.swift; sourceTree = "<group>"; };
7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Weight.swift"; sourceTree = "<group>"; };
+ 7AD0AA192AD69B6E00119E10 /* PacketTunnelActorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorProtocol.swift; sourceTree = "<group>"; };
+ 7AD0AA1B2AD6A63F00119E10 /* PacketTunnelActorStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorStub.swift; sourceTree = "<group>"; };
+ 7AD0AA1E2AD6C8B900119E10 /* URLRequestProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestProxyProtocol.swift; sourceTree = "<group>"; };
+ 7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestProxyStub.swift; sourceTree = "<group>"; };
7AE47E512A17972A000418DA /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = "<group>"; };
+ 7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageHandler.swift; sourceTree = "<group>"; };
7AF6E5EF2A95051E00F2679D /* RouterBlockDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterBlockDelegate.swift; sourceTree = "<group>"; };
7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownStylingOptions.swift; sourceTree = "<group>"; };
+ A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountsProxy+Stubs.swift"; sourceTree = "<group>"; };
+ A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RESTRequestExecutor+Stubs.swift"; sourceTree = "<group>"; };
+ A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevicesProxy+Stubs.swift"; sourceTree = "<group>"; };
+ A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIProxy+Stubs.swift"; sourceTree = "<group>"; };
+ A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessTokenManager+Stubs.swift"; sourceTree = "<group>"; };
A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportStrategyTests.swift; sourceTree = "<group>"; };
A92ECC202A77FFAF0052F1B1 /* TunnelSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettings.swift; sourceTree = "<group>"; };
A92ECC232A7802520052F1B1 /* StoredAccountData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredAccountData.swift; sourceTree = "<group>"; };
@@ -1463,10 +1561,17 @@
A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadTransport.h; sourceTree = "<group>"; };
A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFileCoordinator+Extensions.swift"; sourceTree = "<group>"; };
A9A1DE782AD5708E0073F689 /* RESTTransportStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransportStrategy.swift; sourceTree = "<group>"; };
+ A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManagerTests.swift; sourceTree = "<group>"; };
A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; };
+ A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelayCacheTracker+Stubs.swift"; sourceTree = "<group>"; };
+ A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerRelaysResponse+Stubs.swift"; sourceTree = "<group>"; };
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = "<group>"; };
A9D96B192A8247C100A5C673 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = "<group>"; };
A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportProvider.swift; sourceTree = "<group>"; };
+ A9E031762ACB08950095D843 /* UIApplication+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extensions.swift"; sourceTree = "<group>"; };
+ A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Stubs.swift"; sourceTree = "<group>"; };
+ A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelStore+Stubs.swift"; sourceTree = "<group>"; };
+ A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusBlockObserver.swift; sourceTree = "<group>"; };
A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = "<group>"; };
A9EC20E52A5C488D0040D56E /* Haversine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Haversine.swift; sourceTree = "<group>"; };
A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = "<group>"; };
@@ -1594,6 +1699,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ A988DF242ADD307200D807EF /* libRelaySelector.a in Frameworks */,
A94D691B2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */,
58C7A43E2A863F470060C66F /* PacketTunnelCore.framework in Frameworks */,
);
@@ -1838,6 +1944,8 @@
58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */,
A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */,
581DA2742A1E283E0046ED47 /* WgKeyRotation.swift */,
+ A9E031762ACB08950095D843 /* UIApplication+Extensions.swift */,
+ A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */,
);
path = TunnelManager;
sourceTree = "<group>";
@@ -2192,26 +2300,27 @@
5864AF802A9F52E3008BC928 /* Actor */ = {
isa = PBXGroup;
children = (
+ 58BDEBA02A9CA14B00F578F2 /* AnyTask.swift */,
+ 58F3F3652AA086A400D3B0A4 /* AutoCancellingTask.swift */,
583832282AC3DF1300EA2071 /* Command.swift */,
5838322A2AC3EF9600EA2071 /* CommandChannel.swift */,
- 58E9C3852A4EF1CB00CFDEAC /* Actor.swift */,
- 586C14592AC4735F00245C01 /* Actor+Public.swift */,
- 583832222AC3181400EA2071 /* Actor+ErrorState.swift */,
- 5838321E2AC3160A00EA2071 /* Actor+KeyPolicy.swift */,
- 583832262AC3193600EA2071 /* Actor+SleepCycle.swift */,
- 583832242AC318A100EA2071 /* Actor+ConnectionMonitoring.swift */,
- 583832202AC3174700EA2071 /* Actor+NetworkReachability.swift */,
- 58FE25F32AA9D730003D1918 /* Actor+Extensions.swift */,
- 58DDA18E2ABC32380039C360 /* Timings.swift */,
- 5824030C2A811B0000163DE8 /* State.swift */,
- 58342C032AAB61FB003BA12D /* State+Extensions.swift */,
583E60952A9F6D0800DC61EF /* ConfigurationBuilder.swift */,
- 58BDEBA02A9CA14B00F578F2 /* AnyTask.swift */,
- 586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */,
- 58F3F3652AA086A400D3B0A4 /* AutoCancellingTask.swift */,
- 58ED3A132A7C199C0085CE65 /* StartOptions.swift */,
580D6B892AB31AB400B2D6E0 /* NetworkPath+NetworkReachability.swift */,
+ 58E9C3852A4EF1CB00CFDEAC /* PacketTunnelActor.swift */,
+ 583832242AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift */,
+ 583832222AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift */,
+ 58FE25F32AA9D730003D1918 /* PacketTunnelActor+Extensions.swift */,
+ 5838321E2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift */,
+ 583832202AC3174700EA2071 /* PacketTunnelActor+NetworkReachability.swift */,
+ 586C14592AC4735F00245C01 /* PacketTunnelActor+Public.swift */,
+ 583832262AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift */,
+ 7AD0AA192AD69B6E00119E10 /* PacketTunnelActorProtocol.swift */,
58E7A0312AA0715100C57861 /* Protocols */,
+ 58ED3A132A7C199C0085CE65 /* StartOptions.swift */,
+ 5824030C2A811B0000163DE8 /* State.swift */,
+ 58342C032AAB61FB003BA12D /* State+Extensions.swift */,
+ 586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */,
+ 58DDA18E2ABC32380039C360 /* Timings.swift */,
);
path = Actor;
sourceTree = "<group>";
@@ -2314,19 +2423,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;
@@ -2428,12 +2547,13 @@
58C7A4432A863F490060C66F /* PacketTunnelCoreTests */ = {
isa = PBXGroup;
children = (
- 58FE25D32AA729B5003D1918 /* ActorTests.swift */,
- 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */,
+ 58EC067D2A8D2B0700BEB973 /* Mocks */,
+ 7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */,
+ 586C14572AC463BB00245C01 /* CommandChannelTests.swift */,
+ 58FE25D32AA729B5003D1918 /* PacketTunnelActorTests.swift */,
58C7A46F2A8649ED0060C66F /* PingerTests.swift */,
+ 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */,
58092E532A8B832E00C3CC72 /* TunnelMonitorTests.swift */,
- 586C14572AC463BB00245C01 /* CommandChannelTests.swift */,
- 58EC067D2A8D2B0700BEB973 /* Mocks */,
);
path = PacketTunnelCoreTests;
sourceTree = "<group>";
@@ -2441,6 +2561,7 @@
58C9B8C52ABB23B400040B46 /* IPC */ = {
isa = PBXGroup;
children = (
+ 7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */,
587C575226D2615F005EF767 /* PacketTunnelOptions.swift */,
5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */,
585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */,
@@ -2456,6 +2577,7 @@
063687AF28EB083800BE7161 /* ProxyURLRequest.swift */,
5898D2AD290185D200EB5EBA /* ProxyURLResponse.swift */,
58D229B6298D1D5200BB5A2D /* URLRequestProxy.swift */,
+ 7AD0AA1E2AD6C8B900119E10 /* URLRequestProxyProtocol.swift */,
);
path = URLRequestProxy;
sourceTree = "<group>";
@@ -2691,6 +2813,8 @@
58FE25F12AA77674003D1918 /* SettingsReaderStub.swift */,
58F7753C2AB8473200425B47 /* BlockedStateErrorMapperStub.swift */,
5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */,
+ 7AD0AA1B2AD6A63F00119E10 /* PacketTunnelActorStub.swift */,
+ 7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */,
);
path = Mocks;
sourceTree = "<group>";
@@ -2720,8 +2844,6 @@
isa = PBXGroup;
children = (
58F3F3692AA08E3C00D3B0A4 /* PacketTunnelProvider.swift */,
- 58F775422AB9E3EF00425B47 /* AppMessageHandler.swift */,
- 585B1FF12AB0BC69008AD470 /* State+Extensions.swift */,
580D6B912AB360BE00B2D6E0 /* DeviceCheck+BlockedStateReason.swift */,
5864AF7C2A9F4DC9008BC928 /* SettingsReader.swift */,
580D6B8D2AB33BBF00B2D6E0 /* BlockedStateErrorMapper.swift */,
@@ -3102,8 +3224,6 @@
buildRules = (
);
dependencies = (
- 58915D6C2A2603700066445B /* PBXTargetDependency */,
- 062B45BF28FDA85D00746E77 /* PBXTargetDependency */,
06410DFA292C4ABC00AFC18C /* PBXTargetDependency */,
);
name = MullvadVPNTests;
@@ -3172,6 +3292,7 @@
buildRules = (
);
dependencies = (
+ A988DF232ADD305300D807EF /* PBXTargetDependency */,
58C7A4402A863F470060C66F /* PBXTargetDependency */,
58C7A4722A864B860060C66F /* PBXTargetDependency */,
);
@@ -3281,7 +3402,6 @@
);
dependencies = (
58EED36E29FBEF040000CBAF /* PBXTargetDependency */,
- 586A0DCE2A20E359006C731C /* PBXTargetDependency */,
);
name = Operations;
productName = Operations;
@@ -3864,29 +3984,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;
};
@@ -3916,40 +4122,43 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 58FE25F42AA9D730003D1918 /* Actor+Extensions.swift in Sources */,
+ 58FE25F42AA9D730003D1918 /* PacketTunnelActor+Extensions.swift in Sources */,
58DDA18F2ABC32380039C360 /* Timings.swift in Sources */,
58FE25DF2AA72A9B003D1918 /* RelaySelectorProtocol.swift in Sources */,
58C7A4522A863FB50060C66F /* Pinger.swift in Sources */,
580D6B8C2AB3369300B2D6E0 /* BlockedStateErrorMapperProtocol.swift in Sources */,
58C7AF172ABD84AA007EDD7A /* ProxyURLRequest.swift in Sources */,
- 5838321F2AC3160A00EA2071 /* Actor+KeyPolicy.swift in Sources */,
+ 5838321F2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift in Sources */,
58C7AF122ABD8480007EDD7A /* TunnelProviderReply.swift in Sources */,
58C7A4572A863FB90060C66F /* TunnelDeviceInfoProtocol.swift in Sources */,
58C7A4562A863FB90060C66F /* DefaultPathObserverProtocol.swift in Sources */,
- 58FE25DA2AA72A8F003D1918 /* Actor.swift in Sources */,
+ 58FE25DA2AA72A8F003D1918 /* PacketTunnelActor.swift in Sources */,
58FE25E62AA738E8003D1918 /* TunnelAdapterProtocol.swift in Sources */,
- 583832252AC318A100EA2071 /* Actor+ConnectionMonitoring.swift in Sources */,
+ 583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */,
58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */,
58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */,
58C7A4512A863FB50060C66F /* PingerProtocol.swift in Sources */,
583832292AC3DF1300EA2071 /* Command.swift in Sources */,
- 583832232AC3181400EA2071 /* Actor+ErrorState.swift in Sources */,
+ 583832232AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift in Sources */,
58C7AF112ABD8480007EDD7A /* TunnelProviderMessage.swift in Sources */,
58C7AF162ABD84A8007EDD7A /* URLRequestProxy.swift in Sources */,
58FE25D72AA72A8F003D1918 /* State.swift in Sources */,
58C7AF132ABD8480007EDD7A /* PacketTunnelStatus.swift in Sources */,
58C7A4592A863FB90060C66F /* WgStats.swift in Sources */,
+ 7AD0AA1F2AD6C8B900119E10 /* URLRequestProxyProtocol.swift in Sources */,
7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */,
58FE25DB2AA72A8F003D1918 /* StartOptions.swift in Sources */,
- 583832212AC3174700EA2071 /* Actor+NetworkReachability.swift in Sources */,
+ 583832212AC3174700EA2071 /* PacketTunnelActor+NetworkReachability.swift in Sources */,
58FE25D82AA72A8F003D1918 /* ConfigurationBuilder.swift in Sources */,
+ 7AEF7F1A2AD00F52006FE45D /* AppMessageHandler.swift in Sources */,
580D6B8A2AB31AB400B2D6E0 /* NetworkPath+NetworkReachability.swift in Sources */,
+ 7AD0AA1D2AD6A86700119E10 /* PacketTunnelActorProtocol.swift in Sources */,
5826B6CB2ABD83E200B1CA13 /* PacketTunnelOptions.swift in Sources */,
586E8DB82AAF4AC4007BF3DA /* Task+Duration.swift in Sources */,
5838322B2AC3EF9600EA2071 /* CommandChannel.swift in Sources */,
- 586C145A2AC4735F00245C01 /* Actor+Public.swift in Sources */,
+ 586C145A2AC4735F00245C01 /* PacketTunnelActor+Public.swift in Sources */,
58342C042AAB61FB003BA12D /* State+Extensions.swift in Sources */,
- 583832272AC3193600EA2071 /* Actor+SleepCycle.swift in Sources */,
+ 583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */,
58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */,
58C7AF152ABD8480007EDD7A /* PacketTunnelRelay.swift in Sources */,
58FE25D92AA72A8F003D1918 /* AutoCancellingTask.swift in Sources */,
@@ -3962,20 +4171,26 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 7AD0AA1C2AD6A63F00119E10 /* PacketTunnelActorStub.swift in Sources */,
58EC067A2A8D208D00BEB973 /* TunnelDeviceInfoStub.swift in Sources */,
586C14582AC463BB00245C01 /* CommandChannelTests.swift in Sources */,
58EC067C2A8D2A0B00BEB973 /* NetworkCounters.swift in Sources */,
58FE25EC2AA77639003D1918 /* TunnelMonitorStub.swift in Sources */,
+ 7A3FD1B82AD54AE60042BEA6 /* TimeServerProxy.swift in Sources */,
58FE25EE2AA7764E003D1918 /* TunnelAdapterDummy.swift in Sources */,
581F23AD2A8CF92100788AB6 /* DefaultPathObserverFake.swift in Sources */,
5838321B2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift in Sources */,
5838321D2AC1C54600EA2071 /* TaskSleepTests.swift in Sources */,
58092E542A8B832E00C3CC72 /* TunnelMonitorTests.swift in Sources */,
+ 7AD0AA212AD6CB0000119E10 /* URLRequestProxyStub.swift in Sources */,
58FE25F02AA77664003D1918 /* RelaySelectorStub.swift in Sources */,
581F23AF2A8CF94D00788AB6 /* PingerMock.swift in Sources */,
+ 7A3FD1B62AD542110042BEA6 /* ServerRelaysResponse+Stubs.swift in Sources */,
+ 7A3FD1B72AD54ABD0042BEA6 /* AnyTransport.swift in Sources */,
58FE25F22AA77674003D1918 /* SettingsReaderStub.swift in Sources */,
58F7753D2AB8473200425B47 /* BlockedStateErrorMapperStub.swift in Sources */,
- 58FE25D42AA729B5003D1918 /* ActorTests.swift in Sources */,
+ 58FE25D42AA729B5003D1918 /* PacketTunnelActorTests.swift in Sources */,
+ 7A3FD1B52AD4465A0042BEA6 /* AppMessageHandlerTests.swift in Sources */,
58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -4183,6 +4398,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 */,
@@ -4217,7 +4433,6 @@
581DA2762A1E2FD10046ED47 /* WgKeyRotation.swift in Sources */,
580810E52A30E13A00B74552 /* DeviceStateAccessorProtocol.swift in Sources */,
580810E82A30E15500B74552 /* DeviceCheckRemoteServiceProtocol.swift in Sources */,
- 585B1FF22AB0BC69008AD470 /* State+Extensions.swift in Sources */,
58C9B8CE2ABB252E00040B46 /* DeviceCheck.swift in Sources */,
58915D682A25FA080066445B /* DeviceCheckRemoteService.swift in Sources */,
58E9C3842A4EF15300CFDEAC /* WireGuardAdapter+Async.swift in Sources */,
@@ -4237,7 +4452,6 @@
58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */,
58E511E828DDDF2400B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */,
582403822A827E1500163DE8 /* RelaySelectorWrapper.swift in Sources */,
- 58F775432AB9E3EF00425B47 /* AppMessageHandler.swift in Sources */,
58FDF2D92A0BA11A00C2B061 /* DeviceCheckOperation.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -4288,6 +4502,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 */,
@@ -4325,7 +4540,7 @@
buildActionMask = 2147483647;
files = (
58B465702A98C53300467203 /* RequestExecutorTests.swift in Sources */,
- A9A1DE7A2AD5709A0073F689 /* RESTTransportStrategy.swift in Sources */,
+ A988DF212ADD293D00D807EF /* RESTTransportStrategy.swift in Sources */,
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */,
58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */,
58BDEB9D2A98F69E00F578F2 /* MemoryCache.swift in Sources */,
@@ -4378,11 +4593,6 @@
target = 06799ABB28F98E1D00ACD94E /* MullvadREST */;
targetProxy = 58D2239E294C89B50029F5F8 /* PBXContainerItemProxy */;
};
- 062B45BF28FDA85D00746E77 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 06799ABB28F98E1D00ACD94E /* MullvadREST */;
- targetProxy = 58D2239F294C89B50029F5F8 /* PBXContainerItemProxy */;
- };
063F02782902B63F001FA09F /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 063F02722902B63F001FA09F /* RelayCache */;
@@ -4428,20 +4638,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 */;
@@ -4610,6 +4811,11 @@
target = A97F1F402A1F4E1A00ECEFDE /* MullvadTransport */;
targetProxy = A97F1F452A1F4E1A00ECEFDE /* PBXContainerItemProxy */;
};
+ A988DF232ADD305300D807EF /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 5898D29729017DAC00EB5EBA /* RelaySelector */;
+ targetProxy = A988DF222ADD305300D807EF /* PBXContainerItemProxy */;
+ };
A9B2CF712A1F64B20013CC6C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 06799ABB28F98E1D00ACD94E /* MullvadREST */;
@@ -6174,11 +6380,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 8c1dc2b258..2734729c46 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 c9f208523c..9e9e1cc9c0 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: RESTAccountHandling!
+ 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 70ace90b69..45123d39a4 100644
--- a/ios/MullvadVPN/Classes/ObserverList.swift
+++ b/ios/MullvadVPN/Classes/ObserverList.swift
@@ -23,8 +23,8 @@ struct WeakBox<T> {
}
}
- static func == (lhs: WeakBox<T>, rhs: T) -> Bool {
- (lhs.value as AnyObject) === (rhs as AnyObject)
+ static func == (lhs: WeakBox<T>, rhs: WeakBox<T>) -> Bool {
+ (lhs.value as AnyObject) === (rhs.value as AnyObject)
}
}
@@ -36,7 +36,7 @@ final class ObserverList<T> {
lock.lock()
let hasObserver = observers.contains { box in
- box == observer
+ box == WeakBox(observer)
}
if !hasObserver {
@@ -50,7 +50,7 @@ final class ObserverList<T> {
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 3de34f1515..a58603fb04 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: RESTAccountHandling
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: RESTAccountHandling,
appPreferences: AppPreferencesDataSource
) {
self.tunnelManager = tunnelManager
diff --git a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
index f62869f271..42ddd0e2a7 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 a614dd780f..ec6b9a30a8 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: RESTAccountHandling
private var viewController: WelcomeViewController?
@@ -32,7 +32,7 @@ final class WelcomeCoordinator: Coordinator, Poppable, Presenting {
navigationController: RootContainerViewController,
storePaymentManager: StorePaymentManager,
tunnelManager: TunnelManager,
- accountsProxy: REST.AccountsProxy
+ accountsProxy: RESTAccountHandling
) {
self.navigationController = navigationController
self.storePaymentManager = storePaymentManager
diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
index 5a16d473a1..7f17a5b8da 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<RelaysFetchResult, Error>) -> 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<RelayCacheTrackerObserver>()
@@ -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 f2822ec79e..d29bde3d9d 100644
--- a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift
+++ b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift
@@ -14,7 +14,7 @@ import Operations
import StoreKit
class SendStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentResponse>, 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<REST.CreateApplePaymentResponse
private let logger = Logger(label: "SendStoreReceiptOperation")
init(
- apiProxy: REST.APIProxy,
+ apiProxy: APIQuerying,
accountNumber: String,
forceRefresh: Bool,
receiptProperties: [String: Any]?,
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
index 1474559aff..a5cdb6237e 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
@@ -29,8 +29,8 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
private let application: UIApplication
private let paymentQueue: SKPaymentQueue
- private let apiProxy: REST.APIProxy
- private let accountsProxy: REST.AccountsProxy
+ private let apiProxy: APIQuerying
+ private let accountsProxy: RESTAccountHandling
private var observerList = ObserverList<StorePaymentObserver>()
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: RESTAccountHandling
) {
self.application = application
paymentQueue = queue
diff --git a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
index c9621be8ac..15af753a6a 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 99a59cc116..a89a02977c 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 ad4463d562..8731ca2a5c 100644
--- a/ios/MullvadVPN/TunnelManager/DeleteAccountOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/DeleteAccountOperation.swift
@@ -16,14 +16,14 @@ class DeleteAccountOperation: ResultOperation<Void> {
private let logger = Logger(label: "\(DeleteAccountOperation.self)")
private let accountNumber: String
- private let accountsProxy: REST.AccountsProxy
- private let accessTokenManager: REST.AccessTokenManager
+ private let accountsProxy: RESTAccountHandling
+ private let accessTokenManager: RESTAccessTokenManagement
private var task: Cancellable?
init(
dispatchQueue: DispatchQueue,
- accountsProxy: REST.AccountsProxy,
- accessTokenManager: REST.AccessTokenManager,
+ accountsProxy: RESTAccountHandling,
+ accessTokenManager: RESTAccessTokenManagement,
accountNumber: String
) {
self.accountNumber = accountNumber
diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
index d4c86a74f1..54f14355a7 100644
--- a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
@@ -51,7 +51,7 @@ class LoadTunnelConfigurationOperation: ResultOperation<Void> {
}
}
- 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 78541be833..5282a2a7d5 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 7fab35b2e4..99d77a1204 100644
--- a/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift
@@ -18,14 +18,14 @@ class RedeemVoucherOperation: ResultOperation<REST.SubmitVoucherResponse> {
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 b40968d380..46b340ec9d 100644
--- a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift
@@ -17,10 +17,10 @@ import class WireGuardKitTypes.PrivateKey
class RotateKeyOperation: ResultOperation<Void> {
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 d34e51bdbf..cb9afb10dd 100644
--- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
@@ -24,7 +24,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
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<Output>: ResultOperation<Output>
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 ee805c9d92..97a9c6d75b 100644
--- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
@@ -27,22 +27,19 @@ enum SetAccountAction {
var taskName: String {
switch self {
- case .new:
- return "Set new account"
- case .existing:
- return "Set existing account"
- case .unset:
- return "Unset account"
+ case .new: "Set new account"
+ case .existing: "Set existing account"
+ case .unset: "Unset account"
}
}
}
class SetAccountOperation: ResultOperation<StoredAccountData?> {
private let interactor: TunnelInteractor
- private let accountsProxy: REST.AccountsProxy
- private let devicesProxy: REST.DevicesProxy
+ private let accountsProxy: RESTAccountHandling
+ private let devicesProxy: DeviceHandling
private let action: SetAccountAction
- private let accessTokenManager: REST.AccessTokenManager
+ private let accessTokenManager: RESTAccessTokenManagement
private let logger = Logger(label: "SetAccountOperation")
private var tasks: [Cancellable] = []
@@ -50,9 +47,9 @@ class SetAccountOperation: ResultOperation<StoredAccountData?> {
init(
dispatchQueue: DispatchQueue,
interactor: TunnelInteractor,
- accountsProxy: REST.AccountsProxy,
- devicesProxy: REST.DevicesProxy,
- accessTokenManager: REST.AccessTokenManager,
+ accountsProxy: RESTAccountHandling,
+ devicesProxy: DeviceHandling,
+ accessTokenManager: RESTAccessTokenManagement,
action: SetAccountAction
) {
self.interactor = interactor
diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
index afba76a887..86dce49a3a 100644
--- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
@@ -85,7 +85,7 @@ class StartTunnelOperation: ResultOperation<Void> {
}
}
- private func startTunnel(tunnel: Tunnel, selectedRelay: SelectedRelay) throws {
+ private func startTunnel(tunnel: any TunnelProtocol, selectedRelay: SelectedRelay) throws {
var tunnelOptions = PacketTunnelOptions()
do {
@@ -108,7 +108,7 @@ class StartTunnelOperation: ResultOperation<Void> {
try tunnel.start(options: tunnelOptions.rawOptions())
}
- private func makeTunnelProvider(completionHandler: @escaping (Result<Tunnel, Error>) -> Void) {
+ private func makeTunnelProvider(completionHandler: @escaping (Result<any TunnelProtocol, Error>) -> 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 0eb06130d8..044901c37d 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.
func reconnectTunnel(
to nextRelay: NextRelay,
diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift
index 5a9990c886..1ff8f9179b 100644
--- a/ios/MullvadVPN/TunnelManager/Tunnel.swift
+++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift
@@ -17,11 +17,36 @@ typealias TunnelProviderManagerType = NETunnelProviderManager
#endif
protocol TunnelStatusObserver {
- func tunnel(_ tunnel: Tunnel, didReceiveStatus status: NEVPNStatus)
+ func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus)
+}
+
+protocol TunnelProtocol: AnyObject {
+ var status: NEVPNStatus { get }
+ var isOnDemandEnabled: Bool { get set }
+ var startDate: Date? { get }
+
+ init(tunnelProvider: TunnelProviderManagerType)
+
+ func addObserver(_ observer: any TunnelStatusObserver)
+ func removeObserver(_ observer: any TunnelStatusObserver)
+ 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()
@@ -82,10 +107,10 @@ final class Tunnel: Equatable {
}
private let lock = NSLock()
- private var observerList = ObserverList<TunnelStatusObserver>()
+ private var observerList = ObserverList<any TunnelStatusObserver>()
private var _startDate: Date?
- private let tunnelProvider: TunnelProviderManagerType
+ internal let tunnelProvider: TunnelProviderManagerType
init(tunnelProvider: TunnelProviderManagerType) {
self.tunnelProvider = tunnelProvider
@@ -137,7 +162,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)
@@ -146,11 +171,11 @@ final class Tunnel: Equatable {
return observer
}
- func addObserver(_ observer: TunnelStatusObserver) {
+ func addObserver(_ observer: any TunnelStatusObserver) {
observerList.append(observer)
}
- func removeObserver(_ observer: TunnelStatusObserver) {
+ func removeObserver(_ observer: any TunnelStatusObserver) {
observerList.remove(observer)
}
@@ -197,33 +222,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 26f40bc0d4..15f118f035 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
@@ -13,11 +13,11 @@ import PacketTunnelCore
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 34cfca0d1a..ab178dba38 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: BackgroundTaskProvider
+ fileprivate let tunnelStore: any TunnelStoreProtocol
+ private let relayCacheTracker: RelayCacheTrackerProtocol
+ private let accountsProxy: RESTAccountHandling
+ private let devicesProxy: DeviceHandling
+ private let apiProxy: APIQuerying
+ private let accessTokenManager: RESTAccessTokenManagement
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: BackgroundTaskProvider,
+ tunnelStore: any TunnelStoreProtocol,
+ relayCacheTracker: RelayCacheTrackerProtocol,
+ accountsProxy: RESTAccountHandling,
+ devicesProxy: DeviceHandling,
+ apiProxy: APIQuerying,
+ accessTokenManager: RESTAccessTokenManagement
) {
self.application = application
self.tunnelStore = tunnelStore
@@ -289,7 +289,7 @@ final class TunnelManager: StorePaymentObserver {
finish(result.error)
}
} catch {
- if let error = error as? NoRelaysSatisfyingConstraintsError {
+ if error is NoRelaysSatisfyingConstraintsError {
_ = self.setTunnelStatus { tunnelStatus in
tunnelStatus.state = .error(.noRelaysSatisfyingConstraints)
}
@@ -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() }
@@ -867,7 +867,7 @@ final class TunnelManager: StorePaymentObserver {
}
}
- private func subscribeVPNStatusObserver(tunnel: Tunnel) {
+ private func subscribeVPNStatusObserver(tunnel: any TunnelProtocol) {
nslock.lock()
defer { nslock.unlock() }
@@ -1255,19 +1255,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 0000000000..135e79220c
--- /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 07266bcf17..b93c33ac45 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelStore.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelStore.swift
@@ -11,16 +11,23 @@ import MullvadLogging
import NetworkExtension
import UIKit
+protocol TunnelStoreProtocol {
+ associatedtype TunnelType: TunnelProtocol, Equatable
+ func getPersistentTunnels() -> [TunnelType]
+ func createNewTunnel() -> TunnelType
+}
+
/// Wrapper around system VPN tunnels.
-final class TunnelStore: TunnelStatusObserver {
+final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver {
+ typealias TunnelType = Tunnel
private let logger = Logger(label: "TunnelStore")
private let lock = NSLock()
/// Persistent tunnels registered with the system.
- private var persistentTunnels: [Tunnel] = []
+ private var persistentTunnels: [TunnelType] = []
/// Newly created tunnels, stored as collection of weak boxes.
- private var newTunnels: [WeakBox<Tunnel>] = []
+ private var newTunnels: [WeakBox<TunnelType>] = []
init(application: UIApplication) {
NotificationCenter.default.addObserver(
@@ -31,7 +38,7 @@ final class TunnelStore: TunnelStatusObserver {
)
}
- func getPersistentTunnels() -> [Tunnel] {
+ func getPersistentTunnels() -> [TunnelType] {
lock.lock()
defer { lock.unlock() }
@@ -66,12 +73,12 @@ final class TunnelStore: TunnelStatusObserver {
}
}
- func createNewTunnel() -> Tunnel {
+ func createNewTunnel() -> TunnelType {
lock.lock()
defer { lock.unlock() }
let tunnelProviderManager = TunnelProviderManagerType()
- let tunnel = Tunnel(tunnelProvider: tunnelProviderManager)
+ let tunnel = TunnelType(tunnelProvider: tunnelProviderManager)
tunnel.addObserver(self)
newTunnels = newTunnels.filter { $0.value != nil }
@@ -82,20 +89,23 @@ 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)
+ // swiftlint:disable:next force_cast
+ handleTunnelStatus(tunnel: tunnel as! TunnelType, status: status)
}
- private func handleTunnelStatus(tunnel: Tunnel, status: NEVPNStatus) {
- if status == .invalid, let index = persistentTunnels.firstIndex(of: tunnel) {
+ private func handleTunnelStatus(tunnel: TunnelType, status: NEVPNStatus) {
+ if status == .invalid,
+ let index = persistentTunnels.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.compactMap({ $0.value }).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 0000000000..046244582c
--- /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 BackgroundTaskProvider {
+ func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
+
+ func beginBackgroundTask(
+ withName taskName: String?,
+ expirationHandler handler: (() -> Void)?
+ ) -> UIBackgroundTaskIdentifier
+}
+
+extension UIApplication: BackgroundTaskProvider {}
+
+#endif
diff --git a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift
index 2b880ab1c9..55f7e0eb88 100644
--- a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift
@@ -16,13 +16,13 @@ import Operations
class UpdateAccountDataOperation: ResultOperation<Void> {
private let logger = Logger(label: "UpdateAccountDataOperation")
private let interactor: TunnelInteractor
- private let accountsProxy: REST.AccountsProxy
+ private let accountsProxy: RESTAccountHandling
private var task: Cancellable?
init(
dispatchQueue: DispatchQueue,
interactor: TunnelInteractor,
- accountsProxy: REST.AccountsProxy
+ accountsProxy: RESTAccountHandling
) {
self.interactor = interactor
self.accountsProxy = accountsProxy
diff --git a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift
index 85700d8dca..6803dd7d87 100644
--- a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift
@@ -16,14 +16,14 @@ import class WireGuardKitTypes.PublicKey
class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData> {
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 a08bd10064..05d2d40465 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: RESTAccountHandling
var didReceivePaymentEvent: ((StorePaymentEvent) -> Void)?
var didReceiveDeviceState: ((DeviceState) -> Void)?
@@ -27,7 +27,7 @@ final class AccountInteractor {
init(
storePaymentManager: StorePaymentManager,
tunnelManager: TunnelManager,
- accountsProxy: REST.AccountsProxy
+ accountsProxy: RESTAccountHandling
) {
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 5f9065a006..581b5e0d0a 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 b1bb49ccb2..9dbf3a5b6b 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 fa76391227..26da26f147 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: RESTAccountHandling
private let shouldVerifyVoucherAsAccount: Bool
private var tasks: [Cancellable] = []
@@ -23,7 +23,7 @@ final class RedeemVoucherInteractor {
init(
tunnelManager: TunnelManager,
- accountsProxy: REST.AccountsProxy,
+ accountsProxy: RESTAccountHandling,
verifyVoucherAsAccount: Bool
) {
self.tunnelManager = tunnelManager
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift
index 2af17ec147..3454516808 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 0000000000..7602a1fe7a
--- /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<REST.ServerRelaysCacheResponse>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func createApplePayment(
+ accountNumber: String,
+ receiptString: Data
+ ) -> any RESTRequestExecutor<REST.CreateApplePaymentResponse> {
+ RESTRequestExecutorStub<REST.CreateApplePaymentResponse>(success: {
+ .timeAdded(42, .distantFuture)
+ })
+ }
+
+ func createApplePayment(
+ accountNumber: String,
+ receiptString: Data,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<REST.CreateApplePaymentResponse>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func sendProblemReport(
+ _ body: REST.ProblemReportRequest,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<Void>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func submitVoucher(
+ voucherCode: String,
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping ProxyCompletionHandler<REST.SubmitVoucherResponse>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+}
diff --git a/ios/MullvadVPNTests/AccessTokenManager+Stubs.swift b/ios/MullvadVPNTests/AccessTokenManager+Stubs.swift
new file mode 100644
index 0000000000..a36719314e
--- /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: RESTAccessTokenManagement {
+ func getAccessToken(
+ accountNumber: String,
+ completionHandler: @escaping ProxyCompletionHandler<REST.AccessTokenData>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func invalidateAllTokens() {}
+}
diff --git a/ios/MullvadVPNTests/AccountsProxy+Stubs.swift b/ios/MullvadVPNTests/AccountsProxy+Stubs.swift
new file mode 100644
index 0000000000..6dc2afdade
--- /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: RESTAccountHandling {
+ func createAccount(
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping MullvadREST.ProxyCompletionHandler<REST.NewAccountData>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func getAccountData(accountNumber: String) -> any RESTRequestExecutor<Account> {
+ RESTRequestExecutorStub<Account>(success: {
+ Account(id: accountNumber, expiry: .distantFuture, maxDevices: 1, canAddDevices: true)
+ })
+ }
+
+ func getAccountData(
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Account>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func deleteAccount(
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Void>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+}
diff --git a/ios/MullvadVPNTests/AddressCacheTests.swift b/ios/MullvadVPNTests/AddressCacheTests.swift
index 5a5dedfe1a..a42aa3b8dd 100644
--- a/ios/MullvadVPNTests/AddressCacheTests.swift
+++ b/ios/MullvadVPNTests/AddressCacheTests.swift
@@ -42,10 +42,14 @@ final class AddressCacheTests: XCTestCase {
addressCache.setEndpoints([apiEndpoint])
let dateBeforeUpdate = addressCache.getLastUpdateDate()
+ // Calling `Date()` several times in a row can result in the same Date object being returned.
+ // Force a sleep before setting the next endpoint to avoid getting the same Date object twice in a row.
+ Thread.sleep(forTimeInterval: Duration.milliseconds(10).timeInterval)
addressCache.setEndpoints([apiEndpoint])
let dateAfterUpdate = addressCache.getLastUpdateDate()
- XCTAssertNotEqual(dateBeforeUpdate, dateAfterUpdate)
+ let timeDifference = dateAfterUpdate.timeIntervalSince(dateBeforeUpdate)
+ XCTAssertNotEqual(0.0, timeDifference)
}
func testSetEndpointsDoesNotDoAnythingIfSettingEmptyEndpoints() throws {
diff --git a/ios/MullvadVPNTests/DevicesProxy+Stubs.swift b/ios/MullvadVPNTests/DevicesProxy+Stubs.swift
new file mode 100644
index 0000000000..3ec0f3faff
--- /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<Device>
+ ) -> 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<Device>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func deleteDevice(
+ accountNumber: String,
+ identifier: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Bool>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func rotateDeviceKey(
+ accountNumber: String,
+ identifier: String,
+ publicKey: PublicKey,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Device>
+ ) -> Cancellable {
+ AnyCancellable()
+ }
+}
diff --git a/ios/MullvadVPNTests/RESTRequestExecutor+Stubs.swift b/ios/MullvadVPNTests/RESTRequestExecutor+Stubs.swift
new file mode 100644
index 0000000000..cd7fad6c76
--- /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<Success>: RESTRequestExecutor {
+ typealias Success = Success
+
+ var success: (() -> Success)?
+
+ func execute(completionHandler: @escaping (Result<Success, Error>) -> Void) -> Cancellable {
+ AnyCancellable()
+ }
+
+ func execute(
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping (Result<Success, Error>) -> 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 0000000000..ef6fcd9ab6
--- /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<RelaysFetchResult, Error>) -> 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 fa8fc6659b..b390592b36 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 0000000000..8bca08f4f8
--- /dev/null
+++ b/ios/MullvadVPNTests/ServerRelaysResponse+Stubs.swift
@@ -0,0 +1,211 @@
+//
+// 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
+import WireGuardKitTypes
+
+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: PrivateKey().publicKey.rawValue,
+ includeInCountry: true
+ ),
+ REST.ServerRelay(
+ hostname: "se10-wireguard",
+ active: true,
+ owned: true,
+ location: "se-got",
+ provider: "",
+ weight: 1000,
+ ipv4AddrIn: .loopback,
+ ipv6AddrIn: .loopback,
+ publicKey: PrivateKey().publicKey.rawValue,
+ includeInCountry: true
+ ),
+ REST.ServerRelay(
+ hostname: "se2-wireguard",
+ active: true,
+ owned: true,
+ location: "se-sto",
+ provider: "",
+ weight: 50,
+ ipv4AddrIn: .loopback,
+ ipv6AddrIn: .loopback,
+ publicKey: PrivateKey().publicKey.rawValue,
+ includeInCountry: true
+ ),
+ REST.ServerRelay(
+ hostname: "se6-wireguard",
+ active: true,
+ owned: true,
+ location: "se-sto",
+ provider: "",
+ weight: 100,
+ ipv4AddrIn: .loopback,
+ ipv6AddrIn: .loopback,
+ publicKey: PrivateKey().publicKey.rawValue,
+ includeInCountry: true
+ ),
+ REST.ServerRelay(
+ hostname: "us-dal-wg-001",
+ active: true,
+ owned: true,
+ location: "us-dal",
+ provider: "",
+ weight: 100,
+ ipv4AddrIn: .loopback,
+ ipv6AddrIn: .loopback,
+ publicKey: PrivateKey().publicKey.rawValue,
+ includeInCountry: true
+ ),
+ REST.ServerRelay(
+ hostname: "us-nyc-wg-301",
+ active: true,
+ owned: true,
+ location: "us-nyc",
+ provider: "",
+ weight: 100,
+ ipv4AddrIn: .loopback,
+ ipv6AddrIn: .loopback,
+ publicKey: PrivateKey().publicKey.rawValue,
+ 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 0000000000..958c50e252
--- /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 0000000000..5465839426
--- /dev/null
+++ b/ios/MullvadVPNTests/TunnelStore+Stubs.swift
@@ -0,0 +1,74 @@
+//
+// 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 {
+ typealias TunnelType = TunnelStub
+ func getPersistentTunnels() -> [TunnelType] {
+ []
+ }
+
+ func createNewTunnel() -> TunnelType {
+ TunnelStub(status: .invalid, isOnDemandEnabled: false)
+ }
+}
+
+class DummyTunnelStatusObserver: TunnelStatusObserver {
+ func tunnel(_ tunnel: any TunnelProtocol, didReceiveStatus status: NEVPNStatus) {}
+}
+
+final class TunnelStub: TunnelProtocol, Equatable {
+ convenience init(tunnelProvider: TunnelProviderManagerType) {
+ self.init(status: .invalid, isOnDemandEnabled: false)
+ }
+
+ 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 0000000000..8b8fbe4999
--- /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: BackgroundTaskProvider {
+ 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 53889cecd9..0dcbcb3287 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: BackgroundTaskProvider
public let cancelUponExpiration: Bool
private var taskIdentifier: UIBackgroundTaskIdentifier?
- public init(application: UIApplication, name: String, cancelUponExpiration: Bool) {
+ public init(application: BackgroundTaskProvider, 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 9ad8bcbc75..bbefc30215 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: RESTAccountHandling
+ private let devicesProxy: DeviceHandling
- init(accountsProxy: REST.AccountsProxy, devicesProxy: REST.DevicesProxy) {
+ init(accountsProxy: RESTAccountHandling, devicesProxy: DeviceHandling) {
self.accountsProxy = accountsProxy
self.devicesProxy = devicesProxy
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift b/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift
index f7d22d16db..decf72b1bc 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift
@@ -57,6 +57,10 @@ public struct BlockedStateErrorMapper: BlockedStateErrorMapperProtocol {
// packet tunnel provider.
return .tunnelAdapter
+ case is PublicKeyError:
+ // Returned when there is an endpoint but its public key is invalid.
+ return .invalidRelayPublicKey
+
default:
// Everything else in case we introduce new errors and forget to handle them.
return .unknown
diff --git a/ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift b/ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift
index e4fa756b25..a1933510bd 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: RESTAccountHandling
+ private let devicesProxy: DeviceHandling
- init(accountsProxy: REST.AccountsProxy, devicesProxy: REST.DevicesProxy) {
+ init(accountsProxy: RESTAccountHandling, devicesProxy: DeviceHandling) {
self.accountsProxy = accountsProxy
self.devicesProxy = devicesProxy
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift b/ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift
deleted file mode 100644
index 99862e1521..0000000000
--- a/ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// State+Extensions.swift
-// PacketTunnel
-//
-// Created by pronebird on 12/09/2023.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-import MullvadTypes
-import PacketTunnelCore
-
-extension State {
- var packetTunnelStatus: PacketTunnelStatus {
- var status = PacketTunnelStatus()
-
- switch self {
- case let .connecting(connState),
- let .connected(connState),
- let .reconnecting(connState),
- let .disconnecting(connState):
- switch connState.networkReachability {
- case .reachable:
- status.isNetworkReachable = true
- case .unreachable:
- status.isNetworkReachable = false
- case .undetermined:
- // TODO: fix me
- status.isNetworkReachable = true
- }
-
- status.numberOfFailedAttempts = connState.connectionAttemptCount
- status.tunnelRelay = connState.selectedRelay.packetTunnelRelay
-
- case .disconnected, .initial:
- break
-
- case let .error(blockedState):
- status.blockedStateReason = blockedState.reason
- }
-
- return status
- }
-
- var relayConstraints: RelayConstraints? {
- switch self {
- case let .connecting(connState), let .connected(connState), let .reconnecting(connState):
- return connState.relayConstraints
-
- case let .error(blockedState):
- return blockedState.relayConstraints
-
- case .initial, .disconnecting, .disconnected:
- return nil
- }
- }
-}
diff --git a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
index c4a731aa78..6ba03db89b 100644
--- a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
+++ b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
@@ -13,6 +13,15 @@ import struct WireGuardKitTypes.IPAddressRange
import class WireGuardKitTypes.PrivateKey
import class WireGuardKitTypes.PublicKey
+/// Error returned when there is an endpoint but its public key is invalid.
+public struct PublicKeyError: LocalizedError {
+ let endpoint: MullvadEndpoint
+
+ public var errorDescription: String? {
+ "Public key is invalid, endpoint: \(endpoint)"
+ }
+}
+
/// Struct building tunnel adapter configuration.
struct ConfigurationBuilder {
var privateKey: PrivateKey
@@ -20,22 +29,28 @@ struct ConfigurationBuilder {
var dns: SelectedDNSServers?
var endpoint: MullvadEndpoint?
- func makeConfiguration() -> TunnelAdapterConfiguration {
+ func makeConfiguration() throws -> TunnelAdapterConfiguration {
return TunnelAdapterConfiguration(
privateKey: privateKey,
interfaceAddresses: interfaceAddresses,
dns: dnsServers,
- peer: peer
+ peer: try peer
)
}
private var peer: TunnelPeer? {
- guard let endpoint else { return nil }
+ get throws {
+ guard let endpoint else { return nil }
- return TunnelPeer(
- endpoint: .ipv4(endpoint.ipv4Relay),
- publicKey: PublicKey(rawValue: endpoint.publicKey)!
- )
+ guard let publicKey = PublicKey(rawValue: endpoint.publicKey) else {
+ throw PublicKeyError(endpoint: endpoint)
+ }
+
+ return TunnelPeer(
+ endpoint: .ipv4(endpoint.ipv4Relay),
+ publicKey: publicKey
+ )
+ }
}
private var dnsServers: [IPAddress] {
diff --git a/ios/PacketTunnelCore/Actor/Actor+ConnectionMonitoring.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift
index c2bc921b05..c2bc921b05 100644
--- a/ios/PacketTunnelCore/Actor/Actor+ConnectionMonitoring.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift
diff --git a/ios/PacketTunnelCore/Actor/Actor+ErrorState.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift
index b95e0c8d5e..b95e0c8d5e 100644
--- a/ios/PacketTunnelCore/Actor/Actor+ErrorState.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift
diff --git a/ios/PacketTunnelCore/Actor/Actor+Extensions.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
index 71bd13c057..71bd13c057 100644
--- a/ios/PacketTunnelCore/Actor/Actor+Extensions.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
diff --git a/ios/PacketTunnelCore/Actor/Actor+KeyPolicy.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift
index 0cf8351523..5ab13353e9 100644
--- a/ios/PacketTunnelCore/Actor/Actor+KeyPolicy.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift
@@ -159,7 +159,7 @@ extension PacketTunnelActor {
/**
Internal helper that transitions key policy from `.usePrior` to `.useCurrent`.
- - Parameter keyPolicy: a reference to key policy hend either in connection state or blocked state struct.
+ - Parameter keyPolicy: a reference to key policy held either in connection state or blocked state struct.
- Returns: `true` when the policy was modified, otherwise `false`.
*/
private func setCurrentKeyPolicy(_ keyPolicy: inout KeyPolicy) -> Bool {
diff --git a/ios/PacketTunnelCore/Actor/Actor+NetworkReachability.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+NetworkReachability.swift
index 34a4e02ed1..34a4e02ed1 100644
--- a/ios/PacketTunnelCore/Actor/Actor+NetworkReachability.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+NetworkReachability.swift
diff --git a/ios/PacketTunnelCore/Actor/Actor+Public.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift
index 7a50477c29..b54d397ca4 100644
--- a/ios/PacketTunnelCore/Actor/Actor+Public.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift
@@ -1,5 +1,5 @@
//
-// Actor+Public.swift
+// PacketTunnelActor+Public.swift
// PacketTunnelCore
//
// Created by pronebird on 27/09/2023.
diff --git a/ios/PacketTunnelCore/Actor/Actor+SleepCycle.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+SleepCycle.swift
index c6339ce11e..c6339ce11e 100644
--- a/ios/PacketTunnelCore/Actor/Actor+SleepCycle.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+SleepCycle.swift
diff --git a/ios/PacketTunnelCore/Actor/Actor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index 546ae3f59e..c82b0f8ccb 100644
--- a/ios/PacketTunnelCore/Actor/Actor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -1,5 +1,5 @@
//
-// Actor.swift
+// PacketTunnelActor.swift
// PacketTunnel
//
// Created by pronebird on 30/06/2023.
@@ -378,3 +378,5 @@ extension PacketTunnelActor {
}
}
}
+
+extension PacketTunnelActor: PacketTunnelActorProtocol {}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift
new file mode 100644
index 0000000000..d5acf6bae3
--- /dev/null
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift
@@ -0,0 +1,16 @@
+//
+// PacketTunnelActorProtocol.swift
+// PacketTunnelCoreTests
+//
+// Created by Jon Petersson on 2023-10-11.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public protocol PacketTunnelActorProtocol {
+ var state: State { get async }
+
+ func reconnect(to nextRelay: NextRelay)
+ func notifyKeyRotation(date: Date?)
+}
diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift
index 71a090acb2..6e6d40bfb7 100644
--- a/ios/PacketTunnelCore/Actor/State+Extensions.swift
+++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift
@@ -1,5 +1,5 @@
//
-// State+.swift
+// State+Extensions.swift
// PacketTunnelCore
//
// Created by pronebird on 08/09/2023.
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadTypes
import class WireGuardKitTypes.PrivateKey
extension State {
@@ -36,6 +37,50 @@ extension State {
}
}
+ var packetTunnelStatus: PacketTunnelStatus {
+ var status = PacketTunnelStatus()
+
+ switch self {
+ case let .connecting(connState),
+ let .connected(connState),
+ let .reconnecting(connState),
+ let .disconnecting(connState):
+ switch connState.networkReachability {
+ case .reachable:
+ status.isNetworkReachable = true
+ case .unreachable:
+ status.isNetworkReachable = false
+ case .undetermined:
+ // TODO: fix me
+ status.isNetworkReachable = true
+ }
+
+ status.numberOfFailedAttempts = connState.connectionAttemptCount
+ status.tunnelRelay = connState.selectedRelay.packetTunnelRelay
+
+ case .disconnected, .initial:
+ break
+
+ case let .error(blockedState):
+ status.blockedStateReason = blockedState.reason
+ }
+
+ return status
+ }
+
+ public var relayConstraints: RelayConstraints? {
+ switch self {
+ case let .connecting(connState), let .connected(connState), let .reconnecting(connState):
+ return connState.relayConstraints
+
+ case let .error(blockedState):
+ return blockedState.relayConstraints
+
+ case .initial, .disconnecting, .disconnected:
+ return nil
+ }
+ }
+
// MARK: - Logging
func logFormat() -> String {
@@ -105,7 +150,7 @@ extension BlockedStateReason {
return true
case .noRelaysSatisfyingConstraints, .readSettings, .invalidAccount, .deviceRevoked, .tunnelAdapter, .unknown,
- .deviceLoggedOut, .outdatedSchema:
+ .deviceLoggedOut, .outdatedSchema, .invalidRelayPublicKey:
return false
}
}
diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift
index 36a427e496..eba9f1baa8 100644
--- a/ios/PacketTunnelCore/Actor/State.swift
+++ b/ios/PacketTunnelCore/Actor/State.swift
@@ -1,5 +1,5 @@
//
-// States.swift
+// State.swift
// PacketTunnel
//
// Created by pronebird on 07/08/2023.
@@ -105,7 +105,7 @@ public struct ConnectionState {
/// This is primarily used by packet tunnel for updating constraints in tunnel provider.
public var relayConstraints: RelayConstraints
- /// Last WG key read from setings.
+ /// Last WG key read from settings.
/// Can be `nil` if moved to `keyPolicy`.
public var currentKey: PrivateKey?
@@ -187,6 +187,9 @@ public enum BlockedStateReason: String, Codable, Equatable {
/// Tunnel adapter error.
case tunnelAdapter
+ /// Invalid public key.
+ case invalidRelayPublicKey
+
/// Unidentified reason.
case unknown
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider/AppMessageHandler.swift b/ios/PacketTunnelCore/IPC/AppMessageHandler.swift
index 4071468799..d72e2bf05b 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/AppMessageHandler.swift
+++ b/ios/PacketTunnelCore/IPC/AppMessageHandler.swift
@@ -8,17 +8,16 @@
import Foundation
import MullvadLogging
-import PacketTunnelCore
/**
Actor handling packet tunnel IPC (app) messages and patching them through to the right facility.
*/
-struct AppMessageHandler {
+public struct AppMessageHandler {
private let logger = Logger(label: "AppMessageHandler")
- private let packetTunnelActor: PacketTunnelActor
- private let urlRequestProxy: URLRequestProxy
+ private let packetTunnelActor: PacketTunnelActorProtocol
+ private let urlRequestProxy: URLRequestProxyProtocol
- init(packetTunnelActor: PacketTunnelActor, urlRequestProxy: URLRequestProxy) {
+ public init(packetTunnelActor: PacketTunnelActorProtocol, urlRequestProxy: URLRequestProxyProtocol) {
self.packetTunnelActor = packetTunnelActor
self.urlRequestProxy = urlRequestProxy
}
@@ -34,7 +33,7 @@ struct AppMessageHandler {
the acknowledgment from IPC before starting next operation, hence it's critical to return as soon as possible.
(See `TunnelManager.reconnectTunnel()`, `SendTunnelProviderMessageOperation`)
*/
- func handleAppMessage(_ messageData: Data) async -> Data? {
+ public func handleAppMessage(_ messageData: Data) async -> Data? {
guard let message = decodeMessage(messageData) else { return nil }
logger.debug("Received app message: \(message)")
@@ -51,7 +50,7 @@ struct AppMessageHandler {
return await encodeReply(packetTunnelActor.state.packetTunnelStatus)
case .privateKeyRotation:
- packetTunnelActor.notifyKeyRotation(date: nil)
+ packetTunnelActor.notifyKeyRotation(date: Date())
return nil
case let .reconnectTunnel(nextRelay):
diff --git a/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift b/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift
index fb4f6cbbfc..11d7f77d2e 100644
--- a/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift
+++ b/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift
@@ -86,3 +86,5 @@ extension URLRequestProxy {
}
}
}
+
+extension URLRequestProxy: URLRequestProxyProtocol {}
diff --git a/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxyProtocol.swift b/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxyProtocol.swift
new file mode 100644
index 0000000000..da52b447ba
--- /dev/null
+++ b/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxyProtocol.swift
@@ -0,0 +1,15 @@
+//
+// URLRequestProxyProtocol.swift
+// PacketTunnelCore
+//
+// Created by Jon Petersson on 2023-10-11.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public protocol URLRequestProxyProtocol {
+ func sendRequest(_ proxyRequest: ProxyURLRequest, completionHandler: @escaping @Sendable (ProxyURLResponse) -> Void)
+ func sendRequest(_ proxyRequest: ProxyURLRequest) async -> ProxyURLResponse
+ func cancelRequest(identifier: UUID)
+}
diff --git a/ios/PacketTunnelCoreTests/ActorTests.swift b/ios/PacketTunnelCoreTests/ActorTests.swift
deleted file mode 100644
index c82119759b..0000000000
--- a/ios/PacketTunnelCoreTests/ActorTests.swift
+++ /dev/null
@@ -1,196 +0,0 @@
-//
-// ActorTests.swift
-// PacketTunnelCoreTests
-//
-// Created by pronebird on 05/09/2023.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import Combine
-import MullvadSettings
-import MullvadTypes
-import Network
-@testable import PacketTunnelCore
-@testable import RelaySelector
-import struct WireGuardKitTypes.IPAddressRange
-import class WireGuardKitTypes.PrivateKey
-import XCTest
-
-final class ActorTests: XCTestCase {
- private var actor: PacketTunnelActor?
- private var stateSink: Combine.Cancellable?
-
- override func tearDown() async throws {
- stateSink?.cancel()
- actor?.stop()
- await actor?.waitUntilDisconnected()
- }
-
- /**
- Test a happy path start sequence.
-
- As actor should transition through the following states: .initial → .connecting → .connected
- */
- func testStart() async throws {
- let actor = PacketTunnelActor.mock()
- let initialStateExpectation = expectation(description: "Expect initial state")
- let connectingExpectation = expectation(description: "Expect connecting state")
- let connectedStateExpectation = expectation(description: "Expect connected state")
-
- let allExpectations = [initialStateExpectation, connectingExpectation, connectedStateExpectation]
-
- stateSink = await actor.$state
- .receive(on: DispatchQueue.main)
- .sink { newState in
- switch newState {
- case .initial:
- initialStateExpectation.fulfill()
- case .connecting:
- connectingExpectation.fulfill()
- case .connected:
- connectedStateExpectation.fulfill()
- default:
- break
- }
- }
-
- self.actor = actor
-
- actor.start(options: StartOptions(launchSource: .app))
-
- await fulfillment(of: allExpectations, timeout: 1, enforceOrder: true)
- }
-
- /**
- Test stopping connected tunnel.
-
- As actor should transition through the following states: .connected → .disconnecting → .disconnected
- */
- func testStopConnectedTunnel() async throws {
- let actor = PacketTunnelActor.mock()
- let connectedStateExpectation = expectation(description: "Expect connected state")
- let disconnectingStateExpectation = expectation(description: "Expect disconnecting state")
- let disconnectedStateExpectation = expectation(description: "Expect disconnected state")
-
- let allExpectations = [connectedStateExpectation, disconnectingStateExpectation, disconnectedStateExpectation]
-
- stateSink = await actor.$state
- .receive(on: DispatchQueue.main)
- .sink { newState in
- switch newState {
- case .connected:
- connectedStateExpectation.fulfill()
- actor.stop()
-
- case .disconnecting:
- disconnectingStateExpectation.fulfill()
-
- case .disconnected:
- disconnectedStateExpectation.fulfill()
-
- default:
- break
- }
- }
-
- self.actor = actor
-
- actor.start(options: StartOptions(launchSource: .app))
-
- await fulfillment(of: allExpectations, timeout: 1, enforceOrder: true)
- }
-
- /**
- Each subsequent connection attempt should produce a single change to `state` containing the incremented attempt counter and new relay.
-
- .connecting (attempt: 0) → .connecting (attempt: 1) → .connecting (attempt: 2) → ...
- */
- func testConnectionAttemptTransition() async throws {
- let tunnelMonitor = TunnelMonitorStub { _, _ in }
- let actor = PacketTunnelActor.mock(tunnelMonitor: tunnelMonitor)
- let connectingStateExpectation = expectation(description: "Expect connecting state")
- connectingStateExpectation.expectedFulfillmentCount = 5
-
- var nextAttemptCount: UInt = 0
- stateSink = await actor.$state
- .receive(on: DispatchQueue.main)
- .sink { newState in
- switch newState {
- case .initial:
- break
-
- case let .connecting(connState):
- XCTAssertEqual(connState.connectionAttemptCount, nextAttemptCount)
- nextAttemptCount += 1
- connectingStateExpectation.fulfill()
-
- if nextAttemptCount < connectingStateExpectation.expectedFulfillmentCount {
- tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10))
- }
-
- default:
- XCTFail("Received invalid state: \(newState.name).")
- }
- }
-
- self.actor = actor
-
- actor.start(options: StartOptions(launchSource: .app))
-
- await fulfillment(of: [connectingStateExpectation], timeout: 1)
- }
-
- /**
- Each subsequent re-connection attempt should produce a single change to `state` containing the incremented attempt counter and new relay.
-
- .reconnecting (attempt: 0) → .reconnecting (attempt: 1) → .reconnecting (attempt: 2) → ...
- */
- func testReconnectionAttemptTransition() async throws {
- let tunnelMonitor = TunnelMonitorStub { _, _ in }
- let actor = PacketTunnelActor.mock(tunnelMonitor: tunnelMonitor)
- let connectingStateExpectation = expectation(description: "Expect connecting state")
- let connectedStateExpectation = expectation(description: "Expect connected state")
- let reconnectingStateExpectation = expectation(description: "Expect reconnecting state")
- reconnectingStateExpectation.expectedFulfillmentCount = 5
-
- var nextAttemptCount: UInt = 0
- stateSink = await actor.$state
- .receive(on: DispatchQueue.main)
- .sink { newState in
- switch newState {
- case .initial:
- break
-
- case .connecting:
- connectingStateExpectation.fulfill()
- tunnelMonitor.dispatch(.connectionEstablished, after: .milliseconds(10))
-
- case .connected:
- connectedStateExpectation.fulfill()
- tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10))
-
- case let .reconnecting(connState):
- XCTAssertEqual(connState.connectionAttemptCount, nextAttemptCount)
- nextAttemptCount += 1
- reconnectingStateExpectation.fulfill()
-
- if nextAttemptCount < reconnectingStateExpectation.expectedFulfillmentCount {
- tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10))
- }
-
- default:
- XCTFail("Received invalid state: \(newState.name).")
- }
- }
-
- self.actor = actor
-
- actor.start(options: StartOptions(launchSource: .app))
-
- await fulfillment(
- of: [connectingStateExpectation, connectedStateExpectation, reconnectingStateExpectation],
- timeout: 1,
- enforceOrder: true
- )
- }
-}
diff --git a/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
new file mode 100644
index 0000000000..bea5a83e8b
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
@@ -0,0 +1,114 @@
+//
+// AppMessageHandlerTests.swift
+// PacketTunnelCoreTests
+//
+// Created by Jon Petersson on 2023-09-28.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+@testable import MullvadREST
+import MullvadTypes
+import PacketTunnelCore
+import RelaySelector
+import XCTest
+
+final class AppMessageHandlerTests: XCTestCase {
+ func testHandleAppMessageForSendURLRequest() async throws {
+ let sendRequestExpectation = expectation(description: "Expect sending request")
+
+ let actor = PacketTunnelActorStub()
+ let urlRequestProxy = URLRequestProxyStub(sendRequestExpectation: sendRequestExpectation)
+ let appMessageHandler = createAppMessageHandler(urlRequestProxy: urlRequestProxy)
+
+ let url = URL(string: "localhost")!
+ let urlRequest = ProxyURLRequest(
+ id: UUID(),
+ urlRequest: URLRequest(url: url)
+ )!
+
+ _ = try? await appMessageHandler.handleAppMessage(
+ TunnelProviderMessage.sendURLRequest(urlRequest).encode()
+ )
+
+ await fulfillment(of: [sendRequestExpectation], timeout: 1)
+ }
+
+ func testHandleAppMessageForCancelURLRequest() async throws {
+ let cancelRequestExpectation = expectation(description: "Expect cancelling request")
+
+ let actor = PacketTunnelActorStub()
+ let urlRequestProxy = URLRequestProxyStub(cancelRequestExpectation: cancelRequestExpectation)
+ let appMessageHandler = createAppMessageHandler(urlRequestProxy: urlRequestProxy)
+
+ _ = try? await appMessageHandler.handleAppMessage(
+ TunnelProviderMessage.cancelURLRequest(UUID()).encode()
+ )
+
+ await fulfillment(of: [cancelRequestExpectation], timeout: 1)
+ }
+
+ func testHandleAppMessageForTunnelStatus() async throws {
+ let stateExpectation = expectation(description: "Expect getting state")
+
+ let actor = PacketTunnelActorStub(stateExpectation: stateExpectation)
+ let appMessageHandler = createAppMessageHandler(actor: actor)
+
+ _ = try? await appMessageHandler.handleAppMessage(
+ TunnelProviderMessage.getTunnelStatus.encode()
+ )
+
+ await fulfillment(of: [stateExpectation], timeout: 1)
+ }
+
+ func testHandleAppMessageForKeyRotation() async throws {
+ let keyRotationExpectation = expectation(description: "Expect key rotation")
+
+ let actor = PacketTunnelActorStub(keyRotationExpectation: keyRotationExpectation)
+ let appMessageHandler = createAppMessageHandler(actor: actor)
+
+ _ = try? await appMessageHandler.handleAppMessage(
+ TunnelProviderMessage.privateKeyRotation.encode()
+ )
+
+ await fulfillment(of: [keyRotationExpectation], timeout: 1)
+ }
+
+ func testHandleAppMessageForReconnectTunnel() async throws {
+ let reconnectExpectation = expectation(description: "Expect reconnecting state")
+
+ let actor = PacketTunnelActorStub(reconnectExpectation: reconnectExpectation)
+ let appMessageHandler = createAppMessageHandler(actor: actor)
+
+ let relayConstraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")))
+ let selectorResult = try XCTUnwrap(try? RelaySelector.evaluate(
+ relays: ServerRelaysResponseStubs.sampleRelays,
+ constraints: relayConstraints,
+ numberOfFailedAttempts: 0
+ ))
+
+ let selectedRelay = SelectedRelay(
+ endpoint: selectorResult.endpoint,
+ hostname: selectorResult.relay.hostname,
+ location: selectorResult.location
+ )
+
+ _ = try? await appMessageHandler.handleAppMessage(
+ TunnelProviderMessage.reconnectTunnel(.preSelected(selectedRelay)).encode()
+ )
+
+ await fulfillment(of: [reconnectExpectation], timeout: 1)
+ }
+}
+
+extension AppMessageHandlerTests {
+ func createAppMessageHandler(
+ actor: PacketTunnelActorProtocol = PacketTunnelActorStub(),
+ urlRequestProxy: URLRequestProxyProtocol = URLRequestProxyStub()
+ ) -> AppMessageHandler {
+ return AppMessageHandler(
+ packetTunnelActor: actor,
+ urlRequestProxy: urlRequestProxy
+ )
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift b/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift
index 8031ea888c..8bde84f781 100644
--- a/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift
@@ -22,18 +22,22 @@ class DefaultPathObserverFake: DefaultPathObserverProtocol {
private var innerPath: NetworkPath = NetworkPathStub()
private var stateLock = NSLock()
-
private var defaultPathHandler: ((NetworkPath) -> Void)?
+ public var onStart: (() -> Void)?
+ public var onStop: (() -> Void)?
+
func start(_ body: @escaping (NetworkPath) -> Void) {
stateLock.withLock {
defaultPathHandler = body
+ onStart?()
}
}
func stop() {
stateLock.withLock {
defaultPathHandler = nil
+ onStop?()
}
}
diff --git a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
index 418f691f46..3e01bec31c 100644
--- a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
@@ -13,7 +13,7 @@ extension PacketTunnelActorTimings {
static var timingsForTests: PacketTunnelActorTimings {
return PacketTunnelActorTimings(
bootRecoveryPeriodicity: .milliseconds(10),
- wgKeyPropagationDelay: .milliseconds(10)
+ wgKeyPropagationDelay: .zero
)
}
}
diff --git a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift
new file mode 100644
index 0000000000..80c22971df
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift
@@ -0,0 +1,33 @@
+//
+// PacketTunnelActorStub.swift
+// PacketTunnelCoreTests
+//
+// Created by Jon Petersson on 2023-10-11.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import PacketTunnelCore
+import XCTest
+
+struct PacketTunnelActorStub: PacketTunnelActorProtocol {
+ let innerState: State = .disconnected
+ var stateExpectation: XCTestExpectation?
+ var reconnectExpectation: XCTestExpectation?
+ var keyRotationExpectation: XCTestExpectation?
+
+ var state: State {
+ get async {
+ stateExpectation?.fulfill()
+ return innerState
+ }
+ }
+
+ func reconnect(to nextRelay: NextRelay) {
+ reconnectExpectation?.fulfill()
+ }
+
+ func notifyKeyRotation(date: Date?) {
+ keyRotationExpectation?.fulfill()
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/Mocks/URLRequestProxyStub.swift b/ios/PacketTunnelCoreTests/Mocks/URLRequestProxyStub.swift
new file mode 100644
index 0000000000..a8f21538db
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/Mocks/URLRequestProxyStub.swift
@@ -0,0 +1,32 @@
+//
+// URLRequestProxyStub.swift
+// PacketTunnelCoreTests
+//
+// Created by Jon Petersson on 2023-10-11.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import PacketTunnelCore
+import XCTest
+
+struct URLRequestProxyStub: URLRequestProxyProtocol {
+ var sendRequestExpectation: XCTestExpectation?
+ var cancelRequestExpectation: XCTestExpectation?
+
+ func sendRequest(
+ _ proxyRequest: PacketTunnelCore.ProxyURLRequest,
+ completionHandler: @escaping @Sendable (PacketTunnelCore.ProxyURLResponse) -> Void
+ ) {
+ sendRequestExpectation?.fulfill()
+ }
+
+ func sendRequest(_ proxyRequest: PacketTunnelCore.ProxyURLRequest) async -> PacketTunnelCore.ProxyURLResponse {
+ sendRequestExpectation?.fulfill()
+ return ProxyURLResponse(data: nil, response: nil, error: nil)
+ }
+
+ func cancelRequest(identifier: UUID) {
+ cancelRequestExpectation?.fulfill()
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
new file mode 100644
index 0000000000..3bdf0b062f
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
@@ -0,0 +1,436 @@
+//
+// PacketTunnelActorTests.swift
+// PacketTunnelCoreTests
+//
+// Created by pronebird on 05/09/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+import MullvadSettings
+import MullvadTypes
+import Network
+@testable import PacketTunnelCore
+@testable import RelaySelector
+import struct WireGuardKitTypes.IPAddressRange
+import class WireGuardKitTypes.PrivateKey
+import XCTest
+
+final class PacketTunnelActorTests: XCTestCase {
+ private var stateSink: Combine.Cancellable?
+ private let launchOptions = StartOptions(launchSource: .app)
+
+ override func tearDown() async throws {
+ stateSink?.cancel()
+ }
+
+ /**
+ Test a happy path start sequence.
+
+ As actor should transition through the following states: .initial → .connecting → .connected
+ */
+ func testStartGoesToConnectedInSequence() async throws {
+ let actor = PacketTunnelActor.mock()
+
+ // As actor starts it should transition through the following states based on simulation:
+ // .initial → .connecting → .connected
+ let initialStateExpectation = expectation(description: "Expect initial state")
+ let connectingExpectation = expectation(description: "Expect connecting state")
+ let connectedStateExpectation = expectation(description: "Expect connected state")
+
+ let allExpectations = [initialStateExpectation, connectingExpectation, connectedStateExpectation]
+
+ stateSink = await actor.$state
+ .receive(on: DispatchQueue.main)
+ .sink { newState in
+ switch newState {
+ case .initial:
+ initialStateExpectation.fulfill()
+ case .connecting:
+ connectingExpectation.fulfill()
+ case .connected:
+ connectedStateExpectation.fulfill()
+ default:
+ break
+ }
+ }
+
+ actor.start(options: launchOptions)
+
+ await fulfillment(of: allExpectations, timeout: 1, enforceOrder: true)
+ }
+
+ func testStartIgnoresSubsequentStarts() async throws {
+ let actor = PacketTunnelActor.mock()
+
+ // As actor starts it should transition through the following states based on simulation:
+ // .initial → .connecting → .connected
+ let initialStateExpectation = expectation(description: "Expect initial state")
+ let connectingExpectation = expectation(description: "Expect connecting state")
+ let connectedStateExpectation = expectation(description: "Expect connected state")
+
+ let allExpectations = [initialStateExpectation, connectingExpectation, connectedStateExpectation]
+
+ stateSink = await actor.$state
+ .receive(on: DispatchQueue.main)
+ .sink { newState in
+ switch newState {
+ case .initial:
+ initialStateExpectation.fulfill()
+ case .connecting:
+ connectingExpectation.fulfill()
+ case .connected:
+ connectedStateExpectation.fulfill()
+ default:
+ break
+ }
+ }
+
+ actor.start(options: launchOptions)
+ actor.start(options: launchOptions)
+
+ await fulfillment(of: allExpectations, timeout: 1, enforceOrder: true)
+ }
+
+ /**
+ Each subsequent connection attempt should produce a single change to `state` containing the incremented attempt counter and new relay.
+ .connecting (attempt: 0) → .connecting (attempt: 1) → .connecting (attempt: 2) → ...
+ */
+ func testConnectionAttemptTransition() async throws {
+ let tunnelMonitor = TunnelMonitorStub { _, _ in }
+ let actor = PacketTunnelActor.mock(tunnelMonitor: tunnelMonitor)
+ let connectingStateExpectation = expectation(description: "Expect connecting state")
+ connectingStateExpectation.expectedFulfillmentCount = 5
+ var nextAttemptCount: UInt = 0
+ stateSink = await actor.$state
+ .receive(on: DispatchQueue.main)
+ .sink { newState in
+ switch newState {
+ case .initial:
+ break
+ case let .connecting(connState):
+ XCTAssertEqual(connState.connectionAttemptCount, nextAttemptCount)
+ nextAttemptCount += 1
+ connectingStateExpectation.fulfill()
+ if nextAttemptCount < connectingStateExpectation.expectedFulfillmentCount {
+ tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10))
+ }
+ default:
+ XCTFail("Received invalid state: \(newState.name).")
+ }
+ }
+
+ actor.start(options: StartOptions(launchSource: .app))
+ await fulfillment(of: [connectingStateExpectation], timeout: 1)
+ }
+
+ /**
+ Each subsequent re-connection attempt should produce a single change to `state` containing the incremented attempt counter and new relay.
+ .reconnecting (attempt: 0) → .reconnecting (attempt: 1) → .reconnecting (attempt: 2) → ...
+ */
+ func testReconnectionAttemptTransition() async throws {
+ let tunnelMonitor = TunnelMonitorStub { _, _ in }
+ let actor = PacketTunnelActor.mock(tunnelMonitor: tunnelMonitor)
+ let connectingStateExpectation = expectation(description: "Expect connecting state")
+ let connectedStateExpectation = expectation(description: "Expect connected state")
+ let reconnectingStateExpectation = expectation(description: "Expect reconnecting state")
+ reconnectingStateExpectation.expectedFulfillmentCount = 5
+ var nextAttemptCount: UInt = 0
+ stateSink = await actor.$state
+ .receive(on: DispatchQueue.main)
+ .sink { newState in
+ switch newState {
+ case .initial:
+ break
+ case .connecting:
+ connectingStateExpectation.fulfill()
+ tunnelMonitor.dispatch(.connectionEstablished, after: .milliseconds(10))
+ case .connected:
+ connectedStateExpectation.fulfill()
+ tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10))
+ case let .reconnecting(connState):
+ XCTAssertEqual(connState.connectionAttemptCount, nextAttemptCount)
+ nextAttemptCount += 1
+ reconnectingStateExpectation.fulfill()
+ if nextAttemptCount < reconnectingStateExpectation.expectedFulfillmentCount {
+ tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10))
+ }
+ default:
+ XCTFail("Received invalid state: \(newState.name).")
+ }
+ }
+
+ actor.start(options: StartOptions(launchSource: .app))
+ await fulfillment(
+ of: [connectingStateExpectation, connectedStateExpectation, reconnectingStateExpectation],
+ timeout: 1,
+ enforceOrder: true
+ )
+ }
+
+ /**
+ Test start sequence when reading settings yields an error indicating that device is locked.
+ This is common when network extenesion starts on boot with iOS.
+
+ 1. The first attempt to read settings yields an error indicating that device is locked.
+ 2. An actor should set up a task to reconnect the tunnel periodically.
+ 3. The issue goes away on the second attempt to read settings.
+ 4. An actor should transition through `.connecting` towards`.connected` state.
+ */
+ func testLockedDeviceErrorOnBoot() async throws {
+ let initialStateExpectation = expectation(description: "Expect initial state")
+ let errorStateExpectation = expectation(description: "Expect error state")
+ let connectingStateExpectation = expectation(description: "Expect connecting state")
+ let connectedStateExpectation = expectation(description: "Expect connected state")
+ let allExpectations = [
+ initialStateExpectation,
+ errorStateExpectation,
+ connectingStateExpectation,
+ connectedStateExpectation,
+ ]
+
+ let blockedStateMapper = BlockedStateErrorMapperStub { error in
+ if let error = error as? POSIXError, error.code == .EPERM {
+ return .deviceLocked
+ } else {
+ return .unknown
+ }
+ }
+
+ var isFirstReadAttempt = true
+ let settingsReader = SettingsReaderStub {
+ if isFirstReadAttempt {
+ isFirstReadAttempt = false
+ throw POSIXError(.EPERM)
+ } else {
+ return Settings(
+ privateKey: PrivateKey(),
+ interfaceAddresses: [IPAddressRange(from: "127.0.0.1/32")!],
+ relayConstraints: RelayConstraints(),
+ dnsServers: .gateway
+ )
+ }
+ }
+
+ let actor = PacketTunnelActor.mock(blockedStateErrorMapper: blockedStateMapper, settingsReader: settingsReader)
+
+ stateSink = await actor.$state
+ .receive(on: DispatchQueue.main)
+ .sink { newState in
+ switch newState {
+ case .initial:
+ initialStateExpectation.fulfill()
+ case .error:
+ errorStateExpectation.fulfill()
+ case .connecting:
+ connectingStateExpectation.fulfill()
+ case .connected:
+ connectedStateExpectation.fulfill()
+ default:
+ break
+ }
+ }
+
+ actor.start(options: launchOptions)
+
+ await fulfillment(of: allExpectations, timeout: 1, enforceOrder: true)
+ }
+
+ func testStopGoesToDisconnected() async throws {
+ let actor = PacketTunnelActor.mock()
+ let disconnectedStateExpectation = expectation(description: "Expect disconnected state")
+ let connectedStateExpectation = expectation(description: "Expect connected state")
+
+ let expression: (State) -> Bool = { if case .connected = $0 { true } else { false } }
+
+ await expect(expression, on: actor) {
+ connectedStateExpectation.fulfill()
+ }
+
+ // Wait for the connected state to happen so it doesn't get coalesced immediately after the call to `actor.stop`
+ actor.start(options: launchOptions)
+ await fulfillment(of: [connectedStateExpectation], timeout: 1)
+
+ await expect(.disconnected, on: actor) {
+ disconnectedStateExpectation.fulfill()
+ }
+ actor.stop()
+ await fulfillment(of: [disconnectedStateExpectation], timeout: 1)
+ }
+
+ func testStopIsNoopBeforeStart() async throws {
+ let actor = PacketTunnelActor.mock()
+
+ let disconnectedExpectation = expectation(description: "Disconnected state")
+ disconnectedExpectation.isInverted = true
+
+ await expect(.disconnected, on: actor) {
+ disconnectedExpectation.fulfill()
+ }
+
+ actor.stop()
+ actor.stop()
+ actor.stop()
+
+ await fulfillment(of: [disconnectedExpectation], timeout: Duration.milliseconds(100).timeInterval)
+ }
+
+ func testStopCancelsDefaultPathObserver() async throws {
+ let pathObserver = DefaultPathObserverFake()
+ let actor = PacketTunnelActor.mock(defaultPathObserver: pathObserver)
+
+ let connectedStateExpectation = expectation(description: "Connected state")
+ let didStopObserverExpectation = expectation(description: "Did stop path observer")
+ didStopObserverExpectation.expectedFulfillmentCount = 2
+ pathObserver.onStop = { didStopObserverExpectation.fulfill() }
+
+ let expression: (State) -> Bool = { if case .connected = $0 { true } else { false } }
+
+ await expect(expression, on: actor) {
+ connectedStateExpectation.fulfill()
+ }
+
+ actor.start(options: launchOptions)
+ await fulfillment(of: [connectedStateExpectation], timeout: 1)
+
+ let disconnectedStateExpectation = expectation(description: "Disconnected state")
+
+ await expect(.disconnected, on: actor) {
+ disconnectedStateExpectation.fulfill()
+ }
+ actor.stop()
+ await fulfillment(of: [disconnectedStateExpectation, didStopObserverExpectation], timeout: 1)
+ }
+
+ func testSetErrorStateGetsCancelledWhenStopping() async throws {
+ let actor = PacketTunnelActor.mock()
+ let connectingStateExpectation = expectation(description: "Connecting state")
+ let disconnectedStateExpectation = expectation(description: "Disconnected state")
+ let errorStateExpectation = expectation(description: "Should not enter error state")
+ errorStateExpectation.isInverted = true
+
+ stateSink = await actor.$state
+ .receive(on: DispatchQueue.main)
+ .sink { newState in
+ switch newState {
+ case .connecting:
+ actor.setErrorState(reason: .readSettings)
+ connectingStateExpectation.fulfill()
+ case .error:
+ errorStateExpectation.fulfill()
+ case .disconnected:
+ disconnectedStateExpectation.fulfill()
+ default:
+ break
+ }
+ }
+
+ actor.start(options: launchOptions)
+ actor.stop()
+
+ await fulfillment(of: [connectingStateExpectation, disconnectedStateExpectation], timeout: 1)
+ await fulfillment(of: [errorStateExpectation], timeout: Duration.milliseconds(100).timeInterval)
+ }
+
+ func testReconnectIsNoopBeforeConnecting() async throws {
+ let actor = PacketTunnelActor.mock()
+ let reconnectingStateExpectation = expectation(description: "Expect initial state")
+ reconnectingStateExpectation.isInverted = true
+
+ let expression: (State) -> Bool = { if case .reconnecting = $0 { true } else { false } }
+
+ await expect(expression, on: actor) {
+ reconnectingStateExpectation.fulfill()
+ }
+
+ actor.reconnect(to: .random)
+
+ await fulfillment(
+ of: [reconnectingStateExpectation],
+ timeout: Duration.milliseconds(100).timeInterval
+ )
+ }
+
+ func testCannotReconnectAfterStopping() async throws {
+ let actor = PacketTunnelActor.mock()
+
+ let disconnectedStateExpectation = expectation(description: "Expect disconnected state")
+
+ await expect(.disconnected, on: actor) { disconnectedStateExpectation.fulfill() }
+
+ actor.start(options: launchOptions)
+ actor.stop()
+
+ await fulfillment(of: [disconnectedStateExpectation], timeout: 1)
+
+ let reconnectingStateExpectation = expectation(description: "Expect initial state")
+ reconnectingStateExpectation.isInverted = true
+ let expression: (State) -> Bool = { if case .reconnecting = $0 { true } else { false } }
+
+ await expect(expression, on: actor) { reconnectingStateExpectation.fulfill() }
+
+ actor.reconnect(to: .random)
+ await fulfillment(
+ of: [reconnectingStateExpectation],
+ timeout: Duration.milliseconds(100).timeInterval
+ )
+ }
+
+ func testReconnectionStopsTunnelMonitor() async throws {
+ let stopMonitorExpectation = expectation(description: "Tunnel monitor stop")
+
+ let tunnelMonitor = TunnelMonitorStub { command, dispatcher in
+ switch command {
+ case .start:
+ dispatcher.send(.connectionEstablished, after: .milliseconds(10))
+ case .stop:
+ stopMonitorExpectation.fulfill()
+ }
+ }
+ let actor = PacketTunnelActor.mock(tunnelMonitor: tunnelMonitor)
+ let connectedExpectation = expectation(description: "Expect connected state")
+
+ let expression: (State) -> Bool = { if case .connected = $0 { return true } else { return false } }
+ await expect(expression, on: actor) {
+ connectedExpectation.fulfill()
+ }
+ actor.start(options: launchOptions)
+ await fulfillment(of: [connectedExpectation], timeout: 1)
+
+ // Cancel the state sink to avoid overfulfilling the connected expectation
+ stateSink?.cancel()
+
+ actor.reconnect(to: .random)
+ await fulfillment(of: [stopMonitorExpectation], timeout: 1)
+ }
+}
+
+extension PacketTunnelActorTests {
+ func expect(_ state: State, on actor: PacketTunnelActor, _ action: @escaping () -> Void) async {
+ stateSink = await actor.$state.receive(on: DispatchQueue.main).sink { newState in
+ if state == newState {
+ action()
+ }
+ }
+ }
+
+ func expect(
+ _ expression: @escaping (State) -> Bool,
+ on actor: PacketTunnelActor,
+ _ action: @escaping () -> Void
+ ) async {
+ stateSink = await actor.$state.receive(on: DispatchQueue.main).sink { newState in
+ if expression(newState) {
+ action()
+ }
+ }
+ }
+}
+
+extension State: Equatable {
+ public static func == (lhs: State, rhs: State) -> Bool {
+ lhs.name == rhs.name
+ }
+}
+
+// swiftlint:disable:this file_length
diff --git a/ios/RelayCache/RelayCache.swift b/ios/RelayCache/RelayCache.swift
index abce823786..ba7ed31cf1 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<CachedRelays>
/// Designated initializer