summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-08-25 12:20:29 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-08-25 12:20:29 +0200
commitc118b96b27c088565f403ea0673f65e0f2174666 (patch)
tree2745b5ba0011ca4f4b9a799002cec400a5f48e1a
parent082201f0e662b6c2811e5129519d07a527868ad7 (diff)
parent2130af52d6430f11a1ff2fa61d5ab748d4fd02b9 (diff)
downloadmullvadvpn-c118b96b27c088565f403ea0673f65e0f2174666.tar.xz
mullvadvpn-c118b96b27c088565f403ea0673f65e0f2174666.zip
Merge branch 'change-address-cache-schema'
-rw-r--r--ios/CHANGELOG.md5
-rw-r--r--ios/MullvadREST/AddressCache.swift102
-rw-r--r--ios/MullvadVPNTests/AddressCacheTests.swift29
3 files changed, 62 insertions, 74 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 5b2571b5fc..1bab652812 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -27,6 +27,11 @@ Line wrap the file at 100 chars. Th
- Allow deleting account in account view.
- Add new account flow
+### Fixed
+- Invalidate API IP address cache to fix connectivity issues for some of devices updating from
+ 2023.2 or earlier.
+
+
## [2023.3 - 2023-07-15]
### Added
- Add search functionality to location selection view.
diff --git a/ios/MullvadREST/AddressCache.swift b/ios/MullvadREST/AddressCache.swift
index da27798c9d..8db610fc48 100644
--- a/ios/MullvadREST/AddressCache.swift
+++ b/ios/MullvadREST/AddressCache.swift
@@ -16,10 +16,10 @@ extension REST {
private let logger = Logger(label: "AddressCache")
/// Disk cache.
- private let fileCache: any FileCacheProtocol<CachedAddresses>
+ private let fileCache: any FileCacheProtocol<StoredAddressCache>
/// Memory cache.
- private var cache: CachedAddresses = defaultCachedAddresses
+ private var cache: StoredAddressCache = defaultStoredCache
/// Lock used for synchronizing access to instance members.
private let cacheLock = NSLock()
@@ -27,10 +27,10 @@ extension REST {
/// Whether address cache can be written to.
private let canWriteToCache: Bool
- /// The default set of endpoints to use as a fallback mechanism
- private static let defaultCachedAddresses = CachedAddresses(
+ /// The default endpoint to use as a fallback mechanism.
+ private static let defaultStoredCache = StoredAddressCache(
updatedAt: Date(timeIntervalSince1970: 0),
- endpoints: [REST.defaultAPIEndpoint]
+ endpoint: REST.defaultAPIEndpoint
)
// MARK: - Public API
@@ -44,7 +44,7 @@ extension REST {
}
/// Initializer that accepts a file cache implementation and can be used in tests.
- init(canWriteToCache: Bool, fileCache: some FileCacheProtocol<CachedAddresses>) {
+ init(canWriteToCache: Bool, fileCache: some FileCacheProtocol<StoredAddressCache>) {
self.fileCache = fileCache
self.canWriteToCache = canWriteToCache
}
@@ -54,23 +54,19 @@ extension REST {
/// When running from the Network Extension, this method will read from the cache before returning.
/// - Returns: The latest available endpoint, or a default endpoint if no endpoints are available
public func getCurrentEndpoint() -> AnyIPEndpoint {
- cacheLock.lock()
- defer { cacheLock.unlock() }
- var currentEndpoint = cache.endpoints.first ?? REST.defaultAPIEndpoint
-
- // Reload from disk cache when in the Network Extension as there is no `AddressCacheTracker` running
- // there
- if canWriteToCache == false {
- do {
- cache = try fileCache.read()
- if let firstEndpoint = cache.endpoints.first {
- currentEndpoint = firstEndpoint
+ cacheLock.withLock {
+ // Reload from disk cache when in the Network Extension as there is no `AddressCacheTracker` running
+ // there
+ if canWriteToCache == false {
+ do {
+ cache = try fileCache.read()
+ } catch {
+ logger.error(error: error, message: "Failed to read address cache from disk.")
}
- } catch {
- logger.error(error: error, message: "Failed to read address cache from disk.")
}
+
+ return cache.endpoint
}
- return currentEndpoint
}
/// Updates the available endpoints to use
@@ -79,27 +75,20 @@ extension REST {
/// This method will only modify the on disk cache when running from the UI process.
/// - Parameter endpoints: The new endpoints to use for API requests
public func setEndpoints(_ endpoints: [AnyIPEndpoint]) {
- cacheLock.lock()
- defer { cacheLock.unlock() }
+ cacheLock.withLock {
+ guard let firstEndpoint = endpoints.first else { return }
- guard let firstEndpoint = endpoints.first else { return }
- if Set(cache.endpoints) == Set(endpoints) {
- cache.updatedAt = Date()
- } else {
- cache = CachedAddresses(
- updatedAt: Date(),
- endpoints: [firstEndpoint]
- )
- }
+ cache = StoredAddressCache(updatedAt: Date(), endpoint: firstEndpoint)
- guard canWriteToCache else { return }
- do {
- try fileCache.write(cache)
- } catch {
- logger.error(
- error: error,
- message: "Failed to write address cache after setting new endpoints."
- )
+ guard canWriteToCache else { return }
+ do {
+ try fileCache.write(cache)
+ } catch {
+ logger.error(
+ error: error,
+ message: "Failed to write address cache after setting new endpoints."
+ )
+ }
}
}
@@ -107,32 +96,37 @@ extension REST {
///
/// - Returns: The `Date` when the cache was last updated at
public func getLastUpdateDate() -> Date {
- cacheLock.lock()
- defer { cacheLock.unlock() }
-
- return cache.updatedAt
+ return cacheLock.withLock { cache.updatedAt }
}
- /// Initializes the cache by reading the a cached file on disk.
+ /// Initializes cache by reading it from file on disk.
///
/// If no cache file is present, a default API endpoint will be selected instead.
public func loadFromFile() {
- // The first time the application is ran, this statement will fail as there is no cache. This is fine.
- // The cache will be filled when either `getCurrentEndpoint` or `setEndpoints()` are called.
- do {
- cache = try fileCache.read()
- } catch {
- logger.debug("Initialized cache with default API endpoint.")
- cache = Self.defaultCachedAddresses
+ cacheLock.withLock {
+ // The first time the application is ran, this statement will fail as there is no cache. This is fine.
+ // The cache will be filled when either `getCurrentEndpoint()` or `setEndpoints()` are called.
+ do {
+ cache = try fileCache.read()
+ } catch {
+ // Log all errors except when file is not on disk.
+ if (error as? CocoaError)?.code != .fileReadNoSuchFile {
+ logger.error(error: error, message: "Failed to load address cache from disk.")
+ }
+
+ logger.debug("Initialized cache with default API endpoint.")
+ cache = Self.defaultStoredCache
+ }
}
}
}
- public struct CachedAddresses: Codable, Equatable {
+ /// Serializable address cache representation stored on disk.
+ struct StoredAddressCache: Codable, Equatable {
/// Date when the cached addresses were last updated.
var updatedAt: Date
- /// API endpoints.
- var endpoints: [AnyIPEndpoint]
+ /// API endpoint.
+ var endpoint: AnyIPEndpoint
}
}
diff --git a/ios/MullvadVPNTests/AddressCacheTests.swift b/ios/MullvadVPNTests/AddressCacheTests.swift
index d0e1d823b1..5a5dedfe1a 100644
--- a/ios/MullvadVPNTests/AddressCacheTests.swift
+++ b/ios/MullvadVPNTests/AddressCacheTests.swift
@@ -68,18 +68,17 @@ final class AddressCacheTests: XCTestCase {
let firstIPEndpoint = try XCTUnwrap(ipAddresses.first)
- let fileCache = MockFileCache<REST.CachedAddresses>()
+ let fileCache = MockFileCache<REST.StoredAddressCache>()
let addressCache = REST.AddressCache(canWriteToCache: true, fileCache: fileCache)
addressCache.setEndpoints(ipAddresses)
let fileState = fileCache.getState()
- XCTAssertTrue(fileState.isExists)
- guard case let .exists(cachedAddresses) = fileState else {
+ guard case let .exists(storedAddressCache) = fileState else {
XCTFail("State is expected to contain cached addresses.")
return
}
- XCTAssertEqual(cachedAddresses.endpoints.count, 1)
+ XCTAssertEqual(storedAddressCache.endpoint, firstIPEndpoint)
XCTAssertEqual(addressCache.getCurrentEndpoint(), firstIPEndpoint)
}
@@ -88,7 +87,7 @@ final class AddressCacheTests: XCTestCase {
let addressCache = REST.AddressCache(
canWriteToCache: true,
fileCache: MockFileCache(initialState: .exists(
- REST.CachedAddresses(updatedAt: fixedDate, endpoints: [apiEndpoint])
+ REST.StoredAddressCache(updatedAt: fixedDate, endpoint: apiEndpoint)
))
)
addressCache.loadFromFile()
@@ -98,7 +97,7 @@ final class AddressCacheTests: XCTestCase {
}
func testCacheWritesToDiskWhenSettingNewEndpoints() throws {
- let fileCache = MockFileCache<REST.CachedAddresses>()
+ let fileCache = MockFileCache<REST.StoredAddressCache>()
let addressCache = REST.AddressCache(canWriteToCache: true, fileCache: fileCache)
XCTAssertEqual(fileCache.getState(), .fileNotFound)
@@ -107,32 +106,22 @@ final class AddressCacheTests: XCTestCase {
let fileState = fileCache.getState()
XCTAssertTrue(fileState.isExists)
- guard case let .exists(cachedAddresses) = fileState else {
+ guard case let .exists(storedAddressCache) = fileState else {
XCTFail("State is expected to contain cached addresses.")
return
}
- XCTAssertEqual(cachedAddresses.endpoints, [addressCache.getCurrentEndpoint()])
- XCTAssertEqual(cachedAddresses.updatedAt, addressCache.getLastUpdateDate())
+ XCTAssertEqual(storedAddressCache.endpoint, addressCache.getCurrentEndpoint())
+ XCTAssertEqual(storedAddressCache.updatedAt, addressCache.getLastUpdateDate())
}
func testGetCurrentEndpointReadsFromCacheWhenReadOnly() throws {
let addressCache = REST.AddressCache(
canWriteToCache: false,
fileCache: MockFileCache(initialState: .exists(
- REST.CachedAddresses(updatedAt: Date(), endpoints: [apiEndpoint])
+ REST.StoredAddressCache(updatedAt: Date(), endpoint: apiEndpoint)
))
)
XCTAssertEqual(addressCache.getCurrentEndpoint(), apiEndpoint)
}
-
- func testGetCurrentEndpointHasDefaultEndpointIfCacheIsEmpty() throws {
- let addressCache = REST.AddressCache(
- canWriteToCache: false,
- fileCache: MockFileCache(initialState: .exists(REST.CachedAddresses(updatedAt: Date(), endpoints: [])))
- )
- addressCache.loadFromFile()
-
- XCTAssertEqual(addressCache.getCurrentEndpoint(), REST.defaultAPIEndpoint)
- }
}