diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-05-27 14:55:05 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-05-27 14:55:05 +0200 |
| commit | 0fc586eb732c3a55769069645312de9f42416d82 (patch) | |
| tree | b094a5602e0c7d14f25257c21320b1b6cea5ae51 | |
| parent | 6c9f7ec18da5bd26ce5880e5dc8f5c8b83d5f77f (diff) | |
| parent | dc759cbb35009ccede6fc5eab9b404525eac12fb (diff) | |
| download | mullvadvpn-0fc586eb732c3a55769069645312de9f42416d82.tar.xz mullvadvpn-0fc586eb732c3a55769069645312de9f42416d82.zip | |
Merge branch 'swift-6-packet-tunnel'
45 files changed, 158 insertions, 192 deletions
diff --git a/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift b/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift index efc8c0fe0d..82e0ebdb48 100644 --- a/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift +++ b/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift @@ -15,12 +15,10 @@ import NetworkExtension import XCTest class EphemeralPeerExchangeActorTests: XCTestCase { - var tcpConnection: NWTCPConnectionStub! var tunnelProvider: TunnelProviderStub! override func setUpWithError() throws { - tcpConnection = NWTCPConnectionStub() - tunnelProvider = TunnelProviderStub(tcpConnection: tcpConnection) + tunnelProvider = TunnelProviderStub() } func testKeyExchangeFailsWhenNegotiationCannotStart() { @@ -37,7 +35,6 @@ class EphemeralPeerExchangeActorTests: XCTestCase { let privateKey = PrivateKey() keyExchangeActor.startNegotiation(with: privateKey, enablePostQuantum: true, enableDaita: false) - tcpConnection.becomeViable() wait(for: [negotiationFailure]) } diff --git a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift index 59805230be..a62771003e 100644 --- a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift +++ b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift @@ -12,19 +12,6 @@ import NetworkExtension @testable import PacketTunnelCore @testable import WireGuardKitTypes -class NWTCPConnectionStub: NWTCPConnection { - var _isViable = false - override var isViable: Bool { - _isViable - } - - func becomeViable() { - willChangeValue(for: \.isViable) - _isViable = true - didChangeValue(for: \.isViable) - } -} - class TunnelProviderStub: TunnelProvider { func tunnelHandle() throws -> Int32 { 0 @@ -38,21 +25,6 @@ class TunnelProviderStub: TunnelProvider { send: { _, _, _, _ in return 0 } ) } - - let tcpConnection: NWTCPConnectionStub - - init(tcpConnection: NWTCPConnectionStub) { - self.tcpConnection = tcpConnection - } - - func createTCPConnectionThroughTunnel( - to remoteEndpoint: NWEndpoint, - enableTLS: Bool, - tlsParameters TLSParameters: NWTLSParameters?, - delegate: Any? - ) -> NWTCPConnection { - tcpConnection - } } class FailedNegotiatorStub: EphemeralPeerNegotiating { diff --git a/ios/MullvadRustRuntimeTests/TCPConnection.swift b/ios/MullvadRustRuntimeTests/TCPConnection.swift index cffce29e4e..c4c74ce76a 100644 --- a/ios/MullvadRustRuntimeTests/TCPConnection.swift +++ b/ios/MullvadRustRuntimeTests/TCPConnection.swift @@ -11,7 +11,7 @@ import Network /// Minimal implementation of TCP connection capable of receiving data. /// > Warning: Do not use this implementation in production code. See the warning in `start()`. -class TCPConnection: Connection { +class TCPConnection: Connection, @unchecked Sendable { private let dispatchQueue = DispatchQueue(label: "TCPConnection") private let nwConnection: NWConnection diff --git a/ios/MullvadRustRuntimeTests/UDPConnection.swift b/ios/MullvadRustRuntimeTests/UDPConnection.swift index 66ef178d96..c4b3becc1d 100644 --- a/ios/MullvadRustRuntimeTests/UDPConnection.swift +++ b/ios/MullvadRustRuntimeTests/UDPConnection.swift @@ -16,7 +16,7 @@ protocol Connection { /// Minimal implementation of UDP connection capable of sending data. /// > Warning: Do not use this implementation in production code. See the warning in `start()`. -class UDPConnection: Connection { +class UDPConnection: Connection, @unchecked Sendable { private let dispatchQueue = DispatchQueue(label: "UDPConnection") private let nwConnection: NWConnection diff --git a/ios/MullvadTypes/Promise.swift b/ios/MullvadTypes/Promise.swift index b5f5a58873..3bf66e7ae3 100644 --- a/ios/MullvadTypes/Promise.swift +++ b/ios/MullvadTypes/Promise.swift @@ -52,7 +52,7 @@ public final class Promise<Success, Failure: Error>: @unchecked Sendable { // allows the waiter to wait to `receive()` from another operation // asynchronously. It is important not to forget to call `send`, otherwise this // operation will block indefinitely. -public struct OneshotChannel { +public struct OneshotChannel: Sendable { private var continuation: AsyncStream<Void>.Continuation? private var stream: AsyncStream<Void> diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index de1e60d6b1..eb7a32dcd8 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -7338,7 +7338,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -7358,7 +7358,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -7469,7 +7469,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -7509,7 +7509,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -7532,7 +7532,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -7553,7 +7553,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -7755,7 +7755,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_STRICT_CONCURRENCY = minimal; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -7775,7 +7775,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_STRICT_CONCURRENCY = minimal; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Release; }; @@ -8230,7 +8230,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG MULLVAD_ENVIRONMENT_PRODUCTION $(inherited)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = MullvadVPN; }; @@ -8262,7 +8262,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = MullvadVPN; }; @@ -8396,7 +8396,7 @@ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_STRICT_CONCURRENCY = minimal; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Staging; }; @@ -8415,7 +8415,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Staging; @@ -8634,7 +8634,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -8657,7 +8657,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Staging; @@ -8789,7 +8789,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG MULLVAD_ENVIRONMENT_STAGING"; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = MullvadVPN; }; @@ -8813,7 +8813,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = MULLVAD_ENVIRONMENT_PRODUCTION; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = MullvadVPN; }; @@ -8864,7 +8864,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -8915,7 +8915,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -8966,7 +8966,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -9016,7 +9016,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -9044,7 +9044,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -9069,7 +9069,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Staging; @@ -9094,7 +9094,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -9119,7 +9119,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = MockRelease; @@ -9242,7 +9242,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development"; SWIFT_STRICT_CONCURRENCY = minimal; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = MockRelease; }; @@ -9261,7 +9261,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = MockRelease; @@ -9480,7 +9480,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -9503,7 +9503,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = MockRelease; diff --git a/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift b/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift index 87f215dd33..0ff1ab8df8 100644 --- a/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift +++ b/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Mullvad VPN AB. All rights reserved. // -import Foundation +@preconcurrency import Foundation @testable import MullvadLogging import Testing diff --git a/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift b/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift index 6bd16f5432..36d60bf4f7 100644 --- a/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift +++ b/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Mullvad VPN AB. All rights reserved. // -import Combine +@preconcurrency import Combine import MullvadSettings struct IPOverrideRepositoryStub: IPOverrideRepositoryProtocol { diff --git a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift index 6e619256c5..d268171d99 100644 --- a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift +++ b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift @@ -26,8 +26,8 @@ extension MigrationManagerTests { let backgroundMigrationExpectation = expectation(description: "Migration from packet tunnel") let foregroundMigrationExpectation = expectation(description: "Migration from host") - var migrationHappenedInPacketTunnel = false - var migrationHappenedInHost = false + nonisolated(unsafe) var migrationHappenedInPacketTunnel = false + nonisolated(unsafe) var migrationHappenedInHost = false packetTunnelProcess.async { [unowned self] in manager.migrateSettings(store: MigrationManagerTests.store) { backgroundMigrationResult in diff --git a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift index f62ca74403..ab93a2d37d 100644 --- a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift +++ b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift @@ -12,7 +12,7 @@ @testable import MullvadTypes import XCTest -final class MigrationManagerTests: XCTestCase { +final class MigrationManagerTests: XCTestCase, @unchecked Sendable { static let store = InMemorySettingsStore<SettingNotFound>() var manager: MigrationManager! diff --git a/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift b/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift index def507f6a7..fb77a6f5e9 100644 --- a/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift +++ b/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift @@ -8,6 +8,7 @@ import XCTest +@MainActor class InputTextFormatterTests: XCTestCase { private let accountNumber = "12345678" private var inputTextFormatter: InputTextFormatter! @@ -19,14 +20,14 @@ class InputTextFormatterTests: XCTestCase { maxGroups: 4 ) - override func setUp() { + override func setUp() async throws { inputTextFormatter = InputTextFormatter( string: accountNumber, configuration: configuration ) } - override func tearDown() { + override func tearDown() async throws { inputTextFormatter = nil } diff --git a/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift b/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift index fecc0f384b..8a5f95cb15 100644 --- a/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift +++ b/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift @@ -12,14 +12,14 @@ import MullvadSettings import MullvadTypes import Operations import PacketTunnelCore -import WireGuardKitTypes +@preconcurrency import WireGuardKitTypes import XCTest class DeviceCheckOperationTests: XCTestCase { private let operationQueue = AsyncOperationQueue() private let dispatchQueue = DispatchQueue(label: "TestQueue") - func testShouldReportExpiredAccount() { + func testShouldReportExpiredAccount() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -45,10 +45,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldNotRotateKeyForInvalidAccount() { + func testShouldNotRotateKeyForInvalidAccount() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -76,10 +76,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldNotRotateKeyForRevokedDevice() { + func testShouldNotRotateKeyForRevokedDevice() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -107,10 +107,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldRotateKeyOnMismatchImmediately() { + func testShouldRotateKeyOnMismatchImmediately() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -136,10 +136,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldRespectCooldownWhenAttemptingToRotateImmediately() { + func testShouldRespectCooldownWhenAttemptingToRotateImmediately() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -165,10 +165,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldNotRotateDeviceKeyWhenServerKeyIsIdentical() { + func testShouldNotRotateDeviceKeyWhenServerKeyIsIdentical() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -188,10 +188,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldNotRotateKeyBeforeRetryIntervalPassed() { + func testShouldNotRotateKeyBeforeRetryIntervalPassed() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -213,10 +213,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldRotateKeyOnceInTwentyFourHours() { + func testShouldRotateKeyOnceInTwentyFourHours() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -238,10 +238,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldReportFailedKeyRotationAttempt() { + func testShouldReportFailedKeyRotationAttempt() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -268,10 +268,10 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } - func testShouldFailOnKeyRotationRace() { + func testShouldFailOnKeyRotationRace() async { let expect = expectation(description: "Wait for operation to complete") let currentKey = PrivateKey() @@ -305,7 +305,7 @@ class DeviceCheckOperationTests: XCTestCase { expect.fulfill() } - waitForExpectations(timeout: .UnitTest.timeout) + await fulfillment(of: [expect], timeout: .UnitTest.timeout) } private func startDeviceCheck( @@ -327,7 +327,7 @@ class DeviceCheckOperationTests: XCTestCase { } /// Mock implementation of a remote service used by `DeviceCheckOperation` to reach the API. -private class MockRemoteService: DeviceCheckRemoteServiceProtocol { +private class MockRemoteService: DeviceCheckRemoteServiceProtocol, @unchecked Sendable { typealias AccountDataHandler = (_ accountNumber: String) throws -> Account typealias DeviceDataHandler = (_ accountNumber: String, _ deviceIdentifier: String) throws -> Device typealias RotateDeviceKeyHandler = ( @@ -356,7 +356,7 @@ private class MockRemoteService: DeviceCheckRemoteServiceProtocol { func getAccountData( accountNumber: String, - completion: @escaping (Result<Account, Error>) -> Void + completion: @escaping @Sendable (Result<Account, Error>) -> Void ) -> Cancellable { DispatchQueue.main.async { [self] in let result: Result<Account, Error> = Result { @@ -374,7 +374,7 @@ private class MockRemoteService: DeviceCheckRemoteServiceProtocol { func getDevice( accountNumber: String, identifier: String, - completion: @escaping (Result<Device, Error>) -> Void + completion: @escaping @Sendable (Result<Device, Error>) -> Void ) -> Cancellable { DispatchQueue.main.async { [self] in let result: Result<Device, Error> = Result { @@ -395,7 +395,7 @@ private class MockRemoteService: DeviceCheckRemoteServiceProtocol { accountNumber: String, identifier: String, publicKey: PublicKey, - completion: @escaping (Result<Device, Error>) -> Void + completion: @escaping @Sendable (Result<Device, Error>) -> Void ) -> Cancellable { DispatchQueue.main.async { [self] in let result: Result<Device, Error> = Result { diff --git a/ios/MullvadVPNUITests/Base/BaseUITestCase.swift b/ios/MullvadVPNUITests/Base/BaseUITestCase.swift index d9265ff8ea..71fbbf0b05 100644 --- a/ios/MullvadVPNUITests/Base/BaseUITestCase.swift +++ b/ios/MullvadVPNUITests/Base/BaseUITestCase.swift @@ -9,6 +9,7 @@ import Foundation import XCTest +@MainActor class BaseUITestCase: XCTestCase { let app = XCUIApplication() static let defaultTimeout = 5.0 @@ -203,20 +204,23 @@ class BaseUITestCase: XCTestCase { /// Suite level teardown ran after all tests in suite have been executed override class func tearDown() { - if shouldUninstallAppInTeardown() && uninstallAppInTestSuiteTearDown() { - uninstallApp() + // This function is not marked `@MainActor` therefore cannot legally enter its context without help + Task { @MainActor in + if shouldUninstallAppInTeardown() && uninstallAppInTestSuiteTearDown() { + uninstallApp() + } } } /// Test level setup - override func setUp() { + override func setUp() async throws { currentTestCaseShouldCapturePackets = false // Reset for each test case run continueAfterFailure = false app.launch() } /// Test level teardown - override func tearDown() { + override func tearDown() async throws { if currentTestCaseShouldCapturePackets { guard let packetCaptureSession = packetCaptureSession else { XCTFail("Packet capture session unexpectedly not set up") diff --git a/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift b/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift index 00fbd24ed0..72847d7e42 100644 --- a/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift +++ b/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift @@ -13,8 +13,8 @@ import XCTest class LoggedInWithTimeUITestCase: BaseUITestCase { var hasTimeAccountNumber: String? - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() agreeToTermsOfServiceIfShown() // Make sure that if a previous test ended up in a state where the app got stuck connecting to a relay @@ -35,8 +35,8 @@ class LoggedInWithTimeUITestCase: BaseUITestCase { app.launch() } - override func tearDown() { - super.tearDown() + override func tearDown() async throws { + try await super.tearDown() guard let hasTimeAccountNumber = self.hasTimeAccountNumber else { XCTFail("hasTimeAccountNumber unexpectedly not set") diff --git a/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift b/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift index 3414239dc5..9b00ee47ce 100644 --- a/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift +++ b/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift @@ -10,8 +10,8 @@ import Foundation /// Base class for tests which should start from a logged out state class LoggedOutUITestCase: BaseUITestCase { - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() agreeToTermsOfServiceIfShown() logoutIfLoggedIn() diff --git a/ios/MullvadVPNUITests/Networking/FirewallClient.swift b/ios/MullvadVPNUITests/Networking/FirewallClient.swift index 35d8d25968..55bb35d03b 100644 --- a/ios/MullvadVPNUITests/Networking/FirewallClient.swift +++ b/ios/MullvadVPNUITests/Networking/FirewallClient.swift @@ -35,8 +35,8 @@ class FirewallClient: TestRouterAPIClient { "protocols": firewallRule.protocolsAsStringArray(), ] - var requestError: Error? - var requestResponse: URLResponse? + nonisolated(unsafe) var requestError: Error? + nonisolated(unsafe) var requestResponse: URLResponse? let completionHandlerInvokedExpectation = XCTestExpectation( description: "Completion handler for the request is invoked" ) @@ -82,8 +82,8 @@ class FirewallClient: TestRouterAPIClient { var request = URLRequest(url: removeRulesURL) request.httpMethod = "DELETE" - var requestResponse: URLResponse? - var requestError: Error? + nonisolated(unsafe) var requestResponse: URLResponse? + nonisolated(unsafe) var requestError: Error? let completionHandlerInvokedExpectation = XCTestExpectation( description: "Completion handler for the request is invoked" ) diff --git a/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift b/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift index 922fbabce0..9d5bd0d64b 100644 --- a/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift +++ b/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift @@ -15,7 +15,7 @@ enum MullvadAPIError: Error { case requestError } -class MullvadAPIWrapper { +class MullvadAPIWrapper: @unchecked Sendable { private var mullvadAPI: MullvadApi private let throttleQueue = DispatchQueue(label: "MullvadAPIWrapperThrottleQueue", qos: .userInitiated) private var lastCallDate: Date? @@ -38,7 +38,7 @@ class MullvadAPIWrapper { } /// Throttle what's in the callback. This is used for throttling requests to the app API. All requests should be throttled or else we might be rate limited. The API allows maximum 5 requests per second. Note that the implementation assumes what is being throttled is synchronous. - private func throttle(callback: @escaping () -> Void) { + private func throttle(callback: @Sendable @escaping () -> Void) { throttleQueue.async { let now = Date() var delay: TimeInterval = 0 @@ -85,7 +85,7 @@ class MullvadAPIWrapper { } func createAccount() -> String { - var accountNumber = String() + nonisolated(unsafe) var accountNumber = String() let requestCompletedExpectation = XCTestExpectation(description: "Create account request completed") throttle { @@ -150,7 +150,7 @@ class MullvadAPIWrapper { } func getAccountExpiry(_ account: String) throws -> Date { - var accountExpiryDate: Date = .distantPast + nonisolated(unsafe) var accountExpiryDate: Date = .distantPast let requestCompletedExpectation = XCTestExpectation(description: "Get account expiry request completed") throttle { @@ -171,7 +171,7 @@ class MullvadAPIWrapper { } func getDevices(_ account: String) throws -> [Device] { - var devices: [Device] = [] + nonisolated(unsafe) var devices: [Device] = [] let requestCompletedExpectation = XCTestExpectation(description: "Get devices request completed") throttle { diff --git a/ios/MullvadVPNUITests/Networking/Networking.swift b/ios/MullvadVPNUITests/Networking/Networking.swift index df814f70c7..8ff3d8b836 100644 --- a/ios/MullvadVPNUITests/Networking/Networking.swift +++ b/ios/MullvadVPNUITests/Networking/Networking.swift @@ -7,7 +7,7 @@ // import Foundation -import Network +@preconcurrency import Network import XCTest enum TransportProtocol: Codable { @@ -60,7 +60,7 @@ class Networking { let socketHost = NWEndpoint.Host(host) let socketPort = try XCTUnwrap(NWEndpoint.Port(port)) let connection = NWConnection(host: socketHost, port: socketPort, using: .tcp) - var connectionError: Error? + nonisolated(unsafe) var connectionError: Error? let connectionStateDeterminedExpectation = XCTestExpectation( description: "Completion handler for the reach ad serving domain request is invoked" @@ -173,9 +173,9 @@ class Networking { var request = URLRequest(url: mullvadDNSLeakURL) request.setValue("application/json", forHTTPHeaderField: "accept") - var requestData: Data? - var requestResponse: URLResponse? - var requestError: Error? + nonisolated(unsafe) var requestData: Data? + nonisolated(unsafe) var requestResponse: URLResponse? + nonisolated(unsafe) var requestError: Error? let completionHandlerInvokedExpectation = XCTestExpectation( description: "Completion handler for the request is invoked" ) diff --git a/ios/MullvadVPNUITests/Networking/PacketCapture.swift b/ios/MullvadVPNUITests/Networking/PacketCapture.swift index d47e8433ba..266177c8db 100644 --- a/ios/MullvadVPNUITests/Networking/PacketCapture.swift +++ b/ios/MullvadVPNUITests/Networking/PacketCapture.swift @@ -290,9 +290,9 @@ class PacketCaptureClient: TestRouterAPIClient { } } - var requestResponse: URLResponse? - var requestError: Error? - var responseData: Data? + nonisolated(unsafe) var requestResponse: URLResponse? + nonisolated(unsafe) var requestError: Error? + nonisolated(unsafe) var responseData: Data? let completionHandlerInvokedExpectation = XCTestExpectation( description: "Completion handler for the request is invoked" diff --git a/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift b/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift index 20bc525d13..819c10af60 100644 --- a/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift +++ b/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift @@ -65,7 +65,7 @@ class PartnerAPIClient { request.httpMethod = method request.setValue("Basic \(accessToken)", forHTTPHeaderField: "Authorization") - var jsonResponse: [String: Any] = [:] + nonisolated(unsafe) var jsonResponse: [String: Any] = [:] do { if let jsonObject = jsonObject { @@ -81,7 +81,7 @@ class PartnerAPIClient { description: "Completion handler for the request is invoked" ) - var requestError: Error? + nonisolated(unsafe) var requestError: Error? let task = URLSession.shared.dataTask(with: request) { data, response, error in requestError = error diff --git a/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift b/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift index 0d9c9da258..0035062269 100644 --- a/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift +++ b/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift @@ -19,8 +19,8 @@ class TestRouterAPIClient { let completionHandlerInvokedExpectation = XCTestExpectation( description: "Completion handler for the request is invoked" ) - var deviceIPAddress = "" - var requestError: Error? + nonisolated(unsafe) var deviceIPAddress = "" + nonisolated(unsafe) var requestError: Error? let dataTask = URLSession.shared.dataTask(with: request) { data, _, _ in defer { completionHandlerInvokedExpectation.fulfill() } diff --git a/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift b/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift index 590246c5cc..99d4677235 100644 --- a/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift +++ b/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift @@ -6,10 +6,10 @@ // Copyright © 2024 Mullvad VPN AB. All rights reserved. // -import Network +@preconcurrency import Network import XCTest -class TrafficGenerator { +class TrafficGenerator: @unchecked Sendable { let destinationHost: String let port: Int var connection: NWConnection diff --git a/ios/MullvadVPNUITests/Pages/LoginPage.swift b/ios/MullvadVPNUITests/Pages/LoginPage.swift index 24d1adcf2c..e120637a52 100644 --- a/ios/MullvadVPNUITests/Pages/LoginPage.swift +++ b/ios/MullvadVPNUITests/Pages/LoginPage.swift @@ -60,14 +60,16 @@ class LoginPage: Page { // Success icon is only shown very briefly, since another view is presented after success icon is shown. // Therefore we need to poll faster than waitForElement function. let successIconDisplayedExpectation = XCTestExpectation(description: "Success icon shown") - var isShown = false + nonisolated(unsafe) var isShown = false let timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [self] _ in - let statusImageView = self.app.images[.statusImageView] + DispatchQueue.main.async { + let statusImageView = self.app.images[.statusImageView] - if statusImageView.exists { - if statusImageView.value as? String == "success" { - isShown = true - successIconDisplayedExpectation.fulfill() + if statusImageView.exists { + if statusImageView.value as? String == "success" { + isShown = true + successIconDisplayedExpectation.fulfill() + } } } } diff --git a/ios/MullvadVPNUITests/Pages/Page.swift b/ios/MullvadVPNUITests/Pages/Page.swift index c11ac71577..5cb281f70d 100644 --- a/ios/MullvadVPNUITests/Pages/Page.swift +++ b/ios/MullvadVPNUITests/Pages/Page.swift @@ -9,6 +9,7 @@ import Foundation import XCTest +@MainActor class Page { let app: XCUIApplication diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift index fe3d18c509..f1d3c6a501 100644 --- a/ios/MullvadVPNUITests/RelayTests.swift +++ b/ios/MullvadVPNUITests/RelayTests.swift @@ -17,14 +17,14 @@ private struct RelayInfo { class RelayTests: LoggedInWithTimeUITestCase { var removeFirewallRulesInTearDown = false - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() removeFirewallRulesInTearDown = false } - override func tearDown() { - super.tearDown() + override func tearDown() async throws { + try await super.tearDown() if removeFirewallRulesInTearDown { FirewallClient().removeRules() diff --git a/ios/MullvadVPNUITests/SettingsMigrationTests.swift b/ios/MullvadVPNUITests/SettingsMigrationTests.swift index bf616fac2b..64e631deb9 100644 --- a/ios/MullvadVPNUITests/SettingsMigrationTests.swift +++ b/ios/MullvadVPNUITests/SettingsMigrationTests.swift @@ -43,8 +43,8 @@ class SettingsMigrationTests: BaseUITestCase { return false } - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() agreeToTermsOfServiceIfShown() diff --git a/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift b/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift index 926b94a9e3..ab88b22167 100644 --- a/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift +++ b/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift @@ -66,7 +66,7 @@ final class DeviceCheckOperation: ResultOperation<DeviceCheck>, @unchecked Senda Begins the flow by fetching device state and then fetching account and device data. Calls `didReceiveData()` with the received data when done. */ - private func startFlow(completion: @escaping (Result<DeviceCheck, Error>) -> Void) { + private func startFlow(completion: @escaping @Sendable (Result<DeviceCheck, Error>) -> Void) { do { guard case let .loggedIn(accountData, deviceData) = try deviceStateAccessor.read() else { throw DeviceCheckError.invalidDeviceState @@ -90,7 +90,7 @@ final class DeviceCheckOperation: ResultOperation<DeviceCheck>, @unchecked Senda private func didReceiveData( accountResult: Result<Account, Error>, deviceResult: Result<Device, Error>, - completion: @escaping (Result<DeviceCheck, Error>) -> Void + completion: @escaping @Sendable (Result<DeviceCheck, Error>) -> Void ) { do { let accountVerdict = try accountVerdict(from: accountResult) @@ -158,7 +158,7 @@ final class DeviceCheckOperation: ResultOperation<DeviceCheck>, @unchecked Senda then it rotate device key by marking the beginning of key rotation, updating device state and persisting before proceeding to rotate the key. */ - private func rotateKeyIfNeeded(completion: @escaping (Result<KeyRotationStatus, Error>) -> Void) { + private func rotateKeyIfNeeded(completion: @escaping @Sendable (Result<KeyRotationStatus, Error>) -> Void) { let deviceState: DeviceState do { deviceState = try deviceStateAccessor.read() diff --git a/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift b/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift index cb749f68a5..a86bebbd54 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift @@ -46,6 +46,8 @@ extension NEProviderStopReason: CustomStringConvertible { return "sleep" case .appUpdate: return "app update" + case .internalError: + return "internal error" @unknown default: return "unknown value (\(rawValue))" } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift index 7ae35c4685..5345287b73 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Mullvad VPN AB. All rights reserved. // -import Combine +@preconcurrency import Combine import Foundation extension PacketTunnelActor { diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift index 9cf3833bb8..c467ca270d 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift @@ -7,7 +7,7 @@ // import Foundation -import MullvadTypes +@preconcurrency import MullvadTypes import Network import WireGuardKitTypes diff --git a/ios/PacketTunnelCore/Pinger/PingerProtocol.swift b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift index 1c06c634f9..0da7f7a3f0 100644 --- a/ios/PacketTunnelCore/Pinger/PingerProtocol.swift +++ b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift @@ -29,7 +29,7 @@ public struct PingerSendResult { } /// A type capable of sending and receving ICMP traffic. -public protocol PingerProtocol { +public protocol PingerProtocol: Sendable { var onReply: ((PingerReply) -> Void)? { get set } func startPinging(destAddress: IPv4Address) throws diff --git a/ios/PacketTunnelCore/Pinger/TunnelPinger.swift b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift index 10807e7362..20defd7a90 100644 --- a/ios/PacketTunnelCore/Pinger/TunnelPinger.swift +++ b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift @@ -12,7 +12,7 @@ import Network import PacketTunnelCore import WireGuardKit -public final class TunnelPinger: PingerProtocol { +public final class TunnelPinger: PingerProtocol, @unchecked Sendable { private var sequenceNumber: UInt16 = 0 private let stateLock = NSLock() private let pingReceiveQueue: DispatchQueue diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift index 664fd69ed9..d9a0a33250 100644 --- a/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift +++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift @@ -9,7 +9,7 @@ import Foundation /// A type that can provide statistics and basic information about tunnel device. -public protocol TunnelDeviceInfoProtocol { +public protocol TunnelDeviceInfoProtocol: Sendable { /// Returns tunnel interface name (i.e utun0) if available. var interfaceName: String? { get } diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift index c1160d25f2..9d20efcc01 100644 --- a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift +++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift @@ -13,7 +13,7 @@ import Network import NetworkExtension /// Tunnel monitor. -public final class TunnelMonitor: TunnelMonitorProtocol { +public final class TunnelMonitor: TunnelMonitorProtocol, @unchecked Sendable { private let tunnelDeviceInfo: TunnelDeviceInfoProtocol private let nslock = NSLock() @@ -22,7 +22,6 @@ public final class TunnelMonitor: TunnelMonitorProtocol { private let timings: TunnelMonitorTimings private var pinger: PingerProtocol - private var isObservingDefaultPath = false private var timer: DispatchSourceTimer? private var state: TunnelMonitorState diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift index 7e925ec932..ee1836bb22 100644 --- a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift +++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift @@ -8,7 +8,7 @@ import MullvadTypes -public struct TunnelMonitorTimings { +public struct TunnelMonitorTimings: Sendable { /// Interval for periodic heartbeat ping issued when traffic is flowing. /// Should help to detect connectivity issues on networks that drop traffic in one of directions, /// regardless if tx/rx counters are being updated. diff --git a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift index e40f9d78e5..82e33e8c03 100644 --- a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift +++ b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift @@ -9,7 +9,7 @@ import Foundation /// Struct describing serializable URLRequest data. -public struct ProxyURLRequest: Codable { +public struct ProxyURLRequest: Codable, Sendable { public let id: UUID public let url: URL public let method: String? diff --git a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift index 85737b4bc9..d340974131 100644 --- a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift +++ b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift @@ -89,10 +89,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: true, enableDaita: false) await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) - wait( - for: [reconfigurationExpectation, negotiationSuccessful], - timeout: .UnitTest.invertedTimeout - ) + await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout) } func testSingleHopDaitaPeerExchange() async throws { @@ -123,10 +120,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: false, enableDaita: true) await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) - wait( - for: [reconfigurationExpectation, negotiationSuccessful], - timeout: .UnitTest.invertedTimeout - ) + await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout) } func testMultiHopPostQuantumKeyExchange() async throws { @@ -158,10 +152,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: true, enableDaita: false) await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) - wait( - for: [reconfigurationExpectation, negotiationSuccessful], - timeout: .UnitTest.invertedTimeout - ) + await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout) } func testMultiHopDaitaExchange() async throws { @@ -188,10 +179,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: false, enableDaita: true) await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) - wait( - for: [reconfigurationExpectation, negotiationSuccessful], - timeout: .UnitTest.invertedTimeout - ) + await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout) } func stubConnectionState( diff --git a/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift b/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift index 9766aac9ec..341e41eba6 100644 --- a/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift @@ -12,7 +12,7 @@ import NetworkExtension @testable import PacketTunnelCore @testable import WireGuardKitTypes -final class EphemeralPeerExchangeActorStub: EphemeralPeerExchangeActorProtocol { +final class EphemeralPeerExchangeActorStub: EphemeralPeerExchangeActorProtocol, @unchecked Sendable { typealias KeyNegotiationResult = Result<(PreSharedKey, PrivateKey), EphemeralPeerExchangeErrorStub> var result: KeyNegotiationResult = .failure(.unknown) diff --git a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift index bb8d94a806..135b2a3e1c 100644 --- a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift +++ b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift @@ -8,8 +8,8 @@ import Foundation import MullvadMockData -import MullvadREST -import PacketTunnelCore +@preconcurrency import MullvadREST +@preconcurrency import PacketTunnelCore extension PacketTunnelActorTimings { static var timingsForTests: PacketTunnelActorTimings { diff --git a/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift b/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift index 9c468fd45d..bab560267e 100644 --- a/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift +++ b/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift @@ -12,7 +12,7 @@ import Network @testable import PacketTunnelCore /// Ping client mock that can be used to simulate network transmission errors and delays. -class PingerMock: PingerProtocol { +class PingerMock: PingerProtocol, @unchecked Sendable { typealias OutcomeDecider = (IPv4Address, UInt16) -> Outcome private let decideOutcome: OutcomeDecider diff --git a/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift b/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift index 40aaafee73..e6cde648ba 100644 --- a/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift @@ -10,7 +10,7 @@ import Foundation import PacketTunnelCore /// Tunnel device stub that returns fixed interface name and feeds network stats from the type implementing `NetworkStatsProviding` -struct TunnelDeviceInfoStub: TunnelDeviceInfoProtocol { +struct TunnelDeviceInfoStub: TunnelDeviceInfoProtocol, @unchecked Sendable { let networkStatsProviding: NetworkStatsProviding var interfaceName: String? { diff --git a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift index 4e77a6b617..7221e72a55 100644 --- a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift +++ b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift @@ -90,8 +90,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { await multiHopExchanger.start() - wait( - for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + await fulfillment( + of: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout ) } @@ -133,8 +133,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { }) await multiHopPeerExchanger.start() - wait( - for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + await fulfillment( + of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout ) } @@ -171,8 +171,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { }) await multiHopPeerExchanger.start() - wait( - for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + await fulfillment( + of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout ) } @@ -213,8 +213,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { }) await multiHopPeerExchanger.start() - wait( - for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + await fulfillment( + of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout ) } diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift index 484e69ffe1..2c4e145b1c 100644 --- a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift +++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Mullvad VPN AB. All rights reserved. // -import Combine +@preconcurrency import Combine @testable import MullvadMockData @testable import MullvadREST @testable import MullvadSettings diff --git a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift index cba495a596..7707ffabf7 100644 --- a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift +++ b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift @@ -68,8 +68,8 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { await singleHopPostQuantumKeyExchanging.start() - wait( - for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + await fulfillment( + of: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout ) } @@ -110,8 +110,8 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { }) await singleHopPostQuantumKeyExchanging.start() - wait( - for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + await fulfillment( + of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout ) } @@ -147,8 +147,8 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { }) await multiHopPeerExchanger.start() - wait( - for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + await fulfillment( + of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout ) } diff --git a/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift b/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift index 5f15b7cf45..62d0fc057f 100644 --- a/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift +++ b/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift @@ -15,7 +15,7 @@ import XCTest final class TunnelMonitorTests: XCTestCase { let networkCounters = NetworkCounters() - func testShouldDetermineConnectionEstablished() throws { + func testShouldDetermineConnectionEstablished() async throws { let connectedExpectation = expectation(description: "Should report connected.") let connectionLostExpectation = expectation(description: "Should not report connection loss") connectionLostExpectation.isInverted = true @@ -38,10 +38,10 @@ final class TunnelMonitorTests: XCTestCase { tunnelMonitor.start(probeAddress: .loopback) - waitForExpectations(timeout: .UnitTest.invertedTimeout) + await fulfillment(of: [connectedExpectation, connectionLostExpectation], timeout: .UnitTest.invertedTimeout) } - func testInitialConnectionTimings() { + func testInitialConnectionTimings() async throws { // Setup pinger so that it never receives any replies. let pinger = PingerMock(networkStatsReporting: networkCounters) { _, _ in .ignore } @@ -108,7 +108,7 @@ final class TunnelMonitorTests: XCTestCase { // Start monitoring. tunnelMonitor.start(probeAddress: .loopback) - waitForExpectations(timeout: TimeInterval(timeout) / 1000) + await fulfillment(of: [expectation], timeout: TimeInterval(timeout) / 1000) } } |
