summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authormojganii <mojgan.jelodar@codic.se>2024-08-13 11:46:43 +0200
committermojganii <mojgan.jelodar@codic.se>2024-08-13 11:47:46 +0200
commit85acb9e5155b0db6fb6db65d99007424da312873 (patch)
treeb562e5526e756c5d3a59e6393690b77c3d09f194
parent3562753de20f70b4ac65a157fee367ec9891323f (diff)
downloadmullvadvpn-85acb9e5155b0db6fb6db65d99007424da312873.tar.xz
mullvadvpn-85acb9e5155b0db6fb6db65d99007424da312873.zip
Fix getting stuck in blocked state after reconnecting
-rw-r--r--ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift (renamed from ios/MullvadRESTTests/AccessMethodRepositoryStub.swift)16
-rw-r--r--ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift17
-rw-r--r--ios/MullvadRESTTests/TransportStrategyTests.swift1
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj18
-rw-r--r--ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift2
-rw-r--r--ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift2
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift3
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift4
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift18
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift10
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelInteractor.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift34
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelStore.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift1
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift4
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift6
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift10
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift158
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift25
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/UIApplication+Stubs.swift2
-rw-r--r--ios/MullvadVPNUITests/ConnectivityTests.swift1
-rw-r--r--ios/Operations/BackgroundObserver.swift10
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift1
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState.swift5
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift3
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift15
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift2
30 files changed, 271 insertions, 121 deletions
diff --git a/ios/MullvadRESTTests/AccessMethodRepositoryStub.swift b/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
index a2640df952..c21941f4af 100644
--- a/ios/MullvadRESTTests/AccessMethodRepositoryStub.swift
+++ b/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
@@ -1,5 +1,5 @@
//
-// AccessMethodRepositoryStub.swift
+// AccessMethodRepository+Stub.swift
// MullvadRESTTests
//
// Created by Mojgan on 2024-01-02.
@@ -9,27 +9,27 @@
import Combine
import MullvadSettings
-struct AccessMethodRepositoryStub: AccessMethodRepositoryDataSource {
- var directAccess: PersistentAccessMethod
+public struct AccessMethodRepositoryStub: AccessMethodRepositoryDataSource {
+ public var directAccess: PersistentAccessMethod
- var accessMethodsPublisher: AnyPublisher<[PersistentAccessMethod], Never> {
+ public var accessMethodsPublisher: AnyPublisher<[PersistentAccessMethod], Never> {
passthroughSubject.eraseToAnyPublisher()
}
let passthroughSubject: CurrentValueSubject<[PersistentAccessMethod], Never> = CurrentValueSubject([])
- init(accessMethods: [PersistentAccessMethod]) {
+ public init(accessMethods: [PersistentAccessMethod]) {
directAccess = accessMethods.first(where: { $0.kind == .direct })!
passthroughSubject.send(accessMethods)
}
- func fetchAll() -> [PersistentAccessMethod] {
+ public func fetchAll() -> [PersistentAccessMethod] {
passthroughSubject.value
}
- func saveLastReachable(_ method: PersistentAccessMethod) {}
+ public func saveLastReachable(_ method: PersistentAccessMethod) {}
- func fetchLastReachable() -> PersistentAccessMethod {
+ public func fetchLastReachable() -> PersistentAccessMethod {
directAccess
}
}
diff --git a/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
index f404aff1e2..4445aa8cc8 100644
--- a/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
+++ b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
@@ -11,14 +11,18 @@ import MullvadTypes
import WireGuardKitTypes
/// Relay selector stub that accepts a block that can be used to provide custom implementation.
-public struct RelaySelectorStub: RelaySelectorProtocol {
- let block: (RelayConstraints, UInt) throws -> SelectedRelays
+public final class RelaySelectorStub: RelaySelectorProtocol {
+ var selectedRelaysResult: (RelayConstraints, UInt) throws -> SelectedRelays
+
+ init(selectedRelaysResult: @escaping (RelayConstraints, UInt) throws -> SelectedRelays) {
+ self.selectedRelaysResult = selectedRelaysResult
+ }
public func selectRelays(
with constraints: RelayConstraints,
connectionAttemptCount: UInt
) throws -> SelectedRelays {
- return try block(constraints, connectionAttemptCount)
+ return try selectedRelaysResult(constraints, connectionAttemptCount)
}
}
@@ -53,4 +57,11 @@ extension RelaySelectorStub {
)
}
}
+
+ /// Returns a relay selector that cannot satisfy constraints .
+ public static func unsatisfied() -> RelaySelectorStub {
+ return RelaySelectorStub { _, _ in
+ throw NoRelaysSatisfyingConstraintsError()
+ }
+ }
}
diff --git a/ios/MullvadRESTTests/TransportStrategyTests.swift b/ios/MullvadRESTTests/TransportStrategyTests.swift
index 3174686ff6..b1875c87e5 100644
--- a/ios/MullvadRESTTests/TransportStrategyTests.swift
+++ b/ios/MullvadRESTTests/TransportStrategyTests.swift
@@ -6,6 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
+@testable import MullvadMockData
@testable import MullvadREST
@testable import MullvadSettings
@testable import MullvadTypes
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 8282c9095d..d9ba33a697 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -829,14 +829,12 @@
A9D9A4CD2C36D54E004088DD /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585A02EA2A4B285800C6CAFF /* UDPConnection.swift */; };
A9D9A4CE2C36D54E004088DD /* TunnelObfuscationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58695A9F2A4ADA9200328DB3 /* TunnelObfuscationTests.swift */; };
A9D9A4CF2C36D54E004088DD /* TCPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585A02EC2A4B28F300C6CAFF /* TCPConnection.swift */; };
- A9D9A4D02C36DAFD004088DD /* PostQuantumKeyExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */; };
A9D9A4D22C36DBAF004088DD /* MullvadPostQuantum+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C308392C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift */; };
A9D9A4D42C36E1EA004088DD /* mullvad_rust_runtime.h in Headers */ = {isa = PBXBuildFile; fileRef = A9D9A4D32C36E1EA004088DD /* mullvad_rust_runtime.h */; settings = {ATTRIBUTES = (Private, ); }; };
A9DF789B2B7D1DF10094E4AD /* mullvad-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 01EF6F2D2B6A51B100125696 /* mullvad-api.h */; settings = {ATTRIBUTES = (Private, ); }; };
A9DF789D2B7D1E8B0094E4AD /* LoggedInWithTimeUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */; };
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 */; };
E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; };
@@ -846,7 +844,6 @@
F006CCFC2B99CC8400C6C2AC /* EditLocationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F006CCFB2B99CC8400C6C2AC /* EditLocationsCoordinator.swift */; };
F0077EEE2C52844800DAB2AA /* KeyExchangingResultStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */; };
F01528BB2BFF3FEE00B01D00 /* ShadowsocksRelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01528BA2BFF3FEE00B01D00 /* ShadowsocksRelaySelector.swift */; };
- F0164EBA2B4456D30020268D /* AccessMethodRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EB92B4456D30020268D /* AccessMethodRepositoryStub.swift */; };
F0164EBC2B482E430020268D /* AppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EBB2B482E430020268D /* AppStorage.swift */; };
F0164EBE2B4BFF940020268D /* ShadowsocksLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EBD2B4BFF940020268D /* ShadowsocksLoader.swift */; };
F0164EC32B4C49D30020268D /* ShadowsocksLoaderStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EC22B4C49D30020268D /* ShadowsocksLoaderStub.swift */; };
@@ -887,6 +884,7 @@
F062B94D2C16E09700B6D47A /* TunnelSettingsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */; };
F072D3CF2C07122400906F64 /* MultihopUpdaterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3CE2C07122400906F64 /* MultihopUpdaterTests.swift */; };
F072D3D22C071AD100906F64 /* ShadowsocksLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */; };
+ F073FCB32C6617D70062EA1D /* TunnelStore+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F073FCB22C6617D70062EA1D /* TunnelStore+Stubs.swift */; };
F07751552C50F149006E6A12 /* PostQuantumKeyExchangeActorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C4C9BF2C495E7500A79006 /* PostQuantumKeyExchangeActorStub.swift */; };
F07751572C50F149006E6A12 /* PostQuantumKeyExchangingPipelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F053F4B92C4A94D300FBD937 /* PostQuantumKeyExchangingPipelineTests.swift */; };
F07751582C50F149006E6A12 /* MultiHopPostQuantumKeyExchangingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C4C9BD2C49477B00A79006 /* MultiHopPostQuantumKeyExchangingTests.swift */; };
@@ -895,6 +893,7 @@
F07BF2622A26279100042943 /* RedeemVoucherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */; };
F07C9D952B220C77006F1C5E /* libmullvad_ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 01F1FF1D29F0627D007083C3 /* libmullvad_ios.a */; };
F07CFF2029F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */; };
+ F07F63CE2C63E5790027A351 /* AccessMethodRepository+Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EB92B4456D30020268D /* AccessMethodRepository+Stub.swift */; };
F08827872B318C840020A383 /* ShadowsocksCipherOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DFF7D92B02862E00F864E0 /* ShadowsocksCipherOptions.swift */; };
F08827882B318F960020A383 /* PersistentAccessMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C0D962B04E0AC00E7CDD7 /* PersistentAccessMethod.swift */; };
F08827892B3192110020A383 /* AccessMethodRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EF875A2B16385400C098B2 /* AccessMethodRepositoryProtocol.swift */; };
@@ -2041,7 +2040,6 @@
A9D9A4D32C36E1EA004088DD /* mullvad_rust_runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mullvad_rust_runtime.h; path = include/mullvad_rust_runtime.h; 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>"; };
A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyNegotiator.swift; sourceTree = "<group>"; };
@@ -2053,7 +2051,7 @@
E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivityView.swift; sourceTree = "<group>"; };
F006CCFB2B99CC8400C6C2AC /* EditLocationsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLocationsCoordinator.swift; sourceTree = "<group>"; };
F01528BA2BFF3FEE00B01D00 /* ShadowsocksRelaySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksRelaySelector.swift; sourceTree = "<group>"; };
- F0164EB92B4456D30020268D /* AccessMethodRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodRepositoryStub.swift; sourceTree = "<group>"; };
+ F0164EB92B4456D30020268D /* AccessMethodRepository+Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessMethodRepository+Stub.swift"; sourceTree = "<group>"; };
F0164EBB2B482E430020268D /* AppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorage.swift; sourceTree = "<group>"; };
F0164EBD2B4BFF940020268D /* ShadowsocksLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoader.swift; sourceTree = "<group>"; };
F0164EC22B4C49D30020268D /* ShadowsocksLoaderStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderStub.swift; sourceTree = "<group>"; };
@@ -2092,6 +2090,7 @@
F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManagerTests.swift; sourceTree = "<group>"; };
F072D3CE2C07122400906F64 /* MultihopUpdaterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopUpdaterTests.swift; sourceTree = "<group>"; };
F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderTests.swift; sourceTree = "<group>"; };
+ F073FCB22C6617D70062EA1D /* TunnelStore+Stubs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TunnelStore+Stubs.swift"; sourceTree = "<group>"; };
F07B53562C53B5270024F547 /* LocalNetworkIPs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkIPs.swift; sourceTree = "<group>"; };
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextFormatterTests.swift; sourceTree = "<group>"; };
F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherOperation.swift; sourceTree = "<group>"; };
@@ -2513,7 +2512,7 @@
A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */,
F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */,
44BB5F992BE529FE002520EB /* TunnelStateTests.swift */,
- A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */,
+ F073FCB22C6617D70062EA1D /* TunnelStore+Stubs.swift */,
A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */,
58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */,
);
@@ -3710,7 +3709,6 @@
58FBFBE7291622580020E046 /* MullvadRESTTests */ = {
isa = PBXGroup;
children = (
- F0164EB92B4456D30020268D /* AccessMethodRepositoryStub.swift */,
58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */,
A932D9F22B5EB61100999395 /* HeadRequestTests.swift */,
58BDEB9E2A98F6B400F578F2 /* Mocks */,
@@ -4091,6 +4089,7 @@
F0ACE3172BE4E487006D5333 /* MullvadREST */ = {
isa = PBXGroup;
children = (
+ F0164EB92B4456D30020268D /* AccessMethodRepository+Stub.swift */,
A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */,
A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */,
A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */,
@@ -5271,7 +5270,6 @@
A9A5FA072ACB05160083449F /* SimulatorVPNConnection.swift in Sources */,
7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */,
A9A5FA082ACB05160083449F /* StorePaymentBlockObserver.swift in Sources */,
- A9E0317C2ACBFC7E0095D843 /* TunnelStore+Stubs.swift in Sources */,
7A516C3C2B712F0B00BBD33D /* IPOverrideWrapperTests.swift in Sources */,
A9A5FA092ACB05160083449F /* SendStoreReceiptOperation.swift in Sources */,
A9A5FA0A2ACB05160083449F /* StorePaymentEvent.swift in Sources */,
@@ -5343,6 +5341,7 @@
A9A5FA312ACB05160083449F /* MockFileCache.swift in Sources */,
A9A5FA322ACB05160083449F /* RelayCacheTests.swift in Sources */,
A9A5FA332ACB05160083449F /* RelaySelectorTests.swift in Sources */,
+ F073FCB32C6617D70062EA1D /* TunnelStore+Stubs.swift in Sources */,
58DFF7D32B02570000F864E0 /* MarkdownStylingOptions.swift in Sources */,
A9A5FA342ACB05160083449F /* StringTests.swift in Sources */,
7A52F96C2C17450C00B133B9 /* RelaySelectorWrapperTests.swift in Sources */,
@@ -5972,7 +5971,6 @@
buildActionMask = 2147483647;
files = (
58B465702A98C53300467203 /* RequestExecutorTests.swift in Sources */,
- F0164EBA2B4456D30020268D /* AccessMethodRepositoryStub.swift in Sources */,
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */,
58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */,
F0164EC32B4C49D30020268D /* ShadowsocksLoaderStub.swift in Sources */,
@@ -6070,7 +6068,6 @@
files = (
A9D9A4B12C36D10E004088DD /* ShadowSocksProxy.swift in Sources */,
A9D9A4BB2C36D397004088DD /* PostQuantumKeyNegotiator.swift in Sources */,
- A9D9A4D02C36DAFD004088DD /* PostQuantumKeyExchangeActor.swift in Sources */,
A9D9A4B22C36D12D004088DD /* UDPOverTCPObfuscator.swift in Sources */,
A9173C322C36CCDD00F6A08C /* PacketTunnelProvider+TCPConnection.swift in Sources */,
F05919802C45515200C301F3 /* PostQuantumKeyExchangeActor.swift in Sources */,
@@ -6096,6 +6093,7 @@
buildActionMask = 2147483647;
files = (
F0ACE31D2BE4E4F2006D5333 /* DevicesProxy+Stubs.swift in Sources */,
+ F07F63CE2C63E5790027A351 /* AccessMethodRepository+Stub.swift in Sources */,
F0ACE31E2BE4E4F2006D5333 /* AccountsProxy+Stubs.swift in Sources */,
F0ACE3202BE4E4F2006D5333 /* AccessTokenManager+Stubs.swift in Sources */,
F0ACE32C2BE4E77E006D5333 /* DeviceMock.swift in Sources */,
diff --git a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
index f36d9b6df6..048d63d0b0 100644
--- a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
+++ b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
@@ -103,7 +103,7 @@ final class AddressCacheTracker {
operation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Update endpoints",
cancelUponExpiration: true
)
diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
index 33b0b6cacf..8893436cb6 100644
--- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
+++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
@@ -120,7 +120,7 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
operation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Update relays",
cancelUponExpiration: true
)
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
index 193f79b987..2bd8898e84 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
@@ -62,6 +62,9 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
do {
setInternalStateConnected(with: try selectedRelays ?? pickRelays())
completionHandler(nil)
+ } catch let error where error is NoRelaysSatisfyingConstraintsError {
+ observedState = .error(ObservedBlockedState(reason: .noRelaysSatisfyingConstraints))
+ completionHandler(error)
} catch {
providerLogger.error(
error: error,
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift
index 6b235d285c..5ca0b16f1e 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift
@@ -9,6 +9,7 @@
#if targetEnvironment(simulator)
import Foundation
+import MullvadREST
import NetworkExtension
class SimulatorVPNConnection: NSObject, VPNConnectionProtocol {
@@ -94,6 +95,9 @@ class SimulatorVPNConnection: NSObject, VPNConnectionProtocol {
if error == nil {
self.status = .connected
self.connectedDate = Date()
+ } else if error is NoRelaysSatisfyingConstraintsError {
+ self.reasserting = true
+ self.connectedDate = nil
} else {
self.status = .disconnected
self.connectedDate = nil
diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
index 4b130109bc..9db60518d7 100644
--- a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
+++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift
@@ -232,7 +232,7 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
}
accountOperation.addObserver(BackgroundObserver(
- application: backgroundTaskProvider,
+ backgroundTaskProvider: backgroundTaskProvider,
name: "Validate account number",
cancelUponExpiration: false
))
@@ -267,7 +267,7 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver {
operation.addObserver(
BackgroundObserver(
- application: backgroundTaskProvider,
+ backgroundTaskProvider: backgroundTaskProvider,
name: "Send AppStore receipt",
cancelUponExpiration: true
)
diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
index b605b85b47..a93e33b0b1 100644
--- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
@@ -22,7 +22,7 @@ class MapConnectionStatusOperation: AsyncOperation {
private let logger = Logger(label: "TunnelManager.MapConnectionStatusOperation")
- init(
+ required init(
queue: DispatchQueue,
interactor: TunnelInteractor,
connectionStatus: NEVPNStatus,
diff --git a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
index cb9afb10dd..04c574640c 100644
--- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
@@ -23,7 +23,7 @@ private let defaultTimeout: Duration = .seconds(5)
final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output> {
typealias DecoderHandler = (Data?) throws -> Output
- private let application: UIApplication
+ private let backgroundTaskProvider: BackgroundTaskProvider
private let tunnel: any TunnelProtocol
private let message: TunnelProviderMessage
private let timeout: Duration
@@ -38,14 +38,14 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
init(
dispatchQueue: DispatchQueue,
- application: UIApplication,
+ backgroundTaskProvider: BackgroundTaskProvider,
tunnel: any TunnelProtocol,
message: TunnelProviderMessage,
timeout: Duration? = nil,
decoderHandler: @escaping DecoderHandler,
completionHandler: CompletionHandler?
) {
- self.application = application
+ self.backgroundTaskProvider = backgroundTaskProvider
self.tunnel = tunnel
self.message = message
self.timeout = timeout ?? defaultTimeout
@@ -60,7 +60,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: backgroundTaskProvider,
name: "Send tunnel provider message: \(message)",
cancelUponExpiration: true
)
@@ -193,7 +193,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
return
}
- guard application.backgroundTimeRemaining > timeout else {
+ guard backgroundTaskProvider.backgroundTimeRemaining > timeout else {
finish(result: .failure(SendTunnelProviderMessageError.notEnoughBackgroundTime))
return
}
@@ -218,7 +218,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
extension SendTunnelProviderMessageOperation where Output: Codable {
convenience init(
dispatchQueue: DispatchQueue,
- application: UIApplication,
+ backgroundTaskProvider: BackgroundTaskProvider,
tunnel: any TunnelProtocol,
message: TunnelProviderMessage,
timeout: Duration? = nil,
@@ -226,7 +226,7 @@ extension SendTunnelProviderMessageOperation where Output: Codable {
) {
self.init(
dispatchQueue: dispatchQueue,
- application: application,
+ backgroundTaskProvider: backgroundTaskProvider,
tunnel: tunnel,
message: message,
timeout: timeout,
@@ -245,7 +245,7 @@ extension SendTunnelProviderMessageOperation where Output: Codable {
extension SendTunnelProviderMessageOperation where Output == Void {
convenience init(
dispatchQueue: DispatchQueue,
- application: UIApplication,
+ backgroundTaskProvider: BackgroundTaskProvider,
tunnel: any TunnelProtocol,
message: TunnelProviderMessage,
timeout: Duration? = nil,
@@ -253,7 +253,7 @@ extension SendTunnelProviderMessageOperation where Output == Void {
) {
self.init(
dispatchQueue: dispatchQueue,
- application: application,
+ backgroundTaskProvider: backgroundTaskProvider,
tunnel: tunnel,
message: message,
timeout: timeout,
diff --git a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift
index 04a231c5c4..5f1d731d89 100644
--- a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift
+++ b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift
@@ -29,7 +29,7 @@ extension TunnelProtocol {
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
- application: .shared,
+ backgroundTaskProvider: backgroundTaskProvider,
tunnel: self,
message: .reconnectTunnel(nextRelays),
completionHandler: completionHandler
@@ -46,7 +46,7 @@ extension TunnelProtocol {
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
- application: .shared,
+ backgroundTaskProvider: backgroundTaskProvider,
tunnel: self,
message: .getTunnelStatus,
completionHandler: completionHandler
@@ -64,7 +64,7 @@ extension TunnelProtocol {
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
- application: .shared,
+ backgroundTaskProvider: backgroundTaskProvider,
tunnel: self,
message: .sendURLRequest(proxyRequest),
timeout: proxyRequestTimeout,
@@ -76,7 +76,7 @@ extension TunnelProtocol {
let cancelOperation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
- application: .shared,
+ backgroundTaskProvider: backgroundTaskProvider,
tunnel: self,
message: .cancelURLRequest(proxyRequest.id),
completionHandler: nil
@@ -96,7 +96,7 @@ extension TunnelProtocol {
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
- application: .shared,
+ backgroundTaskProvider: backgroundTaskProvider,
tunnel: self,
message: .privateKeyRotation,
completionHandler: completionHandler
diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift
index 088ff0c888..0b767e80d3 100644
--- a/ios/MullvadVPN/TunnelManager/Tunnel.swift
+++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift
@@ -26,8 +26,9 @@ protocol TunnelProtocol: AnyObject {
var status: NEVPNStatus { get }
var isOnDemandEnabled: Bool { get set }
var startDate: Date? { get }
+ var backgroundTaskProvider: BackgroundTaskProvider { get }
- init(tunnelProvider: TunnelManagerProtocol)
+ init(tunnelProvider: TunnelManagerProtocol, backgroundTaskProvider: BackgroundTaskProvider)
func addObserver(_ observer: any TunnelStatusObserver)
func removeObserver(_ observer: any TunnelStatusObserver)
@@ -52,6 +53,8 @@ final class Tunnel: TunnelProtocol, Equatable {
/// Unique identifier assigned to instance at the time of creation.
let identifier = UUID()
+ var backgroundTaskProvider: any BackgroundTaskProvider
+
#if DEBUG
/// System VPN configuration identifier.
/// This property performs a private call to obtain system configuration ID so it does not
@@ -114,8 +117,9 @@ final class Tunnel: TunnelProtocol, Equatable {
private var _startDate: Date?
internal let tunnelProvider: TunnelProviderManagerType
- init(tunnelProvider: TunnelProviderManagerType) {
+ init(tunnelProvider: TunnelProviderManagerType, backgroundTaskProvider: BackgroundTaskProvider) {
self.tunnelProvider = tunnelProvider
+ self.backgroundTaskProvider = backgroundTaskProvider
NotificationCenter.default.addObserver(
self, selector: #selector(handleVPNStatusChangeNotification(_:)),
diff --git a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
index 3b6735bd39..39587cb688 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
@@ -9,12 +9,14 @@
import Foundation
import MullvadREST
import MullvadSettings
+import MullvadTypes
import PacketTunnelCore
protocol TunnelInteractor {
// MARK: - Tunnel manipulation
var tunnel: (any TunnelProtocol)? { get }
+ var backgroundTaskProvider: any BackgroundTaskProvider { get }
func getPersistentTunnels() -> [any TunnelProtocol]
func createNewTunnel() -> any TunnelProtocol
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index bc3dff4fc7..fb03fa2f18 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -42,7 +42,7 @@ final class TunnelManager: StorePaymentObserver {
// MARK: - Internal variables
- private let application: BackgroundTaskProvider
+ let application: BackgroundTaskProvider
fileprivate let tunnelStore: any TunnelStoreProtocol
private let relayCacheTracker: RelayCacheTrackerProtocol
private let accountsProxy: RESTAccountHandling
@@ -204,7 +204,7 @@ final class TunnelManager: StorePaymentObserver {
loadTunnelOperation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Load tunnel configuration",
cancelUponExpiration: false
)
@@ -244,7 +244,7 @@ final class TunnelManager: StorePaymentObserver {
)
operation.addObserver(BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Start tunnel",
cancelUponExpiration: true
))
@@ -279,7 +279,7 @@ final class TunnelManager: StorePaymentObserver {
}
operation.addObserver(BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Stop tunnel",
cancelUponExpiration: true
))
@@ -315,7 +315,7 @@ final class TunnelManager: StorePaymentObserver {
operation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Reconnect tunnel",
cancelUponExpiration: true
)
@@ -355,7 +355,7 @@ final class TunnelManager: StorePaymentObserver {
}
operation.addObserver(BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: action.taskName,
cancelUponExpiration: true
))
@@ -408,7 +408,7 @@ final class TunnelManager: StorePaymentObserver {
operation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Update account data",
cancelUponExpiration: true
)
@@ -437,7 +437,7 @@ final class TunnelManager: StorePaymentObserver {
operation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Redeem voucher",
cancelUponExpiration: true
)
@@ -467,7 +467,7 @@ final class TunnelManager: StorePaymentObserver {
operation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Update device data",
cancelUponExpiration: true
)
@@ -503,7 +503,7 @@ final class TunnelManager: StorePaymentObserver {
operation.addObserver(
BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Rotate private key",
cancelUponExpiration: true
)
@@ -811,11 +811,11 @@ final class TunnelManager: StorePaymentObserver {
logger.error(error: error, message: "Failed to reconnect the tunnel.")
}
- // Refresh tunnel status only when connecting or reasserting to pick up the next relay,
+ // Refresh tunnel status only when connecting,reasserting or error to pick up the next relay,
// since both states may persist for a long period of time until the tunnel is fully
// connected.
switch tunnelStatus.state {
- case .connecting, .reconnecting:
+ case .connecting, .reconnecting, .error:
logger.debug("Refresh tunnel status due to reconnect.")
refreshTunnelStatus()
@@ -898,7 +898,7 @@ final class TunnelManager: StorePaymentObserver {
operation.addCondition(MutuallyExclusive(category: OperationCategory.deviceStateUpdate.category))
operation.addObserver(BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: "Refresh device state",
cancelUponExpiration: true
))
@@ -958,7 +958,7 @@ final class TunnelManager: StorePaymentObserver {
}
operation.addObserver(BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: taskName,
cancelUponExpiration: false
))
@@ -994,7 +994,7 @@ final class TunnelManager: StorePaymentObserver {
}
operation.addObserver(BackgroundObserver(
- application: application,
+ backgroundTaskProvider: application,
name: taskName,
cancelUponExpiration: false
))
@@ -1204,6 +1204,10 @@ private struct TunnelInteractorProxy: TunnelInteractor {
tunnelManager.tunnel
}
+ var backgroundTaskProvider: any BackgroundTaskProvider {
+ tunnelManager.application
+ }
+
func getPersistentTunnels() -> [any TunnelProtocol] {
tunnelManager.tunnelStore.getPersistentTunnels()
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelStore.swift b/ios/MullvadVPN/TunnelManager/TunnelStore.swift
index 7c9741a7b9..705a1f0ab2 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelStore.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelStore.swift
@@ -23,6 +23,7 @@ final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver {
typealias TunnelType = Tunnel
private let logger = Logger(label: "TunnelStore")
private let lock = NSLock()
+ private let application: BackgroundTaskProvider
/// Persistent tunnels registered with the system.
private var persistentTunnels: [TunnelType] = []
@@ -30,7 +31,8 @@ final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver {
/// Newly created tunnels, stored as collection of weak boxes.
private var newTunnels: [WeakBox<TunnelType>] = []
- init(application: UIApplication) {
+ init(application: BackgroundTaskProvider) {
+ self.application = application
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidBecomeActive(_:)),
@@ -62,7 +64,7 @@ final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver {
}
self.persistentTunnels = managers?.map { manager in
- let tunnel = Tunnel(tunnelProvider: manager)
+ let tunnel = Tunnel(tunnelProvider: manager, backgroundTaskProvider: self.application)
tunnel.addObserver(self)
self.logger.debug(
@@ -79,7 +81,7 @@ final class TunnelStore: TunnelStoreProtocol, TunnelStatusObserver {
defer { lock.unlock() }
let tunnelProviderManager = TunnelProviderManagerType()
- let tunnel = TunnelType(tunnelProvider: tunnelProviderManager)
+ let tunnel = TunnelType(tunnelProvider: tunnelProviderManager, backgroundTaskProvider: application)
tunnel.addObserver(self)
newTunnels = newTunnels.filter { $0.value != nil }
diff --git a/ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift b/ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift
index 046244582c..d1a39e4026 100644
--- a/ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift
+++ b/ios/MullvadVPN/TunnelManager/UIApplication+Extensions.swift
@@ -12,6 +12,7 @@ import Foundation
import UIKit
public protocol BackgroundTaskProvider {
+ var backgroundTimeRemaining: TimeInterval { get }
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
func beginBackgroundTask(
diff --git a/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift b/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
index da049b95cd..253e954512 100644
--- a/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
+++ b/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
@@ -94,7 +94,7 @@ class ShadowsocksLoaderTests: XCTestCase {
}
}
-private class ShadowsocksRelaySelectorStub: ShadowsocksRelaySelectorProtocol {
+class ShadowsocksRelaySelectorStub: ShadowsocksRelaySelectorProtocol {
var entryBridgeResult: Result<REST.BridgeRelay, Error> = .failure(ShadowsocksRelaySelectorStubError())
var exitBridgeResult: Result<REST.BridgeRelay, Error> = .failure(ShadowsocksRelaySelectorStubError())
private let relays: REST.ServerRelaysResponse
@@ -117,7 +117,7 @@ private class ShadowsocksRelaySelectorStub: ShadowsocksRelaySelectorProtocol {
}
}
-private class ShadowsocksConfigurationCacheStub: ShadowsocksConfigurationCacheProtocol {
+class ShadowsocksConfigurationCacheStub: ShadowsocksConfigurationCacheProtocol {
private(set) var cachedConfiguration: ShadowsocksConfiguration?
func read() throws -> ShadowsocksConfiguration {
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift
index b9daa63c87..302e4ca2f7 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnel.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadTypes
import NetworkExtension
class MockTunnel: TunnelProtocol {
@@ -18,10 +19,13 @@ class MockTunnel: TunnelProtocol {
var startDate: Date?
- required init(tunnelProvider: TunnelManagerProtocol) {
+ var backgroundTaskProvider: any BackgroundTaskProvider
+
+ required init(tunnelProvider: TunnelManagerProtocol, backgroundTaskProvider: BackgroundTaskProvider) {
status = .disconnected
isOnDemandEnabled = false
startDate = nil
+ self.backgroundTaskProvider = backgroundTaskProvider
}
// Observers are currently unimplemented
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
index 3da5215922..6f8999b19c 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
@@ -9,6 +9,7 @@
import Foundation
import MullvadREST
import MullvadSettings
+import MullvadTypes
// this is still very minimal, and will be fleshed out as needed.
class MockTunnelInteractor: TunnelInteractor {
@@ -22,6 +23,10 @@ class MockTunnelInteractor: TunnelInteractor {
var tunnel: (any TunnelProtocol)?
+ var backgroundTaskProvider: any BackgroundTaskProvider {
+ UIApplicationStub()
+ }
+
init(
isConfigurationLoaded: Bool,
settings: LatestTunnelSettings,
@@ -41,7 +46,10 @@ class MockTunnelInteractor: TunnelInteractor {
}
func createNewTunnel() -> any TunnelProtocol {
- return MockTunnel(tunnelProvider: SimulatorTunnelProviderManager())
+ return MockTunnel(
+ tunnelProvider: SimulatorTunnelProviderManager(),
+ backgroundTaskProvider: backgroundTaskProvider
+ )
}
func setTunnel(_ tunnel: (any TunnelProtocol)?, shouldRefreshTunnelState: Bool) {
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
index 3b9dff23d0..806cc7a412 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
@@ -14,8 +14,18 @@ import MullvadREST
import XCTest
-final class TunnelManagerTests: XCTestCase {
+class TunnelManagerTests: XCTestCase {
static let store = InMemorySettingsStore<SettingNotFound>()
+ private var tunnelObserver: TunnelObserver!
+
+ var application: UIApplicationStub!
+ var relayCacheTracker: RelayCacheTrackerStub!
+ var accountProxy: AccountsProxyStub!
+ var accessTokenManager: AccessTokenManagerStub!
+ var devicesProxy: DevicesProxyStub!
+ var apiProxy: APIProxyStub!
+
+ var transportProvider: TransportProvider!
override class func setUp() {
SettingsManager.unitTestStore = store
@@ -25,65 +35,100 @@ final class TunnelManagerTests: XCTestCase {
SettingsManager.unitTestStore = nil
}
- func testTunnelManager() {
- let application = UIApplicationStub()
- let tunnelStore = TunnelStoreStub()
- let relayCacheTracker = RelayCacheTrackerStub()
- let accountProxy = AccountsProxyStub()
- let devicesProxy = DevicesProxyStub(deviceResult: .success(Device.mock(publicKey: PrivateKey().publicKey)))
- let apiProxy = APIProxyStub()
- let accessTokenManager = AccessTokenManagerStub()
- let relaySelector = RelaySelectorStub.nonFallible()
+ override func setUp() async throws {
+ application = UIApplicationStub()
+ relayCacheTracker = RelayCacheTrackerStub()
+ accountProxy = AccountsProxyStub()
+ accessTokenManager = AccessTokenManagerStub()
+ devicesProxy = DevicesProxyStub(deviceResult: .success(Device.mock(publicKey: PrivateKey().publicKey)))
+ apiProxy = APIProxyStub()
+
+ transportProvider = TransportProvider(
+ urlSessionTransport: URLSessionTransport(urlSession: REST.makeURLSession()),
+ addressCache: REST.AddressCache(
+ canWriteToCache: true,
+ cacheDirectory: FileManager.default.temporaryDirectory
+ ),
+ transportStrategy: TransportStrategy(
+ datasource: AccessMethodRepositoryStub(accessMethods: [PersistentAccessMethod(
+ id: UUID(),
+ name: "direct",
+ isEnabled: true,
+ proxyConfiguration: .direct
+ )]),
+ shadowsocksLoader: ShadowsocksLoader(
+ cache: ShadowsocksConfigurationCacheStub(),
+ relaySelector: ShadowsocksRelaySelectorStub(relays: .mock()),
+ constraintsUpdater: RelayConstraintsUpdater(),
+ multihopUpdater: MultihopUpdater(listener: MultihopStateListener())
+ )
+ )
+ )
+
+ try SettingsManager.writeSettings(LatestTunnelSettings())
+ }
+
+ override func tearDown() async throws {
+ application = nil
+ relayCacheTracker = nil
+ accountProxy = nil
+ accessTokenManager = nil
+ devicesProxy = nil
+ apiProxy = nil
+ transportProvider = nil
+ tunnelObserver = nil
+ }
+
+ func testLogInStartsKeyRotations() async throws {
+ accountProxy.createAccountResult = .success(REST.NewAccountData.mockValue())
+
let tunnelManager = TunnelManager(
application: application,
- tunnelStore: tunnelStore,
+ tunnelStore: TunnelStoreStub(backgroundTaskProvider: application),
relayCacheTracker: relayCacheTracker,
accountsProxy: accountProxy,
devicesProxy: devicesProxy,
apiProxy: apiProxy,
accessTokenManager: accessTokenManager,
- relaySelector: relaySelector
+ relaySelector: RelaySelectorStub.nonFallible()
)
- XCTAssertNotNil(tunnelManager)
+
+ _ = try await tunnelManager.setNewAccount()
+ XCTAssertEqual(tunnelManager.isRunningPeriodicPrivateKeyRotation, true)
}
- func testLogInStartsKeyRotations() async throws {
- let application = UIApplicationStub()
- let tunnelStore = TunnelStoreStub()
- let relayCacheTracker = RelayCacheTrackerStub()
- var accountProxy = AccountsProxyStub()
- let devicesProxy = DevicesProxyStub(deviceResult: .success(Device.mock(publicKey: PrivateKey().publicKey)))
- let apiProxy = APIProxyStub()
- let accessTokenManager = AccessTokenManagerStub()
+ func testLogOutStopsKeyRotations() async throws {
accountProxy.createAccountResult = .success(REST.NewAccountData.mockValue())
- let relaySelector = RelaySelectorStub.nonFallible()
+
let tunnelManager = TunnelManager(
application: application,
- tunnelStore: tunnelStore,
+ tunnelStore: TunnelStoreStub(backgroundTaskProvider: application),
relayCacheTracker: relayCacheTracker,
accountsProxy: accountProxy,
devicesProxy: devicesProxy,
apiProxy: apiProxy,
accessTokenManager: accessTokenManager,
- relaySelector: relaySelector
+ relaySelector: RelaySelectorStub.nonFallible()
)
_ = try await tunnelManager.setNewAccount()
- XCTAssertEqual(tunnelManager.isRunningPeriodicPrivateKeyRotation, true)
+ await tunnelManager.unsetAccount()
+ XCTAssertEqual(tunnelManager.isRunningPeriodicPrivateKeyRotation, false)
}
- func testLogOutStopsKeyRotations() async throws {
- let application = UIApplicationStub()
- let tunnelStore = TunnelStoreStub()
- let relayCacheTracker = RelayCacheTrackerStub()
- var accountProxy = AccountsProxyStub()
- let devicesProxy = DevicesProxyStub(deviceResult: .success(Device.mock(publicKey: PrivateKey().publicKey)))
- let apiProxy = APIProxyStub()
- let accessTokenManager = AccessTokenManagerStub()
+ /// This test verifies tunnel gets out of `blockedState` after constraints are satisfied.
+ func testExitBlockedStateAfterSatisfyingConstraints() async throws {
+ let blockedExpectation = expectation(description: "Relay constraints aren't satisfied!")
+ let connectedExpectation = expectation(description: "Connected!")
+
accountProxy.createAccountResult = .success(REST.NewAccountData.mockValue())
- let relaySelector = RelaySelectorStub.nonFallible()
+
+ let relaySelector = RelaySelectorStub { _, _ in
+ try RelaySelectorStub.unsatisfied().selectRelays(with: RelayConstraints(), connectionAttemptCount: 0)
+ }
+
let tunnelManager = TunnelManager(
application: application,
- tunnelStore: tunnelStore,
+ tunnelStore: TunnelStore(application: application),
relayCacheTracker: relayCacheTracker,
accountsProxy: accountProxy,
devicesProxy: devicesProxy,
@@ -91,8 +136,47 @@ final class TunnelManagerTests: XCTestCase {
accessTokenManager: accessTokenManager,
relaySelector: relaySelector
)
+
+ let simulatorTunnelProviderHost = SimulatorTunnelProviderHost(
+ relaySelector: relaySelector,
+ transportProvider: transportProvider
+ )
+ SimulatorTunnelProvider.shared.delegate = simulatorTunnelProviderHost
+
+ let tunnelObserver = TunnelBlockObserver(
+ didUpdateTunnelStatus: { _, tunnelStatus in
+ switch tunnelStatus.state {
+ case let .error(blockedStateReason) where blockedStateReason == .noRelaysSatisfyingConstraints:
+ blockedExpectation.fulfill()
+ relaySelector.selectedRelaysResult = { relayConstraints, connectionAttemptCount in
+ try RelaySelectorStub.nonFallible().selectRelays(
+ with: relayConstraints,
+ connectionAttemptCount: connectionAttemptCount
+ )
+ }
+ tunnelManager.reconnectTunnel(selectNewRelay: true)
+
+ case .connected:
+ connectedExpectation.fulfill()
+ default:
+ return
+ }
+ }
+ )
+
+ self.tunnelObserver = tunnelObserver
+ tunnelManager.addObserver(tunnelObserver)
+
_ = try await tunnelManager.setNewAccount()
- await tunnelManager.unsetAccount()
- XCTAssertEqual(tunnelManager.isRunningPeriodicPrivateKeyRotation, false)
+
+ XCTAssertTrue(tunnelManager.deviceState.isLoggedIn)
+
+ tunnelManager.startTunnel()
+
+ await fulfillment(
+ of: [blockedExpectation, connectedExpectation],
+ timeout: .UnitTest.timeout,
+ enforceOrder: true
+ )
}
}
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift
index 5465839426..cc89fd776b 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStore+Stubs.swift
@@ -7,16 +7,18 @@
//
import Foundation
+import MullvadTypes
import NetworkExtension
struct TunnelStoreStub: TunnelStoreProtocol {
typealias TunnelType = TunnelStub
+ let backgroundTaskProvider: any BackgroundTaskProvider
func getPersistentTunnels() -> [TunnelType] {
[]
}
func createNewTunnel() -> TunnelType {
- TunnelStub(status: .invalid, isOnDemandEnabled: false)
+ TunnelStub(backgroundTaskProvider: backgroundTaskProvider, status: .invalid, isOnDemandEnabled: false)
}
}
@@ -25,24 +27,37 @@ class DummyTunnelStatusObserver: TunnelStatusObserver {
}
final class TunnelStub: TunnelProtocol, Equatable {
- convenience init(tunnelProvider: TunnelProviderManagerType) {
- self.init(status: .invalid, isOnDemandEnabled: false)
- }
+ typealias TunnelManagerProtocol = SimulatorTunnelProviderManager
static func == (lhs: TunnelStub, rhs: TunnelStub) -> Bool {
ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
- init(status: NEVPNStatus, isOnDemandEnabled: Bool, startDate: Date? = nil) {
+ convenience init(
+ tunnelProvider: SimulatorTunnelProviderManager,
+ backgroundTaskProvider: any BackgroundTaskProvider
+ ) {
+ self.init(backgroundTaskProvider: backgroundTaskProvider, status: .invalid, isOnDemandEnabled: false)
+ }
+
+ init(
+ backgroundTaskProvider: any BackgroundTaskProvider,
+ status: NEVPNStatus,
+ isOnDemandEnabled: Bool,
+ startDate: Date? = nil
+ ) {
self.status = status
self.isOnDemandEnabled = isOnDemandEnabled
self.startDate = startDate
+ self.backgroundTaskProvider = backgroundTaskProvider
}
func addObserver(_ observer: TunnelStatusObserver) {}
func removeObserver(_ observer: TunnelStatusObserver) {}
+ var backgroundTaskProvider: any BackgroundTaskProvider
+
var status: NEVPNStatus
var isOnDemandEnabled: Bool
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/UIApplication+Stubs.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/UIApplication+Stubs.swift
index 8b8fbe4999..a729c88a18 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/UIApplication+Stubs.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/UIApplication+Stubs.swift
@@ -12,6 +12,8 @@ import UIKit
@testable import MullvadTypes
struct UIApplicationStub: BackgroundTaskProvider {
+ var backgroundTimeRemaining: TimeInterval { .infinity }
+
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) {}
func beginBackgroundTask(
diff --git a/ios/MullvadVPNUITests/ConnectivityTests.swift b/ios/MullvadVPNUITests/ConnectivityTests.swift
index a46042fddd..32a219d05e 100644
--- a/ios/MullvadVPNUITests/ConnectivityTests.swift
+++ b/ios/MullvadVPNUITests/ConnectivityTests.swift
@@ -51,6 +51,7 @@ class ConnectivityTests: LoggedOutUITestCase {
}
/// Get the app into a blocked state by connecting to a relay then applying a filter which don't find this relay, then verify that app can still communicate by logging out and verifying that the device was successfully removed
+ // swiftlint:disable:next function_body_length
func testAPIReachableWhenBlocked() throws {
let hasTimeAccountNumber = getAccountWithTime()
addTeardownBlock {
diff --git a/ios/Operations/BackgroundObserver.swift b/ios/Operations/BackgroundObserver.swift
index 0dcbcb3287..2b9181a073 100644
--- a/ios/Operations/BackgroundObserver.swift
+++ b/ios/Operations/BackgroundObserver.swift
@@ -14,13 +14,13 @@ import UIKit
@available(iOSApplicationExtension, unavailable)
public final class BackgroundObserver: OperationObserver {
public let name: String
- public let application: BackgroundTaskProvider
+ public let backgroundTaskProvider: BackgroundTaskProvider
public let cancelUponExpiration: Bool
private var taskIdentifier: UIBackgroundTaskIdentifier?
- public init(application: BackgroundTaskProvider, name: String, cancelUponExpiration: Bool) {
- self.application = application
+ public init(backgroundTaskProvider: BackgroundTaskProvider, name: String, cancelUponExpiration: Bool) {
+ self.backgroundTaskProvider = backgroundTaskProvider
self.name = name
self.cancelUponExpiration = cancelUponExpiration
}
@@ -28,7 +28,7 @@ public final class BackgroundObserver: OperationObserver {
public func didAttach(to operation: Operation) {
let expirationHandler = cancelUponExpiration ? { operation.cancel() } : nil
- taskIdentifier = application.beginBackgroundTask(
+ taskIdentifier = backgroundTaskProvider.beginBackgroundTask(
withName: name,
expirationHandler: expirationHandler
)
@@ -44,7 +44,7 @@ public final class BackgroundObserver: OperationObserver {
public func operationDidFinish(_ operation: Operation, error: Error?) {
if let taskIdentifier {
- application.endBackgroundTask(taskIdentifier)
+ backgroundTaskProvider.endBackgroundTask(taskIdentifier)
}
}
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index 2cb726c962..c6b7a7e8cd 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -35,6 +35,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
PostQuantumKeyReceiver(tunnelProvider: self)
}()
+ // swiftlint:disable:next function_body_length
override init() {
Self.configureLogging()
providerLogger = Logger(label: "PacketTunnelProvider")
diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift
index 43d99fdfea..f81602019a 100644
--- a/ios/PacketTunnelCore/Actor/ObservedState.swift
+++ b/ios/PacketTunnelCore/Actor/ObservedState.swift
@@ -65,6 +65,11 @@ public struct ObservedConnectionState: Equatable, Codable {
public struct ObservedBlockedState: Equatable, Codable {
public var reason: BlockedStateReason
public var relayConstraints: RelayConstraints?
+
+ public init(reason: BlockedStateReason, relayConstraints: RelayConstraints? = nil) {
+ self.reason = reason
+ self.relayConstraints = relayConstraints
+ }
}
extension State {
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
index 4793193d3c..9f99690414 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
@@ -29,8 +29,7 @@ extension PacketTunnelActor {
Called on receipt of the new PQ-negotiated key, to reconnect to the relay, in PQ-secure mode.
*/
internal func postQuantumConnect() async {
- guard let connectionData = state.connectionData
- else {
+ guard let connectionData = state.connectionData else {
logger.error("Could not create connection state in PostQuantumConnect")
eventChannel.send(.reconnect(.current))
return
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index f8c0f5997e..86d6baae72 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -289,13 +289,6 @@ extension PacketTunnelActor {
let activeKey = activeKey(from: connectionState, in: settings)
- switch targetState {
- case .connecting:
- state = .connecting(connectionState)
- case .reconnecting:
- state = .reconnecting(connectionState)
- }
-
let entryConfiguration: TunnelAdapterConfiguration? = if connectionState.selectedRelays.entry != nil {
try ConfigurationBuilder(
privateKey: activeKey,
@@ -343,6 +336,13 @@ extension PacketTunnelActor {
// Resume tunnel monitoring and use IPv4 gateway as a probe address.
tunnelMonitor.start(probeAddress: connectionState.selectedRelays.exit.endpoint.ipv4Gateway)
+
+ switch targetState {
+ case .connecting:
+ state = .connecting(connectionState)
+ case .reconnecting:
+ state = .reconnecting(connectionState)
+ }
}
/**
@@ -355,6 +355,7 @@ extension PacketTunnelActor {
- Returns: New connection state or `nil` if current state is at or past `.disconnecting` phase.
*/
+ // swiftlint:disable:next function_body_length
internal func makeConnectionState(
nextRelays: NextRelays,
settings: Settings,
diff --git a/ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift b/ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift
index a49b9d0c44..5640b16033 100644
--- a/ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift
@@ -11,5 +11,5 @@ import Foundation
@testable import PacketTunnelCore
final class PostQuantumKeyExchangingUpdaterStub: PostQuantumKeyExchangingUpdaterProtocol {
- var reconfigurationHandler: ConfigUpdater? = nil
+ var reconfigurationHandler: ConfigUpdater?
}