diff options
| author | Emīls <emils@mullvad.net> | 2023-05-12 14:55:50 +0200 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2023-05-12 14:55:50 +0200 |
| commit | 26908beb88908f5b1d2fc39b7ef0f95e2ad0b5a3 (patch) | |
| tree | 8bd5ca47f322fe90537ef42f3bdd2e71f73b67a4 | |
| parent | bdd6590031354943f392326a1d951f26d42240b7 (diff) | |
| parent | a08ceebb971669bcd35992f744f98a4e0f5ecc05 (diff) | |
| download | mullvadvpn-26908beb88908f5b1d2fc39b7ef0f95e2ad0b5a3.tar.xz mullvadvpn-26908beb88908f5b1d2fc39b7ef0f95e2ad0b5a3.zip | |
Merge branch 'rework-address-cache-ios-149'
| -rw-r--r-- | ios/MullvadREST/AddressCache.swift | 313 | ||||
| -rw-r--r-- | ios/MullvadREST/RESTResponseHandler.swift | 1 | ||||
| -rw-r--r-- | ios/MullvadRESTTests/AddressCacheTests.swift | 235 | ||||
| -rw-r--r-- | ios/MullvadTypes/NSFileCoordinator+Extensions.swift | 51 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 32 | ||||
| -rw-r--r-- | ios/MullvadVPN/AppDelegate.swift | 8 | ||||
| -rw-r--r-- | ios/PacketTunnel/PacketTunnelProvider.swift | 7 | ||||
| -rwxr-xr-x | ios/rest-prebuild.sh | 22 |
8 files changed, 378 insertions, 291 deletions
diff --git a/ios/MullvadREST/AddressCache.swift b/ios/MullvadREST/AddressCache.swift index c18709f0ef..bc54f306b4 100644 --- a/ios/MullvadREST/AddressCache.swift +++ b/ios/MullvadREST/AddressCache.swift @@ -21,116 +21,89 @@ extension REST { /// Cache file location. private let cacheFileURL: URL - /// The location of pre-bundled address cache file. - private let prebundledCacheFileURL: URL - /// Lock used for synchronizing access to instance members. - private let nslock = NSLock() + private let cacheLock = NSLock() + + /// Whether address cache can be written to. + private let canWriteToCache: Bool - /// Whether address cache is in readonly mode. - private var isReadOnly: Bool + /// The name of the cache file on disk + internal static let cacheFileName = "api-ip-address.json" + /// The default set of endpoints to use as a fallback mechanism private static let defaultCachedAddresses = CachedAddresses( updatedAt: Date(timeIntervalSince1970: 0), endpoints: [REST.defaultAPIEndpoint] ) - /// Designated initializer. - public init?(securityGroupIdentifier: String, isReadOnly: Bool) { - let cacheFilename = "api-ip-address.json" + // MARK: - - guard let containerURL = FileManager.default.containerURL( - forSecurityApplicationGroupIdentifier: securityGroupIdentifier - ), let prebundledCacheFileURL = Bundle(for: AddressCache.self).url( - forResource: cacheFilename, - withExtension: nil - ) else { return nil } + // MARK: Public API - let cacheFileURL = containerURL.appendingPathComponent( - cacheFilename, + /// Designated initializer. + public init(canWriteToCache: Bool, cacheFolder: URL) { + let cacheFileURL = cacheFolder.appendingPathComponent( + Self.cacheFileName, isDirectory: false ) self.cacheFileURL = cacheFileURL - self.prebundledCacheFileURL = prebundledCacheFileURL - self.isReadOnly = isReadOnly + self.canWriteToCache = canWriteToCache initCache() } + /// Returns the latest available endpoint + /// + /// 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 { - nslock.lock() - defer { nslock.unlock() } - return cachedAddresses.endpoints.first! - } - - public func selectNextEndpoint(_ failedEndpoint: AnyIPEndpoint) -> AnyIPEndpoint { - nslock.lock() - defer { nslock.unlock() } - - var currentEndpoint = cachedAddresses.endpoints.first! - - guard failedEndpoint == currentEndpoint else { - return currentEndpoint - } - - cachedAddresses.endpoints.removeFirst() - cachedAddresses.endpoints.append(failedEndpoint) + cacheLock.lock() + defer { cacheLock.unlock() } + var currentEndpoint = cachedAddresses.endpoints.first ?? REST.defaultAPIEndpoint - if isReadOnly { - refreshAddresses() - } - - currentEndpoint = cachedAddresses.endpoints.first! - - logger.debug( - "Failed to communicate using \(failedEndpoint). Next endpoint: \(currentEndpoint)" - ) - - if !isReadOnly { + // Reload from disk cache when in the Network Extension as there is no `AddressCacheTracker` running + // there + if canWriteToCache == false { do { - try writeToDisk() + cachedAddresses = try readFromCache() + if let firstEndpoint = cachedAddresses.endpoints.first { + currentEndpoint = firstEndpoint + } } catch { - logger.error( - error: error, - message: "Failed to write address cache after selecting next endpoint." - ) + logger.error(error: error) } } - return currentEndpoint } - public func setEndpoints(_ endpoints: [AnyIPEndpoint]) { - nslock.lock() - defer { nslock.unlock() } + public func selectNextEndpoint(_ failedEndpoint: AnyIPEndpoint) -> AnyIPEndpoint { + // This function currently acts as a convoluted no-op. It will be soon deleted. + return getCurrentEndpoint() + } - guard !endpoints.isEmpty else { - return - } + /// Updates the available endpoints to use + /// + /// Only the first available endpoint is kept, the rest are discarded. + /// 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() } + guard let firstEndpoint = endpoints.first else { return } if Set(cachedAddresses.endpoints) == Set(endpoints) { cachedAddresses.updatedAt = Date() } else { - // Shuffle new endpoints - var newEndpoints = endpoints.shuffled() - - // Move current endpoint to the top of the list - let currentEndpoint = cachedAddresses.endpoints.first! - if let index = newEndpoints.firstIndex(of: currentEndpoint) { - newEndpoints.remove(at: index) - newEndpoints.insert(currentEndpoint, at: 0) - } - cachedAddresses = CachedAddresses( updatedAt: Date(), - endpoints: newEndpoints + endpoints: [firstEndpoint] ) } - if !isReadOnly { + if canWriteToCache { do { - try writeToDisk() + try writeToCache() } catch { logger.error( error: error, @@ -140,175 +113,61 @@ extension REST { } } + /// The `Date` when the cache was last updated at + /// + /// - Returns: The `Date` when the cache was last updated at public func getLastUpdateDate() -> Date { - nslock.lock() - defer { nslock.unlock() } + cacheLock.lock() + defer { cacheLock.unlock() } return cachedAddresses.updatedAt } - // MARK: - Private + // MARK: - Private API + /// Initializes the cache by reading the a cached file from disk + /// + /// If no cache file is present, a default API endpoint will be selected instead private func initCache() { + // 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 { - try initCacheInner() + cachedAddresses = try readFromCache() } catch { logger.debug("Initialized cache with default API endpoint.") - cachedAddresses = Self.defaultCachedAddresses } } - private func initCacheInner() throws { - let readResult = try readFromCacheLocationWithFallback() - - switch readResult.source { - case .disk: - cachedAddresses = readResult.cachedAddresses - - case .bundle: - var addresses = readResult.cachedAddresses - addresses.endpoints.shuffle() - cachedAddresses = addresses - - if !isReadOnly { - logger.debug("Persist address list read from bundle.") - - do { - try writeToDisk() - } catch { - logger.error( - error: error, - message: "Failed to persist address cache after reading it from bundle." - ) - } - } - } - - logger.debug( - """ - Initialized cache from \(readResult.source) with \ - \(cachedAddresses.endpoints.count) endpoint(s). - """ - ) - } - - private func readFromCacheLocationWithFallback() throws -> ReadResult { - do { - return try readFromCacheLocation() - } catch { - logger.error( - error: error, - message: "Failed to read address cache from disk. Fallback to pre-bundled cache." - ) - - do { - return try readFromBundle() - } catch { - logger.error( - error: error, - message: "Failed to read address cache from bundle." - ) - - throw error - } - } - } - - private func readFromCacheLocation() throws -> ReadResult { - var result: Result<ReadResult, Swift.Error>? + /// Reads the cache file from disk + /// + /// - Returns: A list of cached API endpoints in a `CachedAddresses` form + private func readFromCache() throws -> CachedAddresses { let fileCoordinator = NSFileCoordinator(filePresenter: nil) - let accessor = { (fileURL: URL) in - result = Result { - let data = try Data(contentsOf: fileURL) + let result = try fileCoordinator + .coordinate(readingItemAt: cacheFileURL, options: [.withoutChanges]) { file in + let data = try Data(contentsOf: file) let cachedAddresses = try JSONDecoder().decode(CachedAddresses.self, from: data) if cachedAddresses.endpoints.isEmpty { - throw EmptyCacheError(source: .disk) + throw EmptyCacheError() } - return ReadResult(cachedAddresses: cachedAddresses, source: .disk) + return cachedAddresses } - } - - var error: NSError? - fileCoordinator.coordinate( - readingItemAt: cacheFileURL, - options: .withoutChanges, - error: &error, - byAccessor: accessor - ) - if let error = error { - result = .failure(error) - } - - return try result!.get() + return result } - private func readFromBundle() throws -> ReadResult { - let data = try Data(contentsOf: prebundledCacheFileURL) - let endpoints = try JSONDecoder().decode([AnyIPEndpoint].self, from: data) - - let cachedAddresses = CachedAddresses( - updatedAt: Date(timeIntervalSince1970: 0), - endpoints: endpoints - ) - - if cachedAddresses.endpoints.isEmpty { - throw EmptyCacheError(source: .bundle) - } - - return ReadResult(cachedAddresses: cachedAddresses, source: .bundle) - } - - private func writeToDisk() throws { - precondition(!isReadOnly) - - var result: Result<Void, Swift.Error>? + /// Writes the cache file to the disk + private func writeToCache() throws { + precondition(canWriteToCache == true) let fileCoordinator = NSFileCoordinator(filePresenter: nil) - let accessor = { (fileURL: URL) in - result = Result { - let data = try JSONEncoder().encode(self.cachedAddresses) - try data.write(to: fileURL) - } - } - - var error: NSError? - fileCoordinator.coordinate( - writingItemAt: cacheFileURL, - options: [.forReplacing], - error: &error, - byAccessor: accessor - ) - - if let error = error { - result = .failure(error) - } - - return try result!.get() - } - - private func refreshAddresses() { - do { - let readResult = try readFromCacheLocation() - var newCachedAddresses = readResult.cachedAddresses - - guard Set(newCachedAddresses.endpoints) != Set(cachedAddresses.endpoints) - else { return } - - // Move current endpoint to the top of the list - let currentEndpoint = cachedAddresses.endpoints.first! - if let index = newCachedAddresses.endpoints.firstIndex(of: currentEndpoint) { - newCachedAddresses.endpoints.remove(at: index) - newCachedAddresses.endpoints.insert(currentEndpoint, at: 0) - } - - cachedAddresses = newCachedAddresses - } catch { - logger.error(error: error, message: "Failed to refresh address cache from disk.") + try fileCoordinator.coordinate(writingItemAt: cacheFileURL, options: [.forReplacing]) { file in + let data = try JSONEncoder().encode(self.cachedAddresses) + try data.write(to: file) } } } @@ -321,33 +180,9 @@ extension REST { var endpoints: [AnyIPEndpoint] } - enum CacheSource: CustomStringConvertible { - /// Cache file originates from disk location. - case disk - - /// Cache file originates from application bundle. - case bundle - - var description: String { - switch self { - case .disk: - return "disk" - case .bundle: - return "bundle" - } - } - } - - struct ReadResult { - var cachedAddresses: CachedAddresses - var source: CacheSource - } - struct EmptyCacheError: LocalizedError { - let source: CacheSource - var errorDescription: String? { - return "Address cache file from \(source) does not contain any API addresses." + return "Address cache file does not contain any API addresses." } } } diff --git a/ios/MullvadREST/RESTResponseHandler.swift b/ios/MullvadREST/RESTResponseHandler.swift index 1a33931f31..fd3b7d2e2b 100644 --- a/ios/MullvadREST/RESTResponseHandler.swift +++ b/ios/MullvadREST/RESTResponseHandler.swift @@ -7,6 +7,7 @@ // import Foundation +import MullvadTypes protocol RESTResponseHandler { associatedtype Success diff --git a/ios/MullvadRESTTests/AddressCacheTests.swift b/ios/MullvadRESTTests/AddressCacheTests.swift new file mode 100644 index 0000000000..85387adee7 --- /dev/null +++ b/ios/MullvadRESTTests/AddressCacheTests.swift @@ -0,0 +1,235 @@ +// +// AddressCacheTests.swift +// MullvadRESTTests +// +// Created by Marco Nikic on 2023-05-05. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +@testable import MullvadREST +import MullvadTypes +import XCTest + +final class AddressCacheTests: XCTestCase { + static var testsCacheDirectory: URL! + var apiEndpoint: AnyIPEndpoint! + var cacheFilePresenter: AddressCacheFilePresenter! + let defaultExpectationTimeout = REST.Duration.milliseconds(200).timeInterval + + // MARK: Tests Setup + + override class func setUp() { + super.setUp() + let temporaryDirectory = FileManager.default.temporaryDirectory + testsCacheDirectory = temporaryDirectory.appendingPathComponent("AddressCacheTests") + } + + override func setUpWithError() throws { + try super.setUpWithError() + apiEndpoint = try XCTUnwrap(AnyIPEndpoint(string: "127.0.0.1:80")) + let cacheFileURL = Self.testsCacheDirectory.appendingPathComponent(REST.AddressCache.cacheFileName) + cacheFilePresenter = AddressCacheFilePresenter(presentedItemURL: cacheFileURL) + NSFileCoordinator.addFilePresenter(cacheFilePresenter) + } + + override func tearDownWithError() throws { + NSFileCoordinator.removeFilePresenter(cacheFilePresenter) + try super.tearDownWithError() + } + + // MARK: - + + // MARK: Tests + + func testAddressCacheHasDefaultEndpoint() { + let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: Self.testsCacheDirectory) + XCTAssertEqual(cache.getCurrentEndpoint(), REST.defaultAPIEndpoint) + } + + func testSetEndpoints() throws { + let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: Self.testsCacheDirectory) + + cache.setEndpoints([apiEndpoint]) + XCTAssertEqual(cache.getCurrentEndpoint(), apiEndpoint) + } + + func testSetEndpointsUpdatesDateWhenSettingSameAddress() throws { + let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: Self.testsCacheDirectory) + cache.setEndpoints([apiEndpoint]) + + let dateBeforeSettingEndpoint = Date() + cache.setEndpoints([apiEndpoint]) + let dateAfterSettingEndpoint = Date() + + let dateIntervalRange = dateBeforeSettingEndpoint ... dateAfterSettingEndpoint + XCTAssertTrue(dateIntervalRange.contains(cache.getLastUpdateDate())) + } + + func testSetEndpointsDoesNotDoAnythingIfSettingEmptyEndpoints() throws { + let didNotWriteToCache = expectation(description: "Did not write to cache") + didNotWriteToCache.isInverted = true + + cacheFilePresenter.onWriterAction = { + didNotWriteToCache.fulfill() + } + + try withCachefolders { cacheDirectory, _ in + let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory) + cache.setEndpoints([]) + } + + waitForExpectations(timeout: defaultExpectationTimeout) + } + + func testSetEndpointsOnlyAcceptsTheFirstEndpoint() throws { + let ipAddresses = (1 ... 10) + .map { "\($0).\($0).\($0).\($0):80" } + .compactMap { AnyIPEndpoint(string: $0) } + + let firstIPEndpoint = try XCTUnwrap(ipAddresses.first) + + try withCachefolders { cacheDirectory, cacheFileURL in + let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory) + cache.setEndpoints(ipAddresses) + + let cachedContent = try Data(contentsOf: cacheFileURL) + let cachedAddresses = try JSONDecoder().decode(REST.CachedAddresses.self, from: cachedContent) + + XCTAssertEqual(cachedAddresses.endpoints.count, 1) + XCTAssertEqual(cache.getCurrentEndpoint(), firstIPEndpoint) + } + } + + func testCacheReadsFromCachedFileAtInit() throws { + let didReadFromCache = expectation(description: "Cache was read") + cacheFilePresenter.onReaderAction = { + didReadFromCache.fulfill() + } + + try withCachefolders { cacheDirectory, cacheFileURL in + let fixedDate = Date() + try prepopulateCache(at: cacheFileURL, fixedDate: fixedDate, with: [apiEndpoint]) + let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory) + + XCTAssertEqual(cache.getCurrentEndpoint(), apiEndpoint) + XCTAssertEqual(cache.getLastUpdateDate(), fixedDate) + } + + waitForExpectations(timeout: defaultExpectationTimeout) + } + + func testCacheWritesToDiskWhenSettingNewEndpoints() throws { + let didWriteToCache = expectation(description: "Cache was written to") + cacheFilePresenter.onWriterAction = { + didWriteToCache.fulfill() + } + + try withCachefolders { cacheDirectory, cacheFileURL in + + let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory) + cache.setEndpoints([apiEndpoint]) + let cachedContent = try Data(contentsOf: cacheFileURL) + let cachedAddresses = try JSONDecoder().decode(REST.CachedAddresses.self, from: cachedContent) + let cachedAddress = try XCTUnwrap(cachedAddresses.endpoints.first) + + XCTAssertEqual(cachedAddress, cache.getCurrentEndpoint()) + XCTAssertEqual(cachedAddresses.updatedAt, cache.getLastUpdateDate()) + } + + waitForExpectations(timeout: defaultExpectationTimeout) + } + + func testGetCurrentEndpointReadsFromCacheWhenReadOnly() throws { + let didReadFromCache = expectation(description: "Cache was read") + // Cache will be read from twice. Once during init, once when getting current endpoint + didReadFromCache.expectedFulfillmentCount = 2 + cacheFilePresenter.onReaderAction = { + didReadFromCache.fulfill() + } + + try withCachefolders { cacheDirectory, cacheFileURL in + let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: cacheDirectory) + try prepopulateCache(at: cacheFileURL, with: [apiEndpoint]) + + XCTAssertEqual(cache.getCurrentEndpoint(), apiEndpoint) + } + + waitForExpectations(timeout: defaultExpectationTimeout) + } + + func testGetCurrentEndpointHasDefaultEndpointIfCacheIsEmpty() throws { + let didReadFromCache = expectation(description: "Cache was read") + // Cache will be read from twice. Once during init, once when getting current endpoint + didReadFromCache.expectedFulfillmentCount = 2 + cacheFilePresenter.onReaderAction = { + didReadFromCache.fulfill() + } + + try withCachefolders { cacheDirectory, cacheFileURL in + try prepopulateCache(at: cacheFileURL, with: []) + + let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: cacheDirectory) + XCTAssertEqual(cache.getCurrentEndpoint(), REST.defaultAPIEndpoint) + } + + waitForExpectations(timeout: defaultExpectationTimeout) + } +} + +// MARK: - + +extension AddressCacheTests { + /// Prepares a cache folder that is expected to be present during the `runTest` closure + /// - Parameter runTest: A closure that expects a `cacheDirectory` encapsulating `cacheFileURL` to be present when + /// it runs + func withCachefolders(_ runTest: (_ cacheDirectory: URL, _ cacheFileURL: URL) throws -> Void) throws { + let cacheFileURL = try XCTUnwrap(cacheFilePresenter.presentedItemURL) + let fileManager = FileManager.default + let cacheDirectory = try XCTUnwrap(Self.testsCacheDirectory) + try fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true) + + try runTest(cacheDirectory, cacheFileURL) + + try fileManager.removeItem(at: cacheDirectory) + } + + /// Populates a JSON cache file containing a `Date` and `[AnyIPEndpoint]` + /// + /// - Parameters: + /// - cacheFileURL: The cache file destination + /// - fixedDate: The `Date` the cache file was written to + /// - endpoints: A list of `AnyIPEndpoint` to write in the cache + func prepopulateCache(at cacheFileURL: URL, fixedDate: Date = Date(), with endpoints: [AnyIPEndpoint]) throws { + let prepopulatedCache = REST.CachedAddresses(updatedAt: fixedDate, endpoints: endpoints) + let encodedCache = try JSONEncoder().encode(prepopulatedCache) + try encodedCache.write(to: cacheFileURL) + } +} + +class AddressCacheFilePresenter: NSObject, NSFilePresenter { + var presentedItemURL: URL? + let operationQueue: OperationQueue + let dispatchQueue = DispatchQueue(label: "com.MullvadVPN.AddressCacheTests") + var presentedItemOperationQueue: OperationQueue { operationQueue } + + var onReaderAction: (() -> Void)? + var onWriterAction: (() -> Void)? + + init(presentedItemURL: URL) { + operationQueue = OperationQueue() + self.presentedItemURL = presentedItemURL + operationQueue.underlyingQueue = dispatchQueue + } + + func relinquishPresentedItem(toReader reader: @escaping ((() -> Void)?) -> Void) { + print(#function) + onReaderAction?() + reader(nil) + } + + func relinquishPresentedItem(toWriter writer: @escaping ((() -> Void)?) -> Void) { + print(#function) + onWriterAction?() + writer(nil) + } +} diff --git a/ios/MullvadTypes/NSFileCoordinator+Extensions.swift b/ios/MullvadTypes/NSFileCoordinator+Extensions.swift new file mode 100644 index 0000000000..242381ccb4 --- /dev/null +++ b/ios/MullvadTypes/NSFileCoordinator+Extensions.swift @@ -0,0 +1,51 @@ +// +// NSFileCoordinator+Extensions.swift +// MullvadTypes +// +// Created by Marco Nikic on 2023-05-11. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +extension NSFileCoordinator { + public func coordinate<R>( + readingItemAt itemURL: URL, + options: ReadingOptions = [], + accessor: (URL) throws -> R + ) throws -> R { + var error: NSError? + var result: Result<R, Error> = .failure(CocoaError(.fileReadUnknown)) + + coordinate(readingItemAt: itemURL, options: options, error: &error) { url in + result = Result { try accessor(url) } + } + + if let error { + throw error + } + + return try result.get() + } + + public func coordinate( + writingItemAt itemURL: URL, + options: WritingOptions = [], + accessor: (URL) throws -> Void + ) throws { + var error: NSError? + var accessorError: Error? + + coordinate(writingItemAt: itemURL, options: options, error: &error) { url in + do { + try accessor(url) + } catch { + accessorError = error + } + } + + if let e = error ?? accessorError { + throw e + } + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index c687145711..3bab2cdcf9 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 062B45A328FD4CA700746E77 /* le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 06799AB428F98CE700ACD94E /* le_root_cert.cer */; }; 062B45AE28FD503000746E77 /* WireGuardKit in Frameworks */ = {isa = PBXBuildFile; productRef = 062B45AD28FD503000746E77 /* WireGuardKit */; }; 062B45BC28FD8C3B00746E77 /* RESTDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B45BB28FD8C3B00746E77 /* RESTDefaults.swift */; }; - 062B45C228FE980000746E77 /* api-ip-address.json in Resources */ = {isa = PBXBuildFile; fileRef = 062B45C128FE97FF00746E77 /* api-ip-address.json */; }; 063687BA28EB234F00BE7161 /* PacketTunnelTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063687B928EB234F00BE7161 /* PacketTunnelTransport.swift */; }; 063F026628FFE11C001FA09F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67828F83CA50033DD93 /* RESTCreateApplePaymentResponse+Localization.swift */; }; 063F02762902B63F001FA09F /* RelayCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 063F02752902B63F001FA09F /* RelayCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -368,6 +367,8 @@ 7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */; }; 7AD2DA1529DC4EB900250737 /* UISearchBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */; }; 7AF0419E29E957EB00D492DD /* AccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */; }; + A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */; }; + A9CF11FD2A0518E7001D9565 /* AddressCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */; }; E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; }; E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */; }; E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; }; @@ -631,7 +632,6 @@ /* Begin PBXFileReference section */ 062B45BB28FD8C3B00746E77 /* RESTDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTDefaults.swift; sourceTree = "<group>"; }; - 062B45C128FE97FF00746E77 /* api-ip-address.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "api-ip-address.json"; sourceTree = "<group>"; }; 063687AF28EB083800BE7161 /* ProxyURLRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyURLRequest.swift; sourceTree = "<group>"; }; 063687B928EB234F00BE7161 /* PacketTunnelTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelTransport.swift; sourceTree = "<group>"; }; 063F02732902B63F001FA09F /* RelayCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RelayCache.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -969,6 +969,8 @@ 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootConfiguration.swift; sourceTree = "<group>"; }; 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Appearance.swift"; sourceTree = "<group>"; }; 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCoordinator.swift; sourceTree = "<group>"; }; + A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFileCoordinator+Extensions.swift"; sourceTree = "<group>"; }; + A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = "<group>"; }; E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; }; E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeContentView.swift; sourceTree = "<group>"; }; E158B35F285381C60002F069 /* String+AccountFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AccountFormatting.swift"; sourceTree = "<group>"; }; @@ -1104,7 +1106,6 @@ 062B45A228FD4C0F00746E77 /* Assets */ = { isa = PBXGroup; children = ( - 062B45C128FE97FF00746E77 /* api-ip-address.json */, 06799AB428F98CE700ACD94E /* le_root_cert.cer */, ); path = Assets; @@ -1208,6 +1209,7 @@ 58A1AA8623F43901009F7EA6 /* Location.swift */, 5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */, 58D223D7294C8E5E0029F5F8 /* MullvadTypes.h */, + A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */, 06410E172934F43B00AFC18C /* PacketTunnelErrorWrapper.swift */, 5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */, 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */, @@ -1935,6 +1937,7 @@ 58FBFBE7291622580020E046 /* MullvadRESTTests */ = { isa = PBXGroup; children = ( + A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */, 58FBFBF0291630700020E046 /* DurationTests.swift */, 58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */, ); @@ -2031,7 +2034,6 @@ isa = PBXNativeTarget; buildConfigurationList = 06799AD328F98E1D00ACD94E /* Build configuration list for PBXNativeTarget "MullvadREST" */; buildPhases = ( - 588E4EB028FEF1CA008046E3 /* Run prebuild script */, 06799AB728F98E1D00ACD94E /* Headers */, 06799AB828F98E1D00ACD94E /* Sources */, 06799AB928F98E1D00ACD94E /* Frameworks */, @@ -2400,7 +2402,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 062B45C228FE980000746E77 /* api-ip-address.json in Resources */, 062B45A328FD4CA700746E77 /* le_root_cert.cer in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2495,25 +2496,6 @@ shellPath = /bin/sh; shellScript = "exec > $PROJECT_DIR/relays-prebuild.log 2>&1\n\n$PROJECT_DIR/relays-prebuild.sh\n"; }; - 588E4EB028FEF1CA008046E3 /* Run prebuild script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run prebuild script"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "exec > $PROJECT_DIR/rest-prebuild.log 2>&1\n\n$PROJECT_DIR/rest-prebuild.sh\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -2881,6 +2863,7 @@ 58D22406294C90210029F5F8 /* IPv4Endpoint.swift in Sources */, 58D22407294C90210029F5F8 /* IPv6Endpoint.swift in Sources */, 58CAFA032985367600BE19F7 /* Promise.swift in Sources */, + A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */, 58D22408294C90210029F5F8 /* AnyIPEndpoint.swift in Sources */, 58D22409294C90210029F5F8 /* AnyIPAddress.swift in Sources */, 58D2240A294C90210029F5F8 /* IPAddress+Codable.swift in Sources */, @@ -2922,6 +2905,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A9CF11FD2A0518E7001D9565 /* AddressCacheTests.swift in Sources */, 58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */, 58FBFBF1291630700020E046 /* DurationTests.swift in Sources */, ); diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 748742ee76..26ac51cccf 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -51,10 +51,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD logger = Logger(label: "AppDelegate") + let containerURL = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier)! + addressCache = REST.AddressCache( - securityGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier, - isReadOnly: false - )! + canWriteToCache: true, cacheFolder: containerURL + ) proxyFactory = REST.ProxyFactory.makeProxyFactory( transportProvider: { [weak self] in diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift index 52220cdf49..4a0a341aba 100644 --- a/ios/PacketTunnel/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider.swift @@ -144,10 +144,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { providerLogger = Logger(label: "PacketTunnelProvider") tunnelLogger = Logger(label: "WireGuard") + let containerURL = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier)! let addressCache = REST.AddressCache( - securityGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier, - isReadOnly: true - )! + canWriteToCache: false, cacheFolder: containerURL + ) let urlSession = REST.makeURLSession() let urlSessionTransport = REST.URLSessionTransport(urlSession: urlSession) diff --git a/ios/rest-prebuild.sh b/ios/rest-prebuild.sh deleted file mode 100755 index 59fde4aa56..0000000000 --- a/ios/rest-prebuild.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -if [ -z "$PROJECT_DIR" ]; then - echo "This script is intended to be executed by Xcode" - exit 1 -fi - -API_IP_ADDRESS_LIST_FILE="$PROJECT_DIR/MullvadREST/Assets/api-ip-address.json" - -if [ $CONFIGURATION == "Release" ]; then - echo "Remove API address list file" - if [ -f "$API_IP_ADDRESS_LIST_FILE" ]; then - rm "$API_IP_ADDRESS_LIST_FILE" - else - echo "API IP address list file does not exist" - fi -fi - -if [ ! -f "$API_IP_ADDRESS_LIST_FILE" ]; then - echo "Download API address list" - curl https://api.mullvad.net/app/v1/api-addrs -s -o "$API_IP_ADDRESS_LIST_FILE" -fi |
