summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadREST/AddressCache.swift76
-rw-r--r--ios/MullvadREST/ServerRelaysResponse.swift14
-rw-r--r--ios/MullvadTransport/ShadowsocksConfiguration.swift (renamed from ios/MullvadTypes/ShadowsocksConfiguration.swift)0
-rw-r--r--ios/MullvadTransport/ShadowsocksConfigurationCache.swift47
-rw-r--r--ios/MullvadTransport/TransportProvider.swift34
-rw-r--r--ios/MullvadTransport/URLSessionShadowsocksTransport.swift65
-rw-r--r--ios/MullvadTransport/URLSessionTransport.swift53
-rw-r--r--ios/MullvadTypes/Cache.swift48
-rw-r--r--ios/MullvadTypes/FileCache.swift42
-rw-r--r--ios/MullvadTypes/ShadowsocksConfigurationCache.swift57
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj45
-rw-r--r--ios/MullvadVPN/AppDelegate.swift8
-rw-r--r--ios/MullvadVPNTests/AddressCacheTests.swift189
-rw-r--r--ios/MullvadVPNTests/CachedTests.swift56
-rw-r--r--ios/MullvadVPNTests/FileCacheTests.swift43
-rw-r--r--ios/MullvadVPNTests/MockFileCache.swift63
-rw-r--r--ios/MullvadVPNTests/RelayCacheTests.swift70
-rw-r--r--ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift34
-rw-r--r--ios/MullvadVPNTests/TestsCacheFilePresenter.swift35
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift8
-rw-r--r--ios/RelayCache/CachedRelays.swift2
-rw-r--r--ios/RelayCache/RelayCache.swift32
22 files changed, 481 insertions, 540 deletions
diff --git a/ios/MullvadREST/AddressCache.swift b/ios/MullvadREST/AddressCache.swift
index 0f449f58ff..8279b1c263 100644
--- a/ios/MullvadREST/AddressCache.swift
+++ b/ios/MullvadREST/AddressCache.swift
@@ -11,24 +11,15 @@ import MullvadLogging
import MullvadTypes
extension REST {
- public struct CachedAddresses: Codable {
- /// Date when the cached addresses were last updated.
- var updatedAt: Date
-
- /// API endpoints.
- var endpoints: [AnyIPEndpoint]
- }
-
- public final class AddressCache: Caching {
- public typealias CacheType = CachedAddresses
+ public final class AddressCache {
/// Logger.
private let logger = Logger(label: "AddressCache")
- /// Memory cache.
- var cache: CachedAddresses = defaultCachedAddresses
+ /// Disk cache.
+ private let fileCache: any FileCacheProtocol<CachedAddresses>
- /// Cache file location.
- public let cacheFileURL: URL
+ /// Memory cache.
+ private var cache: CachedAddresses = defaultCachedAddresses
/// Lock used for synchronizing access to instance members.
private let cacheLock = NSLock()
@@ -36,27 +27,25 @@ extension REST {
/// Whether address cache can be written to.
private let canWriteToCache: Bool
- /// The name of the cache file on disk
- public 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]
)
- // MARK: -
-
- // MARK: Public API
+ // MARK: - Public API
/// Designated initializer.
- public init(canWriteToCache: Bool, cacheFolder: URL) {
- let cacheFileURL = cacheFolder.appendingPathComponent(
- Self.cacheFileName,
- isDirectory: false
+ public init(canWriteToCache: Bool, cacheDirectory: URL) {
+ fileCache = FileCache(
+ fileURL: cacheDirectory.appendingPathComponent("api-ip-address.json", isDirectory: false)
)
+ self.canWriteToCache = canWriteToCache
+ }
- self.cacheFileURL = cacheFileURL
+ /// Initializer that accepts a file cache implementation and can be used in tests.
+ init(canWriteToCache: Bool, fileCache: some FileCacheProtocol<CachedAddresses>) {
+ self.fileCache = fileCache
self.canWriteToCache = canWriteToCache
}
@@ -73,12 +62,12 @@ extension REST {
// there
if canWriteToCache == false {
do {
- cache = try readFromDisk()
+ cache = try fileCache.read()
if let firstEndpoint = cache.endpoints.first {
currentEndpoint = firstEndpoint
}
} catch {
- logger.error(error: error)
+ logger.error(error: error, message: "Failed to read address cache from disk.")
}
}
return currentEndpoint
@@ -108,15 +97,14 @@ extension REST {
)
}
- if canWriteToCache {
- do {
- try writeToDisk(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."
+ )
}
}
@@ -130,18 +118,26 @@ extension REST {
return cache.updatedAt
}
- /// Initializes the cache by reading the a cached file from disk
+ /// Initializes the cache by reading the a cached file on disk.
///
- /// If no cache file is present, a default API endpoint will be selected instead
- public func initCache() {
+ /// 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 readFromDisk()
+ cache = try fileCache.read()
} catch {
logger.debug("Initialized cache with default API endpoint.")
cache = Self.defaultCachedAddresses
}
}
}
+
+ public struct CachedAddresses: Codable, Equatable {
+ /// Date when the cached addresses were last updated.
+ var updatedAt: Date
+
+ /// API endpoints.
+ var endpoints: [AnyIPEndpoint]
+ }
}
diff --git a/ios/MullvadREST/ServerRelaysResponse.swift b/ios/MullvadREST/ServerRelaysResponse.swift
index d91d05baee..081802196b 100644
--- a/ios/MullvadREST/ServerRelaysResponse.swift
+++ b/ios/MullvadREST/ServerRelaysResponse.swift
@@ -12,7 +12,7 @@ import struct Network.IPv4Address
import struct Network.IPv6Address
extension REST {
- public struct ServerLocation: Codable {
+ public struct ServerLocation: Codable, Equatable {
public let country: String
public let city: String
public let latitude: Double
@@ -26,7 +26,7 @@ extension REST {
}
}
- public struct BridgeRelay: Codable {
+ public struct BridgeRelay: Codable, Equatable {
public let hostname: String
public let active: Bool
public let owned: Bool
@@ -37,7 +37,7 @@ extension REST {
public let includeInCountry: Bool
}
- public struct ServerRelay: Codable {
+ public struct ServerRelay: Codable, Equatable {
public let hostname: String
public let active: Bool
public let owned: Bool
@@ -74,7 +74,7 @@ extension REST {
}
}
- public struct ServerWireguardTunnels: Codable {
+ public struct ServerWireguardTunnels: Codable, Equatable {
public let ipv4Gateway: IPv4Address
public let ipv6Gateway: IPv6Address
public let portRanges: [[UInt16]]
@@ -93,7 +93,7 @@ extension REST {
}
}
- public struct ServerShadowsocks: Codable {
+ public struct ServerShadowsocks: Codable, Equatable {
public let `protocol`: String
public let port: UInt16
public let cipher: String
@@ -107,7 +107,7 @@ extension REST {
}
}
- public struct ServerBridges: Codable {
+ public struct ServerBridges: Codable, Equatable {
public let shadowsocks: [ServerShadowsocks]
public let relays: [BridgeRelay]
@@ -117,7 +117,7 @@ extension REST {
}
}
- public struct ServerRelaysResponse: Codable {
+ public struct ServerRelaysResponse: Codable, Equatable {
public let locations: [String: ServerLocation]
public let wireguard: ServerWireguardTunnels
public let bridge: ServerBridges
diff --git a/ios/MullvadTypes/ShadowsocksConfiguration.swift b/ios/MullvadTransport/ShadowsocksConfiguration.swift
index 0a145f0976..0a145f0976 100644
--- a/ios/MullvadTypes/ShadowsocksConfiguration.swift
+++ b/ios/MullvadTransport/ShadowsocksConfiguration.swift
diff --git a/ios/MullvadTransport/ShadowsocksConfigurationCache.swift b/ios/MullvadTransport/ShadowsocksConfigurationCache.swift
new file mode 100644
index 0000000000..111215138e
--- /dev/null
+++ b/ios/MullvadTransport/ShadowsocksConfigurationCache.swift
@@ -0,0 +1,47 @@
+//
+// ShadowsocksConfigurationCache.swift
+// MullvadTransport
+//
+// Created by Marco Nikic on 2023-06-05.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadTypes
+
+/// Holds a shadowsocks configuration object backed by a caching mechanism shared across processes
+public final class ShadowsocksConfigurationCache {
+ private let configurationLock = NSLock()
+ private var cachedConfiguration: ShadowsocksConfiguration?
+ private let fileCache: FileCache<ShadowsocksConfiguration>
+
+ public init(cacheDirectory: URL) {
+ fileCache = FileCache(
+ fileURL: cacheDirectory.appendingPathComponent("shadowsocks-cache.json", isDirectory: false)
+ )
+ }
+
+ /// Returns configration from memory cache if available, otherwise attempts to load it from disk cache before
+ /// returning.
+ public func read() throws -> ShadowsocksConfiguration {
+ configurationLock.lock()
+ defer { configurationLock.unlock() }
+
+ if let cachedConfiguration {
+ return cachedConfiguration
+ } else {
+ let readConfiguration = try fileCache.read()
+ cachedConfiguration = readConfiguration
+ return readConfiguration
+ }
+ }
+
+ /// Replace memory cache with new configuration and attempt to persist it on disk.
+ public func write(_ configuration: ShadowsocksConfiguration) throws {
+ configurationLock.lock()
+ defer { configurationLock.unlock() }
+
+ cachedConfiguration = configuration
+ try fileCache.write(configuration)
+ }
+}
diff --git a/ios/MullvadTransport/TransportProvider.swift b/ios/MullvadTransport/TransportProvider.swift
index 93274e9edb..786f34a2b2 100644
--- a/ios/MullvadTransport/TransportProvider.swift
+++ b/ios/MullvadTransport/TransportProvider.swift
@@ -49,23 +49,27 @@ public final class TransportProvider: RESTTransportProvider {
return shadowsocksTransport
} catch {
- logger.error(error: error)
+ logger.error(error: error, message: "Failed to produce shadowsocks configuration.")
+ return nil
}
- return nil
}
- /// The last used shadowsocks configuration
- ///
- /// The last used shadowsocks configuration if any, otherwise a random one selected by `RelaySelector`
- /// - Returns: A shadowsocks configuration
+ /// Returns the last used shadowsocks configuration, otherwise a new randomized configuration.
private func shadowsocksConfiguration() throws -> ShadowsocksConfiguration {
- // If a previous shadowsocks configuration was in cache, return it directly
- if let configuration = shadowsocksCache.configuration {
- return configuration
- }
-
+ // If a previous shadowsocks configuration was in cache, return it directly.
// There is no previous configuration either if this is the first time this code ran
// Or because the previous shadowsocks configuration was invalid, therefore generate a new one.
+ do {
+ return try shadowsocksCache.read()
+ } catch {
+ // There is no previous configuration either if this is the first time this code ran
+ // Or because the previous shadowsocks configuration was invalid, therefore generate a new one.
+ return try makeNewShadowsocksConfiguration()
+ }
+ }
+
+ /// Returns a randomly selected shadowsocks configuration.
+ private func makeNewShadowsocksConfiguration() throws -> ShadowsocksConfiguration {
let cachedRelays = try relayCache.read()
let bridgeAddress = RelaySelector.getShadowsocksRelay(relays: cachedRelays.relays)?.ipv4AddrIn
let bridgeConfiguration = RelaySelector.getShadowsocksTCPBridge(relays: cachedRelays.relays)
@@ -78,7 +82,13 @@ public final class TransportProvider: RESTTransportProvider {
password: bridgeConfiguration.password,
cipher: bridgeConfiguration.cipher
)
- shadowsocksCache.configuration = newConfiguration
+
+ do {
+ try shadowsocksCache.write(newConfiguration)
+ } catch {
+ logger.error(error: error, message: "Failed to persist shadowsocks cache.")
+ }
+
return newConfiguration
}
}
diff --git a/ios/MullvadTransport/URLSessionShadowsocksTransport.swift b/ios/MullvadTransport/URLSessionShadowsocksTransport.swift
new file mode 100644
index 0000000000..a4e7e34403
--- /dev/null
+++ b/ios/MullvadTransport/URLSessionShadowsocksTransport.swift
@@ -0,0 +1,65 @@
+//
+// ShadowsocksTransport.swift
+// MullvadTransport
+//
+// Created by pronebird on 12/06/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadREST
+import MullvadTypes
+
+public final class URLSessionShadowsocksTransport: RESTTransport {
+ /// The Shadowsocks proxy instance that proxies all the traffic it receives
+ private let shadowsocksProxy: ShadowsocksProxy
+
+ /// The IPv4 representation of the loopback address used by `shadowsocksProxy`
+ private let localhost = "127.0.0.1"
+
+ /// The `URLSession` used to send requests via `shadowsocksProxy`
+ public let urlSession: URLSession
+
+ public var name: String {
+ return "shadow-socks-url-session"
+ }
+
+ public init(
+ urlSession: URLSession,
+ shadowsocksConfiguration: ShadowsocksConfiguration,
+ addressCache: REST.AddressCache
+ ) {
+ self.urlSession = urlSession
+ let apiAddress = addressCache.getCurrentEndpoint()
+
+ shadowsocksProxy = ShadowsocksProxy(
+ forwardAddress: apiAddress.ip,
+ forwardPort: apiAddress.port,
+ bridgeAddress: shadowsocksConfiguration.bridgeAddress,
+ bridgePort: shadowsocksConfiguration.bridgePort,
+ password: shadowsocksConfiguration.password,
+ cipher: shadowsocksConfiguration.cipher
+ )
+ }
+
+ public func sendRequest(
+ _ request: URLRequest,
+ completion: @escaping (Data?, URLResponse?, Swift.Error?) -> Void
+ ) -> Cancellable {
+ // Start the Shadowsocks proxy in order to get a local port
+ shadowsocksProxy.start()
+
+ // Copy the URL request and rewrite the host and port to point to the Shadowsocks proxy instance
+ var urlRequestCopy = request
+ urlRequestCopy.url = request.url.flatMap { url in
+ var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
+ components?.host = localhost
+ components?.port = Int(shadowsocksProxy.localPort())
+ return components?.url
+ }
+
+ let dataTask = urlSession.dataTask(with: urlRequestCopy, completionHandler: completion)
+ dataTask.resume()
+ return dataTask
+ }
+}
diff --git a/ios/MullvadTransport/URLSessionTransport.swift b/ios/MullvadTransport/URLSessionTransport.swift
index 8556f812ca..4ecef05870 100644
--- a/ios/MullvadTransport/URLSessionTransport.swift
+++ b/ios/MullvadTransport/URLSessionTransport.swift
@@ -31,56 +31,3 @@ public final class URLSessionTransport: RESTTransport {
return dataTask
}
}
-
-public final class URLSessionShadowsocksTransport: RESTTransport {
- /// The Shadowsocks proxy instance that proxies all the traffic it receives
- private let shadowsocksProxy: ShadowsocksProxy
- /// The IPv4 representation of the loopback address used by `shadowsocksProxy`
- private let localhost = "127.0.0.1"
-
- /// The `URLSession` used to send requests via `shadowsocksProxy`
- public let urlSession: URLSession
-
- public var name: String {
- return "shadow-socks-url-session"
- }
-
- public init(
- urlSession: URLSession,
- shadowsocksConfiguration: ShadowsocksConfiguration,
- addressCache: REST.AddressCache
- ) {
- self.urlSession = urlSession
- let apiAddress = addressCache.getCurrentEndpoint()
-
- shadowsocksProxy = ShadowsocksProxy(
- forwardAddress: apiAddress.ip,
- forwardPort: apiAddress.port,
- bridgeAddress: shadowsocksConfiguration.bridgeAddress,
- bridgePort: shadowsocksConfiguration.bridgePort,
- password: shadowsocksConfiguration.password,
- cipher: shadowsocksConfiguration.cipher
- )
- }
-
- public func sendRequest(
- _ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Swift.Error?) -> Void
- ) -> Cancellable {
- // Start the Shadowsocks proxy in order to get a local port
- shadowsocksProxy.start()
-
- // Copy the URL request and rewrite the host and port to point to the Shadowsocks proxy instance
- var urlRequestCopy = request
- urlRequestCopy.url = request.url.flatMap { url in
- var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
- components?.host = localhost
- components?.port = Int(shadowsocksProxy.localPort())
- return components?.url
- }
-
- let dataTask = urlSession.dataTask(with: urlRequestCopy, completionHandler: completion)
- dataTask.resume()
- return dataTask
- }
-}
diff --git a/ios/MullvadTypes/Cache.swift b/ios/MullvadTypes/Cache.swift
deleted file mode 100644
index 6238e6c947..0000000000
--- a/ios/MullvadTypes/Cache.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// Cache.swift
-// MullvadTypes
-//
-// Created by Marco Nikic on 2023-05-30.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-/// A protocol for reading and writing to a cache file using `NSFileCoordinator` for cross process protection.
-///
-/// Uses `JSONDecoder` for reading and `JSONEncoder` for writing. `CacheType` must conform to `Codable`
-public protocol Caching<CacheType> where CacheType: Codable {
- associatedtype CacheType
-
- /// The name of the cache file
- static var cacheFileName: String { get }
- /// The location of the cache file
- var cacheFileURL: URL { get }
-
- func readFromDisk() throws -> CacheType
- func writeToDisk(_: CacheType) throws
-}
-
-public extension Caching {
- func readFromDisk() throws -> CacheType {
- let fileCoordinator = NSFileCoordinator(filePresenter: nil)
- let result = try fileCoordinator
- .coordinate(readingItemAt: cacheFileURL, options: [.withoutChanges]) { file in
- let data = try Data(contentsOf: file)
- let cachedFile = try JSONDecoder().decode(CacheType.self, from: data)
-
- return cachedFile
- }
-
- return result
- }
-
- func writeToDisk(_ cache: CacheType) throws {
- let fileCoordinator = NSFileCoordinator(filePresenter: nil)
-
- try fileCoordinator.coordinate(writingItemAt: cacheFileURL, options: [.forReplacing]) { file in
- let data = try JSONEncoder().encode(cache)
- try data.write(to: file)
- }
- }
-}
diff --git a/ios/MullvadTypes/FileCache.swift b/ios/MullvadTypes/FileCache.swift
new file mode 100644
index 0000000000..4e16a73c41
--- /dev/null
+++ b/ios/MullvadTypes/FileCache.swift
@@ -0,0 +1,42 @@
+//
+// FileCache.swift
+// MullvadTypes
+//
+// Created by Marco Nikic on 2023-05-30.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// File cache implementation that can read and write any `Codable` content and uses file coordinator to coordinate I/O.
+public struct FileCache<Content: Codable>: FileCacheProtocol {
+ public let fileURL: URL
+
+ public init(fileURL: URL) {
+ self.fileURL = fileURL
+ }
+
+ public func read() throws -> Content {
+ let fileCoordinator = NSFileCoordinator(filePresenter: nil)
+
+ return try fileCoordinator.coordinate(readingItemAt: fileURL, options: [.withoutChanges]) { fileURL in
+ return try JSONDecoder().decode(Content.self, from: Data(contentsOf: fileURL))
+ }
+ }
+
+ public func write(_ content: Content) throws {
+ let fileCoordinator = NSFileCoordinator(filePresenter: nil)
+
+ try fileCoordinator.coordinate(writingItemAt: fileURL, options: [.forReplacing]) { fileURL in
+ try JSONEncoder().encode(content).write(to: fileURL)
+ }
+ }
+}
+
+/// Protocol describing file cache that's able to read and write serializable content.
+public protocol FileCacheProtocol<Content> {
+ associatedtype Content: Codable
+
+ func read() throws -> Content
+ func write(_ content: Content) throws
+}
diff --git a/ios/MullvadTypes/ShadowsocksConfigurationCache.swift b/ios/MullvadTypes/ShadowsocksConfigurationCache.swift
deleted file mode 100644
index d8f035a8db..0000000000
--- a/ios/MullvadTypes/ShadowsocksConfigurationCache.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// ShadowsocksConfigurationCache.swift
-// MullvadTypes
-//
-// Created by Marco Nikic on 2023-06-05.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-/// Holds a shadowsocks configuration object backed by a caching mechanism shared across processes
-public class ShadowsocksConfigurationCache: Caching {
- public typealias CacheType = ShadowsocksConfiguration
-
- public static var cacheFileName: String { "shadowsocks-cache.json" }
- public let cacheFileURL: URL
-
- private var _configuration: ShadowsocksConfiguration?
- private let cacheLock = NSLock()
-
- public init(cacheFolder: URL) {
- let cacheFileURL = cacheFolder.appendingPathComponent(
- Self.cacheFileName,
- isDirectory: false
- )
-
- self.cacheFileURL = cacheFileURL
- }
-
- /// The cached shadowsocks configuration object
- /// If there is no cache available, a configuration will be read from disk
- public var configuration: ShadowsocksConfiguration? {
- get {
- cacheLock.lock()
- defer { cacheLock.unlock() }
-
- if let _configuration {
- return _configuration
- }
- do {
- let diskCache = try readFromDisk()
- return diskCache
- } catch {
- return nil
- }
- }
- set {
- cacheLock.lock()
- defer { cacheLock.unlock() }
-
- _configuration = newValue
- if let _configuration {
- try? writeToDisk(_configuration)
- }
- }
- }
-}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 89c6598405..f16f22ba27 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -79,6 +79,8 @@
5820676426E771DB00655B05 /* TunnelManagerErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820676326E771DB00655B05 /* TunnelManagerErrors.swift */; };
5820EDA9288FE064006BF4E4 /* DeviceManagementInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820EDA8288FE064006BF4E4 /* DeviceManagementInteractor.swift */; };
5820EDAB288FF0D2006BF4E4 /* DeviceRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820EDAA288FF0D2006BF4E4 /* DeviceRowView.swift */; };
+ 5822C0042A3724A800A3A5FB /* ShadowsocksConfigurationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E8A2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift */; };
+ 5822C0052A3724A800A3A5FB /* ShadowsocksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E872A2DCD57000DC21F /* ShadowsocksConfiguration.swift */; };
5823FA5426CE49F700283BF8 /* TunnelObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA5326CE49F600283BF8 /* TunnelObserver.swift */; };
58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */; };
58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB025124117005D0BB5 /* CustomTextField.swift */; };
@@ -251,6 +253,8 @@
58C3A4B222456F1B00340BDB /* AccountInputGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */; };
58C3F4F92964B08300D72515 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3F4F82964B08300D72515 /* MapViewController.swift */; };
58C3F4FB296C3AD500D72515 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3F4FA296C3AD500D72515 /* SettingsCoordinator.swift */; };
+ 58C3FA662A38549D006A450A /* MockFileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3FA652A38549D006A450A /* MockFileCache.swift */; };
+ 58C3FA682A385C89006A450A /* FileCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3FA672A385C89006A450A /* FileCacheTests.swift */; };
58C76A082A33850E00100D75 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; };
58C76A092A33850E00100D75 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; };
58C76A0B2A338E4300100D75 /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A0A2A338E4300100D75 /* BackgroundTask.swift */; };
@@ -340,6 +344,7 @@
58E0729F28814ACC008902F8 /* WireGuardLogLevel+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0729E28814ACC008902F8 /* WireGuardLogLevel+Logging.swift */; };
58E072A128814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E072A028814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift */; };
58E0A98827C8F46300FE6BDD /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0A98727C8F46300FE6BDD /* Tunnel.swift */; };
+ 58E0E2842A3718CE002E3420 /* URLSessionShadowsocksTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0E2832A3718CE002E3420 /* URLSessionShadowsocksTransport.swift */; };
58E11188292FA11F009FCA84 /* SettingsMigrationUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */; };
58E20771274672CA00DE5D77 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E20770274672CA00DE5D77 /* LaunchViewController.swift */; };
58E25F812837BBBB002CFB2C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E25F802837BBBB002CFB2C /* SceneDelegate.swift */; };
@@ -388,11 +393,6 @@
A93D13782A1F60A6001EB0B1 /* shadowsocks.h in Headers */ = {isa = PBXBuildFile; fileRef = 586F2BE129F6916F009E6924 /* shadowsocks.h */; settings = {ATTRIBUTES = (Private, ); }; };
A9467E7F2A29DEFE000DC21F /* RelayCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */; };
A9467E802A29E0A6000DC21F /* AddressCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */; };
- A9467E822A29E0F8000DC21F /* TestsCacheFilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E812A29E0F8000DC21F /* TestsCacheFilePresenter.swift */; };
- A9467E842A29E69F000DC21F /* CachedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E832A29E69F000DC21F /* CachedTests.swift */; };
- A9467E862A29E9F3000DC21F /* ServerRelaysResponse+Mocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E852A29E9F3000DC21F /* ServerRelaysResponse+Mocks.swift */; };
- A9467E892A2DD688000DC21F /* ShadowsocksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E872A2DCD57000DC21F /* ShadowsocksConfiguration.swift */; };
- A9467E8B2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E8A2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift */; };
A95F86B72A1F53BA00245DAC /* URLSessionTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67C28F83CA50033DD93 /* URLSessionTransport.swift */; };
A95F86B82A1F547000245DAC /* ShadowsocksProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F1FF1B29F06124007083C3 /* ShadowsocksProxy.swift */; };
A97F1F442A1F4E1A00ECEFDE /* MullvadTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -400,7 +400,7 @@
A97F1F482A1F4E1A00ECEFDE /* MullvadTransport.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
A97FF54B2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54A2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift */; };
A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */; };
- A9A8A8EB2A262AB30086D569 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A8A8EA2A262AB30086D569 /* Cache.swift */; };
+ A9A8A8EB2A262AB30086D569 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A8A8EA2A262AB30086D569 /* FileCache.swift */; };
A9B2CF722A1F64CD0013CC6C /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */; };
A9D99BA02A1F7F3A00DE27D3 /* TransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */; };
@@ -1027,6 +1027,8 @@
58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInputGroupView.swift; sourceTree = "<group>"; };
58C3F4F82964B08300D72515 /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = "<group>"; };
58C3F4FA296C3AD500D72515 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
+ 58C3FA652A38549D006A450A /* MockFileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileCache.swift; sourceTree = "<group>"; };
+ 58C3FA672A385C89006A450A /* FileCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCacheTests.swift; sourceTree = "<group>"; };
58C76A072A33850E00100D75 /* ApplicationTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationTarget.swift; sourceTree = "<group>"; };
58C76A0A2A338E4300100D75 /* BackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTask.swift; sourceTree = "<group>"; };
58C774BD29A7A249003A1A56 /* CustomNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationController.swift; sourceTree = "<group>"; };
@@ -1069,6 +1071,7 @@
58E072A028814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MullvadEndpoint+WgEndpoint.swift"; sourceTree = "<group>"; };
58E072A428814C28008902F8 /* TunnelMonitorDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorDelegate.swift; sourceTree = "<group>"; };
58E0A98727C8F46300FE6BDD /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; };
+ 58E0E2832A3718CE002E3420 /* URLSessionShadowsocksTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionShadowsocksTransport.swift; sourceTree = "<group>"; };
58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationUIHandler.swift; sourceTree = "<group>"; };
58E20770274672CA00DE5D77 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
58E25F802837BBBB002CFB2C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -1117,16 +1120,13 @@
A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportStrategy.swift; sourceTree = "<group>"; };
A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportStrategyTests.swift; sourceTree = "<group>"; };
A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTests.swift; sourceTree = "<group>"; };
- A9467E812A29E0F8000DC21F /* TestsCacheFilePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestsCacheFilePresenter.swift; sourceTree = "<group>"; };
- A9467E832A29E69F000DC21F /* CachedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedTests.swift; sourceTree = "<group>"; };
- A9467E852A29E9F3000DC21F /* ServerRelaysResponse+Mocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerRelaysResponse+Mocks.swift"; sourceTree = "<group>"; };
A9467E872A2DCD57000DC21F /* ShadowsocksConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksConfiguration.swift; sourceTree = "<group>"; };
A9467E8A2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksConfigurationCache.swift; sourceTree = "<group>"; };
A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadTransport.h; sourceTree = "<group>"; };
A97FF54A2A0B7AD000900996 /* SimulatorTunnelTransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelTransportProvider.swift; sourceTree = "<group>"; };
A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFileCoordinator+Extensions.swift"; sourceTree = "<group>"; };
- A9A8A8EA2A262AB30086D569 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
+ A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; };
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = "<group>"; };
A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportProvider.swift; sourceTree = "<group>"; };
E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; };
@@ -1373,7 +1373,7 @@
584D26BE270C550B004EA533 /* AnyIPAddress.swift */,
586A951329013235007BAF2B /* AnyIPEndpoint.swift */,
06AC113628F83FD70037AF9A /* Cancellable.swift */,
- A9A8A8EA2A262AB30086D569 /* Cache.swift */,
+ A9A8A8EA2A262AB30086D569 /* FileCache.swift */,
58E511E328DDDE8900B0BCDE /* CustomErrorDescriptionProtocol.swift */,
586168682976F6BD00EF8598 /* DisplayError.swift */,
58E511EA28DDE18400B0BCDE /* Error+Chain.swift */,
@@ -1395,8 +1395,6 @@
5898D2AF2902A67C00EB5EBA /* RelayLocation.swift */,
581DA2722A1E227D0046ED47 /* RESTTypes.swift */,
58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */,
- A9467E872A2DCD57000DC21F /* ShadowsocksConfiguration.swift */,
- A9467E8A2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift */,
58E511E028DDB7F100B0BCDE /* WrappingError.swift */,
);
path = MullvadTypes;
@@ -1882,18 +1880,16 @@
children = (
58B0A2A4238EE67E00BC001D /* Info.plist */,
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */,
- 582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */,
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */,
- A9467E832A29E69F000DC21F /* CachedTests.swift */,
5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */,
58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */,
+ 58C3FA672A385C89006A450A /* FileCacheTests.swift */,
582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */,
A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */,
584B26F3237434D00073B10E /* RelaySelectorTests.swift */,
- A9467E852A29E9F3000DC21F /* ServerRelaysResponse+Mocks.swift */,
5807E2C1243203D000F5FF30 /* StringTests.swift */,
58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */,
- A9467E812A29E0F8000DC21F /* TestsCacheFilePresenter.swift */,
+ 58C3FA652A38549D006A450A /* MockFileCache.swift */,
);
path = MullvadVPNTests;
sourceTree = "<group>";
@@ -2156,8 +2152,11 @@
A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */,
586F2BE129F6916F009E6924 /* shadowsocks.h */,
06FAE67C28F83CA50033DD93 /* URLSessionTransport.swift */,
+ 58E0E2832A3718CE002E3420 /* URLSessionShadowsocksTransport.swift */,
01F1FF1B29F06124007083C3 /* ShadowsocksProxy.swift */,
A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */,
+ A9467E872A2DCD57000DC21F /* ShadowsocksConfiguration.swift */,
+ A9467E8A2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift */,
);
path = MullvadTransport;
sourceTree = "<group>";
@@ -2873,23 +2872,22 @@
58B8644529C7971B005E107C /* InputTextFormatter.swift in Sources */,
58915D692A2601FB0066445B /* WgKeyRotation.swift in Sources */,
580810E62A30E13D00B74552 /* DeviceStateAccessorProtocol.swift in Sources */,
+ 58C3FA662A38549D006A450A /* MockFileCache.swift in Sources */,
58915D642A25F8B30066445B /* DeviceCheckOperation.swift in Sources */,
58915D652A25F9E20066445B /* TunnelSettingsV2.swift in Sources */,
58B8644529C7971B005E107C /* InputTextFormatter.swift in Sources */,
A9467E7F2A29DEFE000DC21F /* RelayCacheTests.swift in Sources */,
- A9467E822A29E0F8000DC21F /* TestsCacheFilePresenter.swift in Sources */,
582A8A3A28BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift in Sources */,
58915D632A25F8400066445B /* DeviceCheckOperationTests.swift in Sources */,
5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */,
58B8644629C7972F005E107C /* CustomDateComponentsFormatting.swift in Sources */,
- A9467E842A29E69F000DC21F /* CachedTests.swift in Sources */,
5807E2C2243203D000F5FF30 /* StringTests.swift in Sources */,
+ 58C3FA682A385C89006A450A /* FileCacheTests.swift in Sources */,
58165EBE2A262CBB00688EAD /* WgKeyRotationTests.swift in Sources */,
5807E2C3243203E700F5FF30 /* String+Split.swift in Sources */,
580810E92A30E17300B74552 /* DeviceCheckRemoteServiceProtocol.swift in Sources */,
F07BF2582A26112D00042943 /* InputTextFormatterTests.swift in Sources */,
58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */,
- A9467E862A29E9F3000DC21F /* ServerRelaysResponse+Mocks.swift in Sources */,
A9467E802A29E0A6000DC21F /* AddressCacheTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -3183,9 +3181,7 @@
58E45A5729F12C5100281ECF /* Result+Extensions.swift in Sources */,
58D2240B294C90210029F5F8 /* Cancellable.swift in Sources */,
58D2240C294C90210029F5F8 /* WrappingError.swift in Sources */,
- A9A8A8EB2A262AB30086D569 /* Cache.swift in Sources */,
- A9467E892A2DD688000DC21F /* ShadowsocksConfiguration.swift in Sources */,
- A9467E8B2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift in Sources */,
+ A9A8A8EB2A262AB30086D569 /* FileCache.swift in Sources */,
58D2240D294C90210029F5F8 /* CustomErrorDescriptionProtocol.swift in Sources */,
58D2240E294C90210029F5F8 /* Error+Chain.swift in Sources */,
586168692976F6BD00EF8598 /* DisplayError.swift in Sources */,
@@ -3232,9 +3228,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 5822C0052A3724A800A3A5FB /* ShadowsocksConfiguration.swift in Sources */,
A95F86B82A1F547000245DAC /* ShadowsocksProxy.swift in Sources */,
A95F86B72A1F53BA00245DAC /* URLSessionTransport.swift in Sources */,
+ 5822C0042A3724A800A3A5FB /* ShadowsocksConfigurationCache.swift in Sources */,
A9D99BA02A1F7F3A00DE27D3 /* TransportProvider.swift in Sources */,
+ 58E0E2842A3718CE002E3420 /* URLSessionShadowsocksTransport.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 2bdda88201..0906f376ed 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -53,8 +53,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let containerURL = ApplicationConfiguration.containerURL
- addressCache = REST.AddressCache(canWriteToCache: true, cacheFolder: containerURL)
- addressCache.initCache()
+ addressCache = REST.AddressCache(canWriteToCache: true, cacheDirectory: containerURL)
+ addressCache.loadFromFile()
proxyFactory = REST.ProxyFactory.makeProxyFactory(
transportProvider: { [weak self] in self?.transportMonitor },
@@ -65,7 +65,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
accountsProxy = proxyFactory.createAccountsProxy()
devicesProxy = proxyFactory.createDevicesProxy()
- let relayCache = RelayCache(cacheFolder: containerURL)
+ let relayCache = RelayCache(cacheDirectory: containerURL)
relayCacheTracker = RelayCacheTracker(relayCache: relayCache, application: application, apiProxy: apiProxy)
addressCacheTracker = AddressCacheTracker(
@@ -92,7 +92,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
let urlSessionTransport = URLSessionTransport(urlSession: REST.makeURLSession())
- let shadowsocksCache = ShadowsocksConfigurationCache(cacheFolder: containerURL)
+ let shadowsocksCache = ShadowsocksConfigurationCache(cacheDirectory: containerURL)
let transportProvider = TransportProvider(
urlSessionTransport: urlSessionTransport,
relayCache: relayCache,
diff --git a/ios/MullvadVPNTests/AddressCacheTests.swift b/ios/MullvadVPNTests/AddressCacheTests.swift
index 4b9091d28c..d0e1d823b1 100644
--- a/ios/MullvadVPNTests/AddressCacheTests.swift
+++ b/ios/MullvadVPNTests/AddressCacheTests.swift
@@ -8,60 +8,57 @@
@testable import MullvadREST
import MullvadTypes
+import struct Network.IPv4Address
import XCTest
-final class AddressCacheTests: CachedTests {
- var apiEndpoint: AnyIPEndpoint!
-
- // MARK: Tests Setup
-
- override class var cacheFileName: String { REST.AddressCache.cacheFileName }
-
- override func setUpWithError() throws {
- try super.setUpWithError()
- apiEndpoint = try XCTUnwrap(AnyIPEndpoint(string: "127.0.0.1:80"))
- }
+final class AddressCacheTests: XCTestCase {
+ let apiEndpoint: AnyIPEndpoint = .ipv4(IPv4Endpoint(ip: IPv4Address.loopback, port: 80))
// MARK: - Tests
func testAddressCacheHasDefaultEndpoint() {
- let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: Self.testsCacheDirectory)
- XCTAssertEqual(cache.getCurrentEndpoint(), REST.defaultAPIEndpoint)
+ let addressCache = REST.AddressCache(
+ canWriteToCache: false,
+ fileCache: MockFileCache(initialState: .fileNotFound)
+ )
+ XCTAssertEqual(addressCache.getCurrentEndpoint(), REST.defaultAPIEndpoint)
}
func testSetEndpoints() throws {
- let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: Self.testsCacheDirectory)
+ let addressCache = REST.AddressCache(
+ canWriteToCache: false,
+ fileCache: MockFileCache(initialState: .fileNotFound)
+ )
- cache.setEndpoints([apiEndpoint])
- XCTAssertEqual(cache.getCurrentEndpoint(), apiEndpoint)
+ addressCache.setEndpoints([apiEndpoint])
+ XCTAssertEqual(addressCache.getCurrentEndpoint(), apiEndpoint)
}
func testSetEndpointsUpdatesDateWhenSettingSameAddress() throws {
- let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: Self.testsCacheDirectory)
- cache.setEndpoints([apiEndpoint])
+ let addressCache = REST.AddressCache(
+ canWriteToCache: false,
+ fileCache: MockFileCache(initialState: .fileNotFound)
+ )
+ addressCache.setEndpoints([apiEndpoint])
- let dateBeforeSettingEndpoint = Date()
- cache.setEndpoints([apiEndpoint])
- let dateAfterSettingEndpoint = Date()
+ let dateBeforeUpdate = addressCache.getLastUpdateDate()
+ addressCache.setEndpoints([apiEndpoint])
+ let dateAfterUpdate = addressCache.getLastUpdateDate()
- let dateIntervalRange = dateBeforeSettingEndpoint ... dateAfterSettingEndpoint
- XCTAssertTrue(dateIntervalRange.contains(cache.getLastUpdateDate()))
+ XCTAssertNotEqual(dateBeforeUpdate, dateAfterUpdate)
}
func testSetEndpointsDoesNotDoAnythingIfSettingEmptyEndpoints() throws {
- let didNotWriteToCache = expectation(description: "Did not write to cache")
- didNotWriteToCache.isInverted = true
+ let addressCache = REST.AddressCache(
+ canWriteToCache: false,
+ fileCache: MockFileCache(initialState: .fileNotFound)
+ )
+ addressCache.loadFromFile()
- cacheFilePresenter.onWriterAction = {
- didNotWriteToCache.fulfill()
- }
-
- try withCachefolders { cacheDirectory, _ in
- let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory)
- cache.setEndpoints([])
- }
+ let currentEndpoint = addressCache.getCurrentEndpoint()
+ addressCache.setEndpoints([])
- waitForExpectations(timeout: defaultExpectationTimeout)
+ XCTAssertEqual(addressCache.getCurrentEndpoint(), currentEndpoint)
}
func testSetEndpointsOnlyAcceptsTheFirstEndpoint() throws {
@@ -71,103 +68,71 @@ final class AddressCacheTests: CachedTests {
let firstIPEndpoint = try XCTUnwrap(ipAddresses.first)
- try withCachefolders { cacheDirectory, cacheFileURL in
- let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory)
- cache.setEndpoints(ipAddresses)
+ let fileCache = MockFileCache<REST.CachedAddresses>()
+ let addressCache = REST.AddressCache(canWriteToCache: true, fileCache: fileCache)
+ addressCache.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 testCacheReadsFromCachedFileWithInitCache() throws {
- let didReadFromCache = expectation(description: "Cache was read")
- cacheFilePresenter.onReaderAction = {
- didReadFromCache.fulfill()
+ let fileState = fileCache.getState()
+ XCTAssertTrue(fileState.isExists)
+ guard case let .exists(cachedAddresses) = fileState else {
+ XCTFail("State is expected to contain cached addresses.")
+ return
}
- try withCachefolders { cacheDirectory, cacheFileURL in
- let fixedDate = Date()
- try prepopulateCache(at: cacheFileURL, fixedDate: fixedDate, with: [apiEndpoint])
- let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory)
- cache.initCache()
+ XCTAssertEqual(cachedAddresses.endpoints.count, 1)
+ XCTAssertEqual(addressCache.getCurrentEndpoint(), firstIPEndpoint)
+ }
- XCTAssertEqual(cache.getCurrentEndpoint(), apiEndpoint)
- XCTAssertEqual(cache.getLastUpdateDate(), fixedDate)
- }
+ func testCacheReadsFromFile() throws {
+ let fixedDate = Date()
+ let addressCache = REST.AddressCache(
+ canWriteToCache: true,
+ fileCache: MockFileCache(initialState: .exists(
+ REST.CachedAddresses(updatedAt: fixedDate, endpoints: [apiEndpoint])
+ ))
+ )
+ addressCache.loadFromFile()
- waitForExpectations(timeout: defaultExpectationTimeout)
+ XCTAssertEqual(addressCache.getCurrentEndpoint(), apiEndpoint)
+ XCTAssertEqual(addressCache.getLastUpdateDate(), fixedDate)
}
func testCacheWritesToDiskWhenSettingNewEndpoints() throws {
- let didWriteToCache = expectation(description: "Cache was written to")
- cacheFilePresenter.onWriterAction = {
- didWriteToCache.fulfill()
- }
+ let fileCache = MockFileCache<REST.CachedAddresses>()
+ let addressCache = REST.AddressCache(canWriteToCache: true, fileCache: fileCache)
- try withCachefolders { cacheDirectory, cacheFileURL in
+ XCTAssertEqual(fileCache.getState(), .fileNotFound)
+ addressCache.setEndpoints([apiEndpoint])
- 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)
+ let fileState = fileCache.getState()
+ XCTAssertTrue(fileState.isExists)
- XCTAssertEqual(cachedAddress, cache.getCurrentEndpoint())
- XCTAssertEqual(cachedAddresses.updatedAt, cache.getLastUpdateDate())
+ guard case let .exists(cachedAddresses) = fileState else {
+ XCTFail("State is expected to contain cached addresses.")
+ return
}
- waitForExpectations(timeout: defaultExpectationTimeout)
+ XCTAssertEqual(cachedAddresses.endpoints, [addressCache.getCurrentEndpoint()])
+ XCTAssertEqual(cachedAddresses.updatedAt, addressCache.getLastUpdateDate())
}
func testGetCurrentEndpointReadsFromCacheWhenReadOnly() throws {
- let didReadFromCache = expectation(description: "Cache was read")
- 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)
+ let addressCache = REST.AddressCache(
+ canWriteToCache: false,
+ fileCache: MockFileCache(initialState: .exists(
+ REST.CachedAddresses(updatedAt: Date(), endpoints: [apiEndpoint])
+ ))
+ )
+ XCTAssertEqual(addressCache.getCurrentEndpoint(), apiEndpoint)
}
func testGetCurrentEndpointHasDefaultEndpointIfCacheIsEmpty() throws {
- let didReadFromCache = expectation(description: "Cache was read")
- 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: -
+ let addressCache = REST.AddressCache(
+ canWriteToCache: false,
+ fileCache: MockFileCache(initialState: .exists(REST.CachedAddresses(updatedAt: Date(), endpoints: [])))
+ )
+ addressCache.loadFromFile()
-extension AddressCacheTests {
- /// 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)
+ XCTAssertEqual(addressCache.getCurrentEndpoint(), REST.defaultAPIEndpoint)
}
}
diff --git a/ios/MullvadVPNTests/CachedTests.swift b/ios/MullvadVPNTests/CachedTests.swift
deleted file mode 100644
index 48b8f9a50d..0000000000
--- a/ios/MullvadVPNTests/CachedTests.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// CachedTests.swift
-// MullvadVPNTests
-//
-// Created by Marco Nikic on 2023-06-02.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import MullvadREST
-import XCTest
-
-class CachedTests: XCTestCase {
- static var testsCacheDirectory: URL!
- var cacheFilePresenter: TestsCacheFilePresenter!
- let defaultExpectationTimeout = REST.Duration.milliseconds(200).timeInterval
-
- open class var cacheFileName: String {
- XCTFail("Do not use this class directly, inherit from it instead")
- return ""
- }
-
- override class func setUp() {
- super.setUp()
- let temporaryDirectory = FileManager.default.temporaryDirectory
- testsCacheDirectory = temporaryDirectory.appendingPathComponent("\(self)")
- }
-
- override func setUpWithError() throws {
- try super.setUpWithError()
- let cacheFileURL = Self.testsCacheDirectory.appendingPathComponent(Self.cacheFileName)
- cacheFilePresenter = TestsCacheFilePresenter(presentedItemURL: cacheFileURL)
- NSFileCoordinator.addFilePresenter(cacheFilePresenter)
- }
-
- override func tearDownWithError() throws {
- NSFileCoordinator.removeFilePresenter(cacheFilePresenter)
- try super.tearDownWithError()
- }
-}
-
-extension CachedTests {
- /// 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)
- }
-}
diff --git a/ios/MullvadVPNTests/FileCacheTests.swift b/ios/MullvadVPNTests/FileCacheTests.swift
new file mode 100644
index 0000000000..dc3b250ed8
--- /dev/null
+++ b/ios/MullvadVPNTests/FileCacheTests.swift
@@ -0,0 +1,43 @@
+//
+// FileCacheTests.swift
+// MullvadVPNTests
+//
+// Created by pronebird on 13/06/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+@testable import MullvadTypes
+import XCTest
+
+class FileCacheTests: XCTestCase {
+ var testFileURL: URL!
+
+ override func setUp() {
+ testFileURL = FileManager.default.temporaryDirectory
+ .appendingPathComponent("FileCacheTest-\(UUID().uuidString)", isDirectory: true)
+ }
+
+ override func tearDown() {
+ try? FileManager.default.removeItem(at: testFileURL)
+ }
+
+ func testRead() throws {
+ let stringData = UUID().uuidString
+ try JSONEncoder().encode(stringData).write(to: testFileURL)
+
+ let fileCache = FileCache<String>(fileURL: testFileURL)
+ XCTAssertEqual(try fileCache.read(), stringData)
+ }
+
+ func testWrite() throws {
+ let fileCache = FileCache<String>(fileURL: testFileURL)
+
+ let stringData = UUID().uuidString
+ let serializedData = try JSONEncoder().encode(stringData)
+
+ try fileCache.write(stringData)
+
+ XCTAssertEqual(try Data(contentsOf: testFileURL), serializedData)
+ }
+}
diff --git a/ios/MullvadVPNTests/MockFileCache.swift b/ios/MullvadVPNTests/MockFileCache.swift
new file mode 100644
index 0000000000..0b07c788e2
--- /dev/null
+++ b/ios/MullvadVPNTests/MockFileCache.swift
@@ -0,0 +1,63 @@
+//
+// MockFileCache.swift
+// MullvadVPNTests
+//
+// Created by pronebird on 13/06/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadTypes
+
+/// File cache implementation that simulates file state and uses internal lock to synchronize access to it.
+final class MockFileCache<Content: Codable & Equatable>: FileCacheProtocol {
+ private var state: State
+ private let stateLock = NSLock()
+
+ init(initialState: State = .fileNotFound) {
+ state = initialState
+ }
+
+ /// Returns internal state.
+ func getState() -> State {
+ stateLock.lock()
+ defer { stateLock.unlock() }
+
+ return state
+ }
+
+ func read() throws -> Content {
+ stateLock.lock()
+ defer { stateLock.unlock() }
+
+ switch state {
+ case .fileNotFound:
+ throw CocoaError(.fileReadNoSuchFile)
+ case let .exists(content):
+ return content
+ }
+ }
+
+ func write(_ content: Content) throws {
+ stateLock.lock()
+ defer { stateLock.unlock() }
+
+ state = .exists(content)
+ }
+
+ enum State: Equatable {
+ /// File does not exist yet.
+ case fileNotFound
+
+ /// File exists with the given contents.
+ case exists(Content)
+
+ var isExists: Bool {
+ if case .exists = self {
+ return true
+ } else {
+ return false
+ }
+ }
+ }
+}
diff --git a/ios/MullvadVPNTests/RelayCacheTests.swift b/ios/MullvadVPNTests/RelayCacheTests.swift
index 05f1b33180..1a5d4804d4 100644
--- a/ios/MullvadVPNTests/RelayCacheTests.swift
+++ b/ios/MullvadVPNTests/RelayCacheTests.swift
@@ -11,51 +11,47 @@ import MullvadTransport
@testable import RelayCache
import XCTest
-final class RelayCacheTests: CachedTests {
- override class var cacheFileName: String { RelayCache.cacheFileName }
+final class RelayCacheTests: XCTestCase {
+ func testCanReadCache() throws {
+ let fileCache = MockFileCache(
+ initialState: .exists(CachedRelays(relays: .mock(), updatedAt: .distantPast))
+ )
+ let cache = RelayCache(fileCache: fileCache)
+ let relays = try XCTUnwrap(cache.read())
- func testReadReadsFromCache() throws {
- let didReadFromCache = expectation(description: "Cache was read")
- cacheFilePresenter.onReaderAction = {
- didReadFromCache.fulfill()
- }
-
- try withCachefolders { cacheDirectory, cacheFileURL in
- try prepopulateCache(at: cacheFileURL, fixedDate: .distantPast)
-
- let cache = RelayCache(cacheFolder: cacheDirectory)
- let relays = try cache.read()
-
- XCTAssertEqual(relays.updatedAt, .distantPast)
- }
-
- waitForExpectations(timeout: defaultExpectationTimeout)
+ XCTAssertEqual(fileCache.getState(), .exists(relays))
}
- func testWriteWritesToCache() throws {
- let didWriteToCache = expectation(description: "Cache was written to")
- cacheFilePresenter.onWriterAction = {
- didWriteToCache.fulfill()
- }
-
- try withCachefolders { cacheDirectory, cacheFileURL in
- let cache = RelayCache(cacheFolder: cacheDirectory)
- try cache.write(record: CachedRelays(relays: .empty, updatedAt: .distantPast))
+ func testCanWriteCache() throws {
+ let fileCache = MockFileCache(
+ initialState: .exists(CachedRelays(relays: .mock(), updatedAt: .distantPast))
+ )
+ let cache = RelayCache(fileCache: fileCache)
+ let newCachedRelays = CachedRelays(relays: .mock(), updatedAt: Date())
- let cachedContent = try Data(contentsOf: cacheFileURL)
- let cachedRelays = try JSONDecoder().decode(CachedRelays.self, from: cachedContent)
+ try cache.write(record: newCachedRelays)
+ XCTAssertEqual(fileCache.getState(), .exists(newCachedRelays))
+ }
- XCTAssertEqual(cachedRelays.updatedAt, .distantPast)
- }
+ func testCanReadPrebundledRelaysWhenNoCacheIsStored() throws {
+ let fileCache = MockFileCache<CachedRelays>(initialState: .fileNotFound)
+ let cache = RelayCache(fileCache: fileCache)
- waitForExpectations(timeout: defaultExpectationTimeout)
+ XCTAssertNoThrow(try cache.read())
}
}
-extension RelayCacheTests {
- func prepopulateCache(at cacheFileURL: URL, fixedDate: Date = .init()) throws {
- let prepopulatedCache = CachedRelays(relays: .empty, updatedAt: fixedDate)
- let encodedCache = try JSONEncoder().encode(prepopulatedCache)
- try encodedCache.write(to: cacheFileURL)
+private extension REST.ServerRelaysResponse {
+ static func mock() -> Self {
+ return REST.ServerRelaysResponse(
+ locations: [:],
+ wireguard: REST.ServerWireguardTunnels(
+ ipv4Gateway: .loopback,
+ ipv6Gateway: .loopback,
+ portRanges: [],
+ relays: []
+ ),
+ bridge: REST.ServerBridges(shadowsocks: [], relays: [])
+ )
}
}
diff --git a/ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift b/ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift
deleted file mode 100644
index 3eaa173d39..0000000000
--- a/ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-// ServerRelaysResponse+Mocks.swift
-// MullvadVPNTests
-//
-// Created by Marco Nikic on 2023-06-02.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import MullvadREST
-import Network
-
-extension REST.ServerRelaysResponse {
- static var empty: Self {
- REST.ServerRelaysResponse(locations: [:], wireguard: .empty, bridge: .empty)
- }
-}
-
-extension REST.ServerLocation {
- static var empty: Self {
- .init(country: "", city: "", latitude: 0, longitude: 0)
- }
-}
-
-extension REST.ServerWireguardTunnels {
- static var empty: Self {
- .init(ipv4Gateway: .loopback, ipv6Gateway: .loopback, portRanges: [], relays: [])
- }
-}
-
-extension REST.ServerBridges {
- static var empty: Self {
- .init(shadowsocks: [], relays: [])
- }
-}
diff --git a/ios/MullvadVPNTests/TestsCacheFilePresenter.swift b/ios/MullvadVPNTests/TestsCacheFilePresenter.swift
deleted file mode 100644
index 27a67b6d28..0000000000
--- a/ios/MullvadVPNTests/TestsCacheFilePresenter.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// TestsCacheFilePresenter.swift
-// MullvadVPNTests
-//
-// Created by Marco Nikic on 2023-06-02.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-class TestsCacheFilePresenter: NSObject, NSFilePresenter {
- var presentedItemURL: URL?
- let operationQueue: OperationQueue
- let dispatchQueue = DispatchQueue(label: "com.MullvadVPN.TestsCacheFilePresenter")
- 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) {
- onReaderAction?()
- reader(nil)
- }
-
- func relinquishPresentedItem(toWriter writer: @escaping ((() -> Void)?) -> Void) {
- onWriterAction?()
- writer(nil)
- }
-}
diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift
index 3691fecebc..e3bb561067 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -135,14 +135,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
tunnelLogger = Logger(label: "WireGuard")
let containerURL = ApplicationConfiguration.containerURL
- let addressCache = REST.AddressCache(canWriteToCache: false, cacheFolder: containerURL)
- addressCache.initCache()
+ let addressCache = REST.AddressCache(canWriteToCache: false, cacheDirectory: containerURL)
+ addressCache.loadFromFile()
- relayCache = RelayCache(cacheFolder: containerURL)
+ relayCache = RelayCache(cacheDirectory: containerURL)
let urlSession = REST.makeURLSession()
let urlSessionTransport = URLSessionTransport(urlSession: urlSession)
- let shadowsocksCache = ShadowsocksConfigurationCache(cacheFolder: containerURL)
+ let shadowsocksCache = ShadowsocksConfigurationCache(cacheDirectory: containerURL)
let transportProvider = TransportProvider(
urlSessionTransport: urlSessionTransport,
relayCache: relayCache,
diff --git a/ios/RelayCache/CachedRelays.swift b/ios/RelayCache/CachedRelays.swift
index 75a775c650..499eb9cde3 100644
--- a/ios/RelayCache/CachedRelays.swift
+++ b/ios/RelayCache/CachedRelays.swift
@@ -10,7 +10,7 @@ import Foundation
import MullvadREST
/// A struct that represents the relay cache on disk
-public struct CachedRelays: Codable {
+public struct CachedRelays: Codable, Equatable {
/// E-tag returned by server
public let etag: String?
diff --git a/ios/RelayCache/RelayCache.swift b/ios/RelayCache/RelayCache.swift
index ed1cbf4c9e..abce823786 100644
--- a/ios/RelayCache/RelayCache.swift
+++ b/ios/RelayCache/RelayCache.swift
@@ -10,26 +10,24 @@ import Foundation
import MullvadREST
import MullvadTypes
-public final class RelayCache: Caching {
- public typealias CacheType = CachedRelays
+public final class RelayCache {
+ private let fileCache: any FileCacheProtocol<CachedRelays>
- /// Cache file location.
- public let cacheFileURL: URL
- public static let cacheFileName = "relays.json"
+ /// Designated initializer
+ public init(cacheDirectory: URL) {
+ fileCache = FileCache(fileURL: cacheDirectory.appendingPathComponent("relays.json", isDirectory: false))
+ }
- public init(cacheFolder: URL) {
- let cacheFileURL = cacheFolder.appendingPathComponent(
- Self.cacheFileName,
- isDirectory: false
- )
- self.cacheFileURL = cacheFileURL
+ /// Initializer that accepts a custom FileCache implementation. Used in tests.
+ init(fileCache: some FileCacheProtocol<CachedRelays>) {
+ 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 {
do {
- return try readFromDisk()
+ return try fileCache.read()
} catch {
if error is DecodingError || (error as? CocoaError)?.code == .fileReadNoSuchFile {
return try readPrebundledRelays()
@@ -41,16 +39,16 @@ public final class RelayCache: Caching {
/// Safely write the cache file on disk using file coordinator.
public func write(record: CachedRelays) throws {
- try writeToDisk(record)
+ try fileCache.write(record)
}
/// Read pre-bundled relays file from disk.
private func readPrebundledRelays() throws -> CachedRelays {
- guard let prebundledRelaysFileURL = Bundle(for: Self.self)
- .url(forResource: "relays", withExtension: "json") else { throw POSIXError(.ENOENT) }
+ 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)
+ let relays = try REST.Coding.makeJSONDecoder().decode(REST.ServerRelaysResponse.self, from: data)
return CachedRelays(
relays: relays,