summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2023-05-30 15:52:26 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-06-13 16:25:10 +0200
commite84905793ac54c4768a416f76dd4589e1f66fcf1 (patch)
tree7bfffbdb6520bce6fd42c1702fbb8c7cd4e2537d
parent7448427f811daf99dd4ab9d4f3676f354beff245 (diff)
downloadmullvadvpn-e84905793ac54c4768a416f76dd4589e1f66fcf1.tar.xz
mullvadvpn-e84905793ac54c4768a416f76dd4589e1f66fcf1.zip
Cache the last used shadowsocks configuration and reuse it subsequently
-rw-r--r--ios/MullvadREST/AddressCache.swift89
-rw-r--r--ios/MullvadTransport/TransportProvider.swift49
-rw-r--r--ios/MullvadTransport/URLSessionTransport.swift7
-rw-r--r--ios/MullvadTypes/Cache.swift48
-rw-r--r--ios/MullvadTypes/ShadowsocksConfiguration.swift24
-rw-r--r--ios/MullvadTypes/ShadowsocksConfigurationCache.swift57
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj41
-rw-r--r--ios/MullvadVPN/AppDelegate.swift15
-rw-r--r--ios/MullvadVPNTests/AddressCacheTests.swift (renamed from ios/MullvadRESTTests/AddressCacheTests.swift)72
-rw-r--r--ios/MullvadVPNTests/CachedTests.swift56
-rw-r--r--ios/MullvadVPNTests/RelayCacheTests.swift61
-rw-r--r--ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift34
-rw-r--r--ios/MullvadVPNTests/TestsCacheFilePresenter.swift35
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift14
-rw-r--r--ios/RelayCache/CachedRelays.swift6
-rw-r--r--ios/RelayCache/RelayCache.swift81
16 files changed, 456 insertions, 233 deletions
diff --git a/ios/MullvadREST/AddressCache.swift b/ios/MullvadREST/AddressCache.swift
index bc54f306b4..0f449f58ff 100644
--- a/ios/MullvadREST/AddressCache.swift
+++ b/ios/MullvadREST/AddressCache.swift
@@ -11,15 +11,24 @@ import MullvadLogging
import MullvadTypes
extension REST {
- public final class AddressCache {
+ 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
/// Logger.
private let logger = Logger(label: "AddressCache")
/// Memory cache.
- private var cachedAddresses: CachedAddresses = defaultCachedAddresses
+ var cache: CachedAddresses = defaultCachedAddresses
/// Cache file location.
- private let cacheFileURL: URL
+ public let cacheFileURL: URL
/// Lock used for synchronizing access to instance members.
private let cacheLock = NSLock()
@@ -28,7 +37,7 @@ extension REST {
private let canWriteToCache: Bool
/// The name of the cache file on disk
- internal static let cacheFileName = "api-ip-address.json"
+ public static let cacheFileName = "api-ip-address.json"
/// The default set of endpoints to use as a fallback mechanism
private static let defaultCachedAddresses = CachedAddresses(
@@ -49,8 +58,6 @@ extension REST {
self.cacheFileURL = cacheFileURL
self.canWriteToCache = canWriteToCache
-
- initCache()
}
/// Returns the latest available endpoint
@@ -60,14 +67,14 @@ extension REST {
public func getCurrentEndpoint() -> AnyIPEndpoint {
cacheLock.lock()
defer { cacheLock.unlock() }
- var currentEndpoint = cachedAddresses.endpoints.first ?? REST.defaultAPIEndpoint
+ var currentEndpoint = cache.endpoints.first ?? REST.defaultAPIEndpoint
// Reload from disk cache when in the Network Extension as there is no `AddressCacheTracker` running
// there
if canWriteToCache == false {
do {
- cachedAddresses = try readFromCache()
- if let firstEndpoint = cachedAddresses.endpoints.first {
+ cache = try readFromDisk()
+ if let firstEndpoint = cache.endpoints.first {
currentEndpoint = firstEndpoint
}
} catch {
@@ -92,10 +99,10 @@ extension REST {
defer { cacheLock.unlock() }
guard let firstEndpoint = endpoints.first else { return }
- if Set(cachedAddresses.endpoints) == Set(endpoints) {
- cachedAddresses.updatedAt = Date()
+ if Set(cache.endpoints) == Set(endpoints) {
+ cache.updatedAt = Date()
} else {
- cachedAddresses = CachedAddresses(
+ cache = CachedAddresses(
updatedAt: Date(),
endpoints: [firstEndpoint]
)
@@ -103,7 +110,7 @@ extension REST {
if canWriteToCache {
do {
- try writeToCache()
+ try writeToDisk(cache)
} catch {
logger.error(
error: error,
@@ -120,69 +127,21 @@ extension REST {
cacheLock.lock()
defer { cacheLock.unlock() }
- return cachedAddresses.updatedAt
+ return cache.updatedAt
}
- // 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() {
+ public 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 {
- cachedAddresses = try readFromCache()
+ cache = try readFromDisk()
} catch {
logger.debug("Initialized cache with default API endpoint.")
- cachedAddresses = Self.defaultCachedAddresses
- }
- }
-
- /// 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 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()
- }
-
- return cachedAddresses
- }
-
- return result
- }
-
- /// Writes the cache file to the disk
- private func writeToCache() throws {
- precondition(canWriteToCache == true)
- let fileCoordinator = NSFileCoordinator(filePresenter: nil)
-
- try fileCoordinator.coordinate(writingItemAt: cacheFileURL, options: [.forReplacing]) { file in
- let data = try JSONEncoder().encode(self.cachedAddresses)
- try data.write(to: file)
+ cache = Self.defaultCachedAddresses
}
}
}
-
- struct CachedAddresses: Codable {
- /// Date when the cached addresses were last updated.
- var updatedAt: Date
-
- /// API endpoints.
- var endpoints: [AnyIPEndpoint]
- }
-
- struct EmptyCacheError: LocalizedError {
- var errorDescription: String? {
- return "Address cache file does not contain any API addresses."
- }
- }
}
diff --git a/ios/MullvadTransport/TransportProvider.swift b/ios/MullvadTransport/TransportProvider.swift
index 821ea3d059..93274e9edb 100644
--- a/ios/MullvadTransport/TransportProvider.swift
+++ b/ios/MullvadTransport/TransportProvider.swift
@@ -18,11 +18,18 @@ public final class TransportProvider: RESTTransportProvider {
private let relayCache: RelayCache
private let logger = Logger(label: "TransportProvider")
private let addressCache: REST.AddressCache
+ private let shadowsocksCache: ShadowsocksConfigurationCache
- public init(urlSessionTransport: URLSessionTransport, relayCache: RelayCache, addressCache: REST.AddressCache) {
+ public init(
+ urlSessionTransport: URLSessionTransport,
+ relayCache: RelayCache,
+ addressCache: REST.AddressCache,
+ shadowsocksCache: ShadowsocksConfigurationCache
+ ) {
self.urlSessionTransport = urlSessionTransport
self.relayCache = relayCache
self.addressCache = addressCache
+ self.shadowsocksCache = shadowsocksCache
}
public func transport() -> RESTTransport? {
@@ -31,22 +38,12 @@ public final class TransportProvider: RESTTransportProvider {
public func shadowsocksTransport() -> RESTTransport? {
do {
- let cachedRelays = try relayCache.read()
- let shadowsocksConfiguration = RelaySelector.getShadowsocksTCPBridge(relays: cachedRelays.relays)
- let shadowsocksBridgeRelay = RelaySelector.getShadowsocksRelay(relays: cachedRelays.relays)
-
- guard let shadowsocksConfiguration,
- let shadowsocksBridgeRelay
- else {
- logger.error("Could not get shadow socks bridge information.")
- return nil
- }
+ let shadowsocksConfiguration = try shadowsocksConfiguration()
let shadowsocksURLSession = urlSessionTransport.urlSession
let shadowsocksTransport = URLSessionShadowsocksTransport(
urlSession: shadowsocksURLSession,
shadowsocksConfiguration: shadowsocksConfiguration,
- shadowsocksBridgeRelay: shadowsocksBridgeRelay,
addressCache: addressCache
)
@@ -56,4 +53,32 @@ public final class TransportProvider: RESTTransportProvider {
}
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
+ private func shadowsocksConfiguration() throws -> ShadowsocksConfiguration {
+ // If a previous shadowsocks configuration was in cache, return it directly
+ if let configuration = shadowsocksCache.configuration {
+ return configuration
+ }
+
+ // 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.
+ let cachedRelays = try relayCache.read()
+ let bridgeAddress = RelaySelector.getShadowsocksRelay(relays: cachedRelays.relays)?.ipv4AddrIn
+ let bridgeConfiguration = RelaySelector.getShadowsocksTCPBridge(relays: cachedRelays.relays)
+
+ guard let bridgeAddress, let bridgeConfiguration else { throw POSIXError(.ENOENT) }
+
+ let newConfiguration = ShadowsocksConfiguration(
+ bridgeAddress: bridgeAddress,
+ bridgePort: bridgeConfiguration.port,
+ password: bridgeConfiguration.password,
+ cipher: bridgeConfiguration.cipher
+ )
+ shadowsocksCache.configuration = newConfiguration
+ return newConfiguration
+ }
}
diff --git a/ios/MullvadTransport/URLSessionTransport.swift b/ios/MullvadTransport/URLSessionTransport.swift
index 49bc131895..8556f812ca 100644
--- a/ios/MullvadTransport/URLSessionTransport.swift
+++ b/ios/MullvadTransport/URLSessionTransport.swift
@@ -47,8 +47,7 @@ public final class URLSessionShadowsocksTransport: RESTTransport {
public init(
urlSession: URLSession,
- shadowsocksConfiguration: REST.ServerShadowsocks,
- shadowsocksBridgeRelay: REST.BridgeRelay,
+ shadowsocksConfiguration: ShadowsocksConfiguration,
addressCache: REST.AddressCache
) {
self.urlSession = urlSession
@@ -57,8 +56,8 @@ public final class URLSessionShadowsocksTransport: RESTTransport {
shadowsocksProxy = ShadowsocksProxy(
forwardAddress: apiAddress.ip,
forwardPort: apiAddress.port,
- bridgeAddress: shadowsocksBridgeRelay.ipv4AddrIn,
- bridgePort: shadowsocksConfiguration.port,
+ bridgeAddress: shadowsocksConfiguration.bridgeAddress,
+ bridgePort: shadowsocksConfiguration.bridgePort,
password: shadowsocksConfiguration.password,
cipher: shadowsocksConfiguration.cipher
)
diff --git a/ios/MullvadTypes/Cache.swift b/ios/MullvadTypes/Cache.swift
new file mode 100644
index 0000000000..6238e6c947
--- /dev/null
+++ b/ios/MullvadTypes/Cache.swift
@@ -0,0 +1,48 @@
+//
+// 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/ShadowsocksConfiguration.swift b/ios/MullvadTypes/ShadowsocksConfiguration.swift
new file mode 100644
index 0000000000..0a145f0976
--- /dev/null
+++ b/ios/MullvadTypes/ShadowsocksConfiguration.swift
@@ -0,0 +1,24 @@
+//
+// ShadowsocksConfiguration.swift
+// MullvadTransport
+//
+// Created by Marco Nikic on 2023-06-05.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Network
+
+public struct ShadowsocksConfiguration: Codable {
+ public let bridgeAddress: IPv4Address
+ public let bridgePort: UInt16
+ public let password: String
+ public let cipher: String
+
+ public init(bridgeAddress: IPv4Address, bridgePort: UInt16, password: String, cipher: String) {
+ self.bridgeAddress = bridgeAddress
+ self.bridgePort = bridgePort
+ self.password = password
+ self.cipher = cipher
+ }
+}
diff --git a/ios/MullvadTypes/ShadowsocksConfigurationCache.swift b/ios/MullvadTypes/ShadowsocksConfigurationCache.swift
new file mode 100644
index 0000000000..d8f035a8db
--- /dev/null
+++ b/ios/MullvadTypes/ShadowsocksConfigurationCache.swift
@@ -0,0 +1,57 @@
+//
+// 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 03d65f838e..89c6598405 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -386,6 +386,13 @@
A917351F29FAA9C400D5DCFD /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */; };
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; };
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, ); }; };
@@ -393,8 +400,8 @@
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 */; };
A9B2CF722A1F64CD0013CC6C /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
- A9CF11FD2A0518E7001D9565 /* AddressCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */; };
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */; };
A9D99BA02A1F7F3A00DE27D3 /* TransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */; };
A9D99BA52A1F808900DE27D3 /* RelayCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 063F02732902B63F001FA09F /* RelayCache.framework */; };
@@ -1109,10 +1116,17 @@
7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCoordinator.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
@@ -1359,6 +1373,7 @@
584D26BE270C550B004EA533 /* AnyIPAddress.swift */,
586A951329013235007BAF2B /* AnyIPEndpoint.swift */,
06AC113628F83FD70037AF9A /* Cancellable.swift */,
+ A9A8A8EA2A262AB30086D569 /* Cache.swift */,
58E511E328DDDE8900B0BCDE /* CustomErrorDescriptionProtocol.swift */,
586168682976F6BD00EF8598 /* DisplayError.swift */,
58E511EA28DDE18400B0BCDE /* Error+Chain.swift */,
@@ -1380,6 +1395,8 @@
5898D2AF2902A67C00EB5EBA /* RelayLocation.swift */,
581DA2722A1E227D0046ED47 /* RESTTypes.swift */,
58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */,
+ A9467E872A2DCD57000DC21F /* ShadowsocksConfiguration.swift */,
+ A9467E8A2A2E0317000DC21F /* ShadowsocksConfigurationCache.swift */,
58E511E028DDB7F100B0BCDE /* WrappingError.swift */,
);
path = MullvadTypes;
@@ -1863,14 +1880,20 @@
58B0A2A1238EE67E00BC001D /* MullvadVPNTests */ = {
isa = PBXGroup;
children = (
+ 58B0A2A4238EE67E00BC001D /* Info.plist */,
+ F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */,
+ 582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */,
+ A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */,
+ A9467E832A29E69F000DC21F /* CachedTests.swift */,
5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */,
58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */,
582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */,
- 58B0A2A4238EE67E00BC001D /* Info.plist */,
- F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */,
+ A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */,
584B26F3237434D00073B10E /* RelaySelectorTests.swift */,
+ A9467E852A29E9F3000DC21F /* ServerRelaysResponse+Mocks.swift */,
5807E2C1243203D000F5FF30 /* StringTests.swift */,
58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */,
+ A9467E812A29E0F8000DC21F /* TestsCacheFilePresenter.swift */,
);
path = MullvadVPNTests;
sourceTree = "<group>";
@@ -2120,7 +2143,6 @@
58FBFBE7291622580020E046 /* MullvadRESTTests */ = {
isa = PBXGroup;
children = (
- A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */,
58FBFBF0291630700020E046 /* DurationTests.swift */,
58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */,
A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */,
@@ -2131,10 +2153,10 @@
A97F1F422A1F4E1A00ECEFDE /* MullvadTransport */ = {
isa = PBXGroup;
children = (
+ A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */,
586F2BE129F6916F009E6924 /* shadowsocks.h */,
06FAE67C28F83CA50033DD93 /* URLSessionTransport.swift */,
01F1FF1B29F06124007083C3 /* ShadowsocksProxy.swift */,
- A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */,
A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */,
);
path = MullvadTransport;
@@ -2854,16 +2876,21 @@
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 */,
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;
};
@@ -3156,6 +3183,9 @@
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 */,
58D2240D294C90210029F5F8 /* CustomErrorDescriptionProtocol.swift in Sources */,
58D2240E294C90210029F5F8 /* Error+Chain.swift in Sources */,
586168692976F6BD00EF8598 /* DisplayError.swift in Sources */,
@@ -3192,7 +3222,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- A9CF11FD2A0518E7001D9565 /* AddressCacheTests.swift in Sources */,
A917352129FAAA5200D5DCFD /* TransportStrategyTests.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 0272c27b8a..2bdda88201 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -10,6 +10,7 @@ import BackgroundTasks
import MullvadLogging
import MullvadREST
import MullvadTransport
+import MullvadTypes
import Operations
import RelayCache
import StoreKit
@@ -50,7 +51,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
logger = Logger(label: "AppDelegate")
- addressCache = REST.AddressCache(canWriteToCache: true, cacheFolder: ApplicationConfiguration.containerURL)
+ let containerURL = ApplicationConfiguration.containerURL
+
+ addressCache = REST.AddressCache(canWriteToCache: true, cacheFolder: containerURL)
+ addressCache.initCache()
proxyFactory = REST.ProxyFactory.makeProxyFactory(
transportProvider: { [weak self] in self?.transportMonitor },
@@ -61,10 +65,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
accountsProxy = proxyFactory.createAccountsProxy()
devicesProxy = proxyFactory.createDevicesProxy()
- let relayCache = RelayCache(
- securityGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier
- )!
-
+ let relayCache = RelayCache(cacheFolder: containerURL)
relayCacheTracker = RelayCacheTracker(relayCache: relayCache, application: application, apiProxy: apiProxy)
addressCacheTracker = AddressCacheTracker(
@@ -91,10 +92,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
let urlSessionTransport = URLSessionTransport(urlSession: REST.makeURLSession())
+ let shadowsocksCache = ShadowsocksConfigurationCache(cacheFolder: containerURL)
let transportProvider = TransportProvider(
urlSessionTransport: urlSessionTransport,
relayCache: relayCache,
- addressCache: addressCache
+ addressCache: addressCache,
+ shadowsocksCache: shadowsocksCache
)
transportMonitor = TransportMonitor(
diff --git a/ios/MullvadRESTTests/AddressCacheTests.swift b/ios/MullvadVPNTests/AddressCacheTests.swift
index 85387adee7..4b9091d28c 100644
--- a/ios/MullvadRESTTests/AddressCacheTests.swift
+++ b/ios/MullvadVPNTests/AddressCacheTests.swift
@@ -10,36 +10,19 @@
import MullvadTypes
import XCTest
-final class AddressCacheTests: XCTestCase {
- static var testsCacheDirectory: URL!
+final class AddressCacheTests: CachedTests {
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 class var cacheFileName: String { REST.AddressCache.cacheFileName }
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
+ // MARK: - Tests
func testAddressCacheHasDefaultEndpoint() {
let cache = REST.AddressCache(canWriteToCache: false, cacheFolder: Self.testsCacheDirectory)
@@ -100,7 +83,7 @@ final class AddressCacheTests: XCTestCase {
}
}
- func testCacheReadsFromCachedFileAtInit() throws {
+ func testCacheReadsFromCachedFileWithInitCache() throws {
let didReadFromCache = expectation(description: "Cache was read")
cacheFilePresenter.onReaderAction = {
didReadFromCache.fulfill()
@@ -110,6 +93,7 @@ final class AddressCacheTests: XCTestCase {
let fixedDate = Date()
try prepopulateCache(at: cacheFileURL, fixedDate: fixedDate, with: [apiEndpoint])
let cache = REST.AddressCache(canWriteToCache: true, cacheFolder: cacheDirectory)
+ cache.initCache()
XCTAssertEqual(cache.getCurrentEndpoint(), apiEndpoint)
XCTAssertEqual(cache.getLastUpdateDate(), fixedDate)
@@ -141,8 +125,6 @@ final class AddressCacheTests: XCTestCase {
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()
}
@@ -159,8 +141,6 @@ final class AddressCacheTests: XCTestCase {
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()
}
@@ -179,20 +159,6 @@ final class AddressCacheTests: XCTestCase {
// 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:
@@ -205,31 +171,3 @@ extension AddressCacheTests {
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/MullvadVPNTests/CachedTests.swift b/ios/MullvadVPNTests/CachedTests.swift
new file mode 100644
index 0000000000..48b8f9a50d
--- /dev/null
+++ b/ios/MullvadVPNTests/CachedTests.swift
@@ -0,0 +1,56 @@
+//
+// 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/RelayCacheTests.swift b/ios/MullvadVPNTests/RelayCacheTests.swift
new file mode 100644
index 0000000000..05f1b33180
--- /dev/null
+++ b/ios/MullvadVPNTests/RelayCacheTests.swift
@@ -0,0 +1,61 @@
+//
+// RelayCacheTests.swift
+// MullvadVPNTests
+//
+// Created by Marco Nikic on 2023-06-02.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadREST
+import MullvadTransport
+@testable import RelayCache
+import XCTest
+
+final class RelayCacheTests: CachedTests {
+ override class var cacheFileName: String { RelayCache.cacheFileName }
+
+ 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)
+ }
+
+ 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))
+
+ let cachedContent = try Data(contentsOf: cacheFileURL)
+ let cachedRelays = try JSONDecoder().decode(CachedRelays.self, from: cachedContent)
+
+ XCTAssertEqual(cachedRelays.updatedAt, .distantPast)
+ }
+
+ waitForExpectations(timeout: defaultExpectationTimeout)
+ }
+}
+
+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)
+ }
+}
diff --git a/ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift b/ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift
new file mode 100644
index 0000000000..3eaa173d39
--- /dev/null
+++ b/ios/MullvadVPNTests/ServerRelaysResponse+Mocks.swift
@@ -0,0 +1,34 @@
+//
+// 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
new file mode 100644
index 0000000000..27a67b6d28
--- /dev/null
+++ b/ios/MullvadVPNTests/TestsCacheFilePresenter.swift
@@ -0,0 +1,35 @@
+//
+// 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 820b59e7e6..3691fecebc 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -65,9 +65,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
private var tunnelStartupFailureRecoveryTimer: DispatchSourceTimer?
/// Relay cache.
- private let relayCache = RelayCache(
- securityGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier
- )!
+ private let relayCache: RelayCache
/// Current selector result.
private var selectorResult: RelaySelectorResult?
@@ -136,14 +134,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
providerLogger = Logger(label: "PacketTunnelProvider")
tunnelLogger = Logger(label: "WireGuard")
- let addressCache = REST.AddressCache(canWriteToCache: false, cacheFolder: ApplicationConfiguration.containerURL)
+ let containerURL = ApplicationConfiguration.containerURL
+ let addressCache = REST.AddressCache(canWriteToCache: false, cacheFolder: containerURL)
+ addressCache.initCache()
+
+ relayCache = RelayCache(cacheFolder: containerURL)
let urlSession = REST.makeURLSession()
let urlSessionTransport = URLSessionTransport(urlSession: urlSession)
+ let shadowsocksCache = ShadowsocksConfigurationCache(cacheFolder: containerURL)
let transportProvider = TransportProvider(
urlSessionTransport: urlSessionTransport,
relayCache: relayCache,
- addressCache: addressCache
+ addressCache: addressCache,
+ shadowsocksCache: shadowsocksCache
)
let proxyFactory = REST.ProxyFactory.makeProxyFactory(
diff --git a/ios/RelayCache/CachedRelays.swift b/ios/RelayCache/CachedRelays.swift
index 6ff9c36091..75a775c650 100644
--- a/ios/RelayCache/CachedRelays.swift
+++ b/ios/RelayCache/CachedRelays.swift
@@ -12,13 +12,13 @@ import MullvadREST
/// A struct that represents the relay cache on disk
public struct CachedRelays: Codable {
/// E-tag returned by server
- public var etag: String?
+ public let etag: String?
/// The relay list stored within the cache entry
- public var relays: REST.ServerRelaysResponse
+ public let relays: REST.ServerRelaysResponse
/// The date when this cache was last updated
- public var updatedAt: Date
+ public let updatedAt: Date
public init(etag: String? = nil, relays: REST.ServerRelaysResponse, updatedAt: Date) {
self.etag = etag
diff --git a/ios/RelayCache/RelayCache.swift b/ios/RelayCache/RelayCache.swift
index 3c5ceb3a20..ed1cbf4c9e 100644
--- a/ios/RelayCache/RelayCache.swift
+++ b/ios/RelayCache/RelayCache.swift
@@ -8,30 +8,28 @@
import Foundation
import MullvadREST
+import MullvadTypes
-public final class RelayCache {
- /// Cache file location.
- let cacheFileURL: URL
-
- /// Location of pre-bundled relays file.
- let prebundledRelaysFileURL: URL
+public final class RelayCache: Caching {
+ public typealias CacheType = CachedRelays
- /// Initialize cache with default cache file location in app group container.
- public init?(securityGroupIdentifier: String) {
- guard let containerURL = FileManager.default.containerURL(
- forSecurityApplicationGroupIdentifier: securityGroupIdentifier
- ), let prebundledRelaysFileURL = Bundle(for: Self.self)
- .url(forResource: "relays", withExtension: "json") else { return nil }
+ /// Cache file location.
+ public let cacheFileURL: URL
+ public static let cacheFileName = "relays.json"
- cacheFileURL = containerURL.appendingPathComponent("relays.json", isDirectory: false)
- self.prebundledRelaysFileURL = prebundledRelaysFileURL
+ public init(cacheFolder: URL) {
+ let cacheFileURL = cacheFolder.appendingPathComponent(
+ Self.cacheFileName,
+ isDirectory: false
+ )
+ self.cacheFileURL = cacheFileURL
}
/// 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 readDiskCache()
+ return try readFromDisk()
} catch {
if error is DecodingError || (error as? CocoaError)?.code == .fileReadNoSuchFile {
return try readPrebundledRelays()
@@ -43,60 +41,13 @@ public final class RelayCache {
/// Safely write the cache file on disk using file coordinator.
public func write(record: CachedRelays) throws {
- var result: Result<Void, Error>?
- let fileCoordinator = NSFileCoordinator(filePresenter: nil)
-
- let accessor = { (fileURLForWriting: URL) in
- result = Result {
- let data = try JSONEncoder().encode(record)
- try data.write(to: fileURLForWriting)
- }
- }
-
- var error: NSError?
- fileCoordinator.coordinate(
- writingItemAt: cacheFileURL,
- options: [.forReplacing],
- error: &error,
- byAccessor: accessor
- )
-
- if let error {
- result = .failure(error)
- }
-
- try result?.get()
- }
-
- /// Safely read the cache file from disk using file coordinator.
- private func readDiskCache() throws -> CachedRelays {
- var result: Result<CachedRelays, Error>?
- let fileCoordinator = NSFileCoordinator(filePresenter: nil)
-
- let accessor = { (fileURLForReading: URL) in
- result = Result {
- let data = try Data(contentsOf: fileURLForReading)
- return try JSONDecoder().decode(CachedRelays.self, from: data)
- }
- }
-
- var error: NSError?
- fileCoordinator.coordinate(
- readingItemAt: cacheFileURL,
- options: [.withoutChanges],
- error: &error,
- byAccessor: accessor
- )
-
- if let error {
- result = .failure(error)
- }
-
- return try result!.get()
+ try writeToDisk(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) }
let data = try Data(contentsOf: prebundledRelaysFileURL)
let relays = try REST.Coding.makeJSONDecoder()
.decode(REST.ServerRelaysResponse.self, from: data)