diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift index 24d02d36e787..691781ef4ce5 100644 --- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift +++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift @@ -107,13 +107,14 @@ extension REST { switch httpStatus { case let httpStatus where httpStatus.isSuccess: return .decoding { - let serverRelays = try self.responseDecoder.decode( + // Discarding result since we're only interested in knowing that it's parseable. + _ = try self.responseDecoder.decode( ServerRelaysResponse.self, from: data ) let newEtag = response.value(forHTTPHeaderField: HTTPHeader.etag) - return .newContent(newEtag, serverRelays) + return .newContent(newEtag, data) } case .notModified where etag != nil: @@ -284,7 +285,7 @@ extension REST { public enum ServerRelaysCacheResponse { case notModified - case newContent(_ etag: String?, _ value: ServerRelaysResponse) + case newContent(_ etag: String?, _ rawData: Data) } private struct CreateApplePaymentRequest: Encodable { diff --git a/ios/MullvadREST/Relay/CachedRelays.swift b/ios/MullvadREST/Relay/CachedRelays.swift index f22b236e87b4..b1f8c706c0ce 100644 --- a/ios/MullvadREST/Relay/CachedRelays.swift +++ b/ios/MullvadREST/Relay/CachedRelays.swift @@ -8,7 +8,7 @@ import Foundation -/// A struct that represents the relay cache on disk +/// A struct that represents the relay cache in memory public struct CachedRelays: Codable, Equatable { /// E-tag returned by server public let etag: String? diff --git a/ios/MullvadREST/Relay/IPOverrideWrapper.swift b/ios/MullvadREST/Relay/IPOverrideWrapper.swift index 84516130d1ec..1ac2c4dc7dc6 100644 --- a/ios/MullvadREST/Relay/IPOverrideWrapper.swift +++ b/ios/MullvadREST/Relay/IPOverrideWrapper.swift @@ -18,21 +18,23 @@ public class IPOverrideWrapper: RelayCacheProtocol { self.ipOverrideRepository = ipOverrideRepository } - public func read() throws -> CachedRelays { + public func read() throws -> StoredRelays { let cache = try relayCache.read() let relayResponse = apply(overrides: ipOverrideRepository.fetchAll(), to: cache.relays) + let rawData = try REST.Coding.makeJSONEncoder().encode(relayResponse) - return CachedRelays(relays: relayResponse, updatedAt: cache.updatedAt) + return try StoredRelays(etag: cache.etag, rawData: rawData, updatedAt: cache.updatedAt) } - public func readPrebundledRelays() throws -> CachedRelays { + public func readPrebundledRelays() throws -> StoredRelays { let prebundledRelays = try relayCache.readPrebundledRelays() let relayResponse = apply(overrides: ipOverrideRepository.fetchAll(), to: prebundledRelays.relays) + let rawData = try REST.Coding.makeJSONEncoder().encode(relayResponse) - return CachedRelays(relays: relayResponse, updatedAt: prebundledRelays.updatedAt) + return try StoredRelays(etag: prebundledRelays.etag, rawData: rawData, updatedAt: prebundledRelays.updatedAt) } - public func write(record: CachedRelays) throws { + public func write(record: StoredRelays) throws { try relayCache.write(record: record) } diff --git a/ios/MullvadREST/Relay/RelayCache.swift b/ios/MullvadREST/Relay/RelayCache.swift index eedf3b997ad2..966d3a810d83 100644 --- a/ios/MullvadREST/Relay/RelayCache.swift +++ b/ios/MullvadREST/Relay/RelayCache.swift @@ -12,59 +12,66 @@ import MullvadTypes public protocol RelayCacheProtocol { /// Reads from a cached list, /// which falls back to reading from prebundled relays if there was no cache hit - func read() throws -> CachedRelays + func read() throws -> StoredRelays /// Reads the relays file that were prebundled with the app installation. /// /// > Warning: Prefer `read()` over this unless there is an explicit need to read /// relays from the bundle, because those might contain stale data. - func readPrebundledRelays() throws -> CachedRelays - func write(record: CachedRelays) throws + func readPrebundledRelays() throws -> StoredRelays + func write(record: StoredRelays) throws } /// - Warning: `RelayCache` should not be used directly. It should be used through `IPOverrideWrapper` to have /// ip overrides applied. public final class RelayCache: RelayCacheProtocol { - private let fileCache: any FileCacheProtocol + private let fileURL: URL + private let fileCache: any FileCacheProtocol /// Designated initializer public init(cacheDirectory: URL) { - fileCache = FileCache(fileURL: cacheDirectory.appendingPathComponent("relays.json", isDirectory: false)) + fileURL = cacheDirectory.appendingPathComponent("relays.json", isDirectory: false) + fileCache = FileCache(fileURL: fileURL) } /// Initializer that accepts a custom FileCache implementation. Used in tests. - init(fileCache: some FileCacheProtocol) { + init(fileCache: some FileCacheProtocol) { + fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("relays.json", isDirectory: false) self.fileCache = fileCache } - /// Safely read the cache file from disk using file coordinator and fallback to prebundled - /// relays in case if the relay cache file is missing. - public func read() throws -> CachedRelays { + /// Safely read the cache file from disk using file coordinator and fallback in the following manner: + /// 1. If there is a file but it's not decodable, try to parse into the old cache format. If it's still + /// not decodable, read the pre-bundled data. + /// 2. If there is no file, read from the pre-bundled data. + public func read() throws -> StoredRelays { do { return try fileCache.read() - } catch { - if error is DecodingError || (error as? CocoaError)?.code == .fileReadNoSuchFile { + } catch is DecodingError { + do { + let oldFormatFileCache = FileCache(fileURL: fileURL) + return try StoredRelays(cachedRelays: try oldFormatFileCache.read()) + } catch { return try readPrebundledRelays() - } else { - throw error } + } catch { + return try readPrebundledRelays() } } /// Safely write the cache file on disk using file coordinator. - public func write(record: CachedRelays) throws { + public func write(record: StoredRelays) throws { try fileCache.write(record) } /// Read pre-bundled relays file from disk. - public func readPrebundledRelays() throws -> CachedRelays { + public func readPrebundledRelays() throws -> StoredRelays { guard let prebundledRelaysFileURL = Bundle(for: Self.self).url(forResource: "relays", withExtension: "json") else { throw CocoaError(.fileNoSuchFile) } let data = try Data(contentsOf: prebundledRelaysFileURL) - let relays = try REST.Coding.makeJSONDecoder().decode(REST.ServerRelaysResponse.self, from: data) - return CachedRelays( - relays: relays, + return try StoredRelays( + rawData: data, updatedAt: Date(timeIntervalSince1970: 0) ) } diff --git a/ios/MullvadREST/Relay/StoredRelays.swift b/ios/MullvadREST/Relay/StoredRelays.swift new file mode 100644 index 000000000000..9700a03496eb --- /dev/null +++ b/ios/MullvadREST/Relay/StoredRelays.swift @@ -0,0 +1,43 @@ +// +// StoredRelays.swift +// MullvadVPNUITests +// +// Created by Jon Petersson on 2024-09-09. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +/// A struct that represents the relay cache on disk +public struct StoredRelays: Codable, Equatable { + /// E-tag returned by server + public let etag: String? + + /// The raw relay JSON data stored within the cache entry + public let rawData: Data + + /// The date when this cache was last updated + public let updatedAt: Date + + /// Relays parsed from the JSON data + public let relays: REST.ServerRelaysResponse + + /// `CachedRelays` representation + public var cachedRelays: CachedRelays { + CachedRelays(etag: etag, relays: relays, updatedAt: updatedAt) + } + + public init(etag: String? = nil, rawData: Data, updatedAt: Date) throws { + self.etag = etag + self.rawData = rawData + self.updatedAt = updatedAt + relays = try REST.Coding.makeJSONDecoder().decode(REST.ServerRelaysResponse.self, from: rawData) + } + + public init(cachedRelays: CachedRelays) throws { + etag = cachedRelays.etag + rawData = try REST.Coding.makeJSONEncoder().encode(cachedRelays.relays) + updatedAt = cachedRelays.updatedAt + relays = cachedRelays.relays + } +} diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift index c0d83bc701fb..2cc305879864 100644 --- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift +++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift @@ -43,6 +43,6 @@ final public class ShadowsocksRelaySelector: ShadowsocksRelaySelectorProtocol { public func getBridges() throws -> REST.ServerShadowsocks? { let cachedRelays = try relayCache.read() - return RelaySelector.Shadowsocks.tcpBridge(from: cachedRelays.relays) + return RelaySelector.Shadowsocks.tcpBridge(from: try cachedRelays.relays) } } diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 9bee09d01bce..3874f7550c8f 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -577,6 +577,7 @@ 7A9FA1422A2E3306000B728D /* CheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1412A2E3306000B728D /* CheckboxView.swift */; }; 7A9FA1442A2E3FE5000B728D /* CheckableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */; }; 7AA513862BC91C6B00D081A4 /* LogRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */; }; + 7AA7046A2C8EFE2B0045699D /* StoredRelays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA704682C8EFE050045699D /* StoredRelays.swift */; }; 7AB2B6702BA1EB8C00B03E3B /* ListCustomListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */; }; 7AB2B6712BA1EB8C00B03E3B /* ListCustomListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */; }; 7AB3BEB52BD7A6CB00E34384 /* LocationViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */; }; @@ -1888,6 +1889,7 @@ 7A9FA1412A2E3306000B728D /* CheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxView.swift; sourceTree = ""; }; 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckableSettingsCell.swift; sourceTree = ""; }; 7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRotationTests.swift; sourceTree = ""; }; + 7AA704682C8EFE050045699D /* StoredRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredRelays.swift; sourceTree = ""; }; 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListViewController.swift; sourceTree = ""; }; 7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListCoordinator.swift; sourceTree = ""; }; 7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewControllerWrapper.swift; sourceTree = ""; }; @@ -4159,6 +4161,7 @@ 7AEBA5292C2179F20018BEC5 /* RelaySelectorWrapper.swift */, F0B894F02BF751E300817A42 /* RelayWithDistance.swift */, F0B894EE2BF751C500817A42 /* RelayWithLocation.swift */, + 7AA704682C8EFE050045699D /* StoredRelays.swift */, ); path = Relay; sourceTree = ""; @@ -5190,6 +5193,7 @@ A90763B32B2857D50045ADF0 /* Socks5Authentication.swift in Sources */, 06799ADB28F98E4800ACD94E /* RESTProxyFactory.swift in Sources */, F0DDE4182B220458006B57A7 /* ShadowsocksConfiguration.swift in Sources */, + 7AA7046A2C8EFE2B0045699D /* StoredRelays.swift in Sources */, 06799AF228F98E4800ACD94E /* RESTAccessTokenManager.swift in Sources */, A90763B12B2857D50045ADF0 /* Socks5Endpoint.swift in Sources */, 06799AF328F98E4800ACD94E /* RESTAuthenticationProxy.swift in Sources */, diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift index 2d2d0d384ed0..27f1caa1fa02 100644 --- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift +++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift @@ -63,7 +63,7 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol { cache = relayCache do { - cachedRelays = try cache.read() + cachedRelays = try cache.read().cachedRelays try hotfixRelaysThatDoNotHaveDaita() } catch { logger.error( @@ -89,8 +89,8 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol { // If the cached relays already have daita information, this fix is not necessary guard daitaPropertyMissing else { return } - let preBundledRelays = try cache.readPrebundledRelays() - let preBundledDaitaRelays = preBundledRelays.relays.wireguard.relays.filter { $0.daita == true } + let preBundledRelays = try cache.readPrebundledRelays().relays + let preBundledDaitaRelays = preBundledRelays.wireguard.relays.filter { $0.daita == true } var cachedRelaysWithFixedDaita = cachedRelays.relays.wireguard.relays // For each daita enabled relay in the prebundled relays @@ -119,9 +119,19 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol { bridge: cachedRelays.relays.bridge ) - let updatedCachedRelays = CachedRelays(relays: updatedRelays, updatedAt: Date()) + let updatedRawRelayData = try REST.Coding.makeJSONEncoder().encode(updatedRelays) + let updatedCachedRelays = try StoredRelays( + etag: cachedRelays.etag, + rawData: updatedRawRelayData, + updatedAt: cachedRelays.updatedAt + ) + try cache.write(record: updatedCachedRelays) - self.cachedRelays = updatedCachedRelays + self.cachedRelays = CachedRelays( + etag: cachedRelays.etag, + relays: updatedRelays, + updatedAt: cachedRelays.updatedAt + ) } func startPeriodicUpdates() { @@ -196,7 +206,7 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol { } func refreshCachedRelays() throws { - let newCachedRelays = try cache.read() + let newCachedRelays = try cache.read().cachedRelays relayCacheLock.lock() cachedRelays = newCachedRelays @@ -244,8 +254,8 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol { -> Result { result.tryMap { response -> RelaysFetchResult in switch response { - case let .newContent(etag, relays): - try self.storeResponse(etag: etag, relays: relays) + case let .newContent(etag, rawData): + try self.storeResponse(etag: etag, rawData: rawData) return .newContent @@ -262,18 +272,14 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol { } } - private func storeResponse(etag: String?, relays: REST.ServerRelaysResponse) throws { - let numRelays = relays.wireguard.relays.count - - logger.info("Downloaded \(numRelays) relays.") - - let newCachedRelays = CachedRelays( + private func storeResponse(etag: String?, rawData: Data) throws { + let newCachedData = try StoredRelays( etag: etag, - relays: relays, + rawData: rawData, updatedAt: Date() ) - try cache.write(record: newCachedRelays) + try cache.write(record: newCachedData) try refreshCachedRelays() } diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/RelayCacheTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/RelayCacheTests.swift index fffd9e33830e..6d9699574216 100644 --- a/ios/MullvadVPNTests/MullvadREST/Relay/RelayCacheTests.swift +++ b/ios/MullvadVPNTests/MullvadREST/Relay/RelayCacheTests.swift @@ -12,7 +12,7 @@ import XCTest final class RelayCacheTests: XCTestCase { func testReadCache() throws { let fileCache = MockFileCache( - initialState: .exists(CachedRelays(relays: .mock(), updatedAt: .distantPast)) + initialState: .exists(try StoredRelays(rawData: try .mock(), updatedAt: .distantPast)) ) let cache = RelayCache(fileCache: fileCache) let relays = try XCTUnwrap(cache.read()) @@ -22,17 +22,17 @@ final class RelayCacheTests: XCTestCase { func testWriteCache() throws { let fileCache = MockFileCache( - initialState: .exists(CachedRelays(relays: .mock(), updatedAt: .distantPast)) + initialState: .exists(try StoredRelays(rawData: try .mock(), updatedAt: .distantPast)) ) let cache = RelayCache(fileCache: fileCache) - let newCachedRelays = CachedRelays(relays: .mock(), updatedAt: Date()) + let newCachedRelays = try StoredRelays(rawData: try .mock(), updatedAt: Date()) try cache.write(record: newCachedRelays) XCTAssertEqual(fileCache.getState(), .exists(newCachedRelays)) } func testReadPrebundledRelaysWhenNoCacheIsStored() throws { - let fileCache = MockFileCache(initialState: .fileNotFound) + let fileCache = MockFileCache(initialState: .fileNotFound) let cache = RelayCache(fileCache: fileCache) XCTAssertNoThrow(try cache.read()) @@ -42,7 +42,7 @@ final class RelayCacheTests: XCTestCase { extension REST.ServerRelaysResponse { static func mock( serverRelays: [REST.ServerRelay] = [], - brideRelays: [REST.BridgeRelay] = [] + bridgeRelays: [REST.BridgeRelay] = [] ) -> Self { REST.ServerRelaysResponse( locations: [:], @@ -52,7 +52,17 @@ extension REST.ServerRelaysResponse { portRanges: [], relays: serverRelays ), - bridge: REST.ServerBridges(shadowsocks: [], relays: brideRelays) + bridge: REST.ServerBridges(shadowsocks: [], relays: bridgeRelays) ) } } + +extension Data { + static func mock( + serverRelays: [REST.ServerRelay] = [], + bridgeRelays: [REST.BridgeRelay] = [] + ) throws -> Data { + let relays = REST.ServerRelaysResponse.mock(serverRelays: serverRelays, bridgeRelays: bridgeRelays) + return try REST.Coding.makeJSONEncoder().encode(relays) + } +} diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift index 2cd6b37c9c44..beca599c5ffc 100644 --- a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift +++ b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorWrapperTests.swift @@ -12,12 +12,6 @@ import XCTest class RelaySelectorWrapperTests: XCTestCase { - let fileCache = MockFileCache( - initialState: .exists(CachedRelays( - relays: ServerRelaysResponseStubs.sampleRelays, - updatedAt: .distantPast - )) - ) let multihopWithDaitaConstraints = RelayConstraints( entryLocations: .only(UserSelectedRelays(locations: [.country("es")])), // Relay with DAITA. exitLocations: .only(UserSelectedRelays(locations: [.country("us")])) @@ -37,7 +31,14 @@ class RelaySelectorWrapperTests: XCTestCase { ) var relayCache: RelayCache! - override func setUp() { + override func setUpWithError() throws { + let fileCache = MockFileCache( + initialState: .exists(try StoredRelays( + rawData: try REST.Coding.makeJSONEncoder().encode(ServerRelaysResponseStubs.sampleRelays), + updatedAt: .distantPast + )) + ) + relayCache = RelayCache(fileCache: fileCache) } diff --git a/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift b/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift index a5e0d742e6d5..c694f50f9004 100644 --- a/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift +++ b/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift @@ -19,7 +19,7 @@ final class IPOverrideWrapperTests: XCTestCase { ] let fileCache = MockFileCache( - initialState: .exists(CachedRelays(relays: .mock(serverRelays: relays), updatedAt: .distantPast)) + initialState: .exists(try StoredRelays(rawData: try .mock(serverRelays: relays), updatedAt: .distantPast)) ) let override = try IPOverride(hostname: "Host 1", ipv4Address: .loopback, ipv6Address: .broadcast) @@ -32,12 +32,12 @@ final class IPOverrideWrapperTests: XCTestCase { let storedCache = try overrideWrapper.read() // Assert that relay was overridden. - let host1 = storedCache.relays.wireguard.relays.first + let host1 = try storedCache.relays.wireguard.relays.first XCTAssertEqual(host1?.ipv4AddrIn, .loopback) XCTAssertEqual(host1?.ipv6AddrIn, .broadcast) // Assert that relay was NOT overridden. - let host2 = storedCache.relays.wireguard.relays.last + let host2 = try storedCache.relays.wireguard.relays.last XCTAssertEqual(host2?.ipv4AddrIn, .any) XCTAssertEqual(host2?.ipv6AddrIn, .any) } @@ -49,7 +49,7 @@ final class IPOverrideWrapperTests: XCTestCase { ] let fileCache = MockFileCache( - initialState: .exists(CachedRelays(relays: .mock(brideRelays: relays), updatedAt: .distantPast)) + initialState: .exists(try StoredRelays(rawData: try .mock(bridgeRelays: relays), updatedAt: .distantPast)) ) let override = try IPOverride(hostname: "Host 1", ipv4Address: .loopback, ipv6Address: .broadcast) @@ -62,11 +62,11 @@ final class IPOverrideWrapperTests: XCTestCase { let storedCache = try overrideWrapper.read() // Assert that relay was overridden. - let host1 = storedCache.relays.bridge.relays.first + let host1 = try storedCache.relays.bridge.relays.first XCTAssertEqual(host1?.ipv4AddrIn, .loopback) // Assert that relay was NOT overridden. - let host2 = storedCache.relays.bridge.relays.last + let host2 = try storedCache.relays.bridge.relays.last XCTAssertEqual(host2?.ipv4AddrIn, .any) } } diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift index 8c76b5431253..b33f6e5af237 100644 --- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift +++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift @@ -115,6 +115,7 @@ class TunnelManagerTests: XCTestCase { } /// This test verifies tunnel gets out of `blockedState` after constraints are satisfied. + // swiftlint:disable:next function_body_length func testExitBlockedStateAfterSatisfyingConstraints() async throws { let blockedExpectation = expectation(description: "Relay constraints aren't satisfied!") let connectedExpectation = expectation(description: "Connected!") diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift index f266a602a122..00a067342b64 100644 --- a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift +++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift @@ -209,7 +209,6 @@ final class PacketTunnelActorTests: XCTestCase { 3. The issue goes away on the second attempt to read settings. 4. An actor should transition through `.connecting` towards`.connected` state. */ - // swiftlint:disable:next function_body_length func testLockedDeviceErrorOnBoot() async throws { let initialStateExpectation = expectation(description: "Expect initial state") let errorStateExpectation = expectation(description: "Expect error state")