summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/CHANGELOG.md1
-rw-r--r--ios/MullvadREST/Duration.swift62
-rw-r--r--ios/MullvadREST/ExponentialBackoff.swift54
-rw-r--r--ios/MullvadREST/RESTAPIProxy.swift1
-rw-r--r--ios/MullvadREST/RESTNetworkOperation.swift31
-rw-r--r--ios/MullvadREST/RESTRetryStrategy.swift80
-rw-r--r--ios/MullvadREST/ServerRelaysResponse.swift2
-rw-r--r--ios/MullvadREST/URLSessionTransport.swift32
-rw-r--r--ios/MullvadRESTTests/DurationTests.swift35
-rw-r--r--ios/MullvadRESTTests/ExponentialBackoffTests.swift59
-rw-r--r--ios/MullvadTypes/FixedWidthInteger+Arithmetics.swift (renamed from ios/PacketTunnel/FixedWidthInteger+Arithmetics.swift)8
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj136
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme10
-rw-r--r--ios/MullvadVPN/TransportMonitor/TransportMonitor.swift4
-rw-r--r--ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift1
15 files changed, 474 insertions, 42 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 73eca63dda..3a60a96ab9 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -39,6 +39,7 @@ Line wrap the file at 100 chars. Th
### Changed
- When logged into an account with no time left, a new view is shown instead of account settings,
with the option to buy more time.
+- Use exponential backoff with jitter for delay interval when retrying REST API requests.
### Fixed
- Improve random port distribution. Should be less biased towards port 53.
diff --git a/ios/MullvadREST/Duration.swift b/ios/MullvadREST/Duration.swift
new file mode 100644
index 0000000000..d773d944c1
--- /dev/null
+++ b/ios/MullvadREST/Duration.swift
@@ -0,0 +1,62 @@
+//
+// Duration.swift
+// MullvadREST
+//
+// Created by pronebird on 04/11/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+extension REST {
+ public struct Duration: Comparable {
+ public let milliseconds: UInt64
+
+ public var seconds: UInt64 {
+ return milliseconds / 1000
+ }
+
+ public var timeInterval: TimeInterval {
+ return TimeInterval(milliseconds) / 1000
+ }
+
+ private init(milliseconds: UInt64) {
+ self.milliseconds = milliseconds
+ }
+
+ public func format() -> String {
+ guard milliseconds >= 1000 else {
+ return "\(milliseconds)ms"
+ }
+
+ let trailingZeroesSuffix = ".00"
+ var string = String(format: "%.2f", timeInterval)
+
+ if string.hasSuffix(trailingZeroesSuffix) {
+ string.removeLast(trailingZeroesSuffix.count)
+ }
+
+ return "\(string)s"
+ }
+
+ public static func seconds(_ seconds: UInt64) -> Duration {
+ return Duration(milliseconds: seconds.saturatingMultiplication(1000))
+ }
+
+ public static func milliseconds(_ milliseconds: UInt64) -> Duration {
+ return Duration(milliseconds: milliseconds)
+ }
+
+ public static func < (lhs: Duration, rhs: Duration) -> Bool {
+ return lhs.milliseconds < rhs.milliseconds
+ }
+
+ public static func == (lhs: Duration, rhs: Duration) -> Bool {
+ return lhs.milliseconds == rhs.milliseconds
+ }
+
+ public static func * (lhs: Duration, factor: UInt64) -> Duration {
+ return Duration(milliseconds: lhs.milliseconds.saturatingMultiplication(factor))
+ }
+ }
+}
diff --git a/ios/MullvadREST/ExponentialBackoff.swift b/ios/MullvadREST/ExponentialBackoff.swift
new file mode 100644
index 0000000000..a765d53011
--- /dev/null
+++ b/ios/MullvadREST/ExponentialBackoff.swift
@@ -0,0 +1,54 @@
+//
+// ExponentialBackoff.swift
+// MullvadREST
+//
+// Created by pronebird on 03/11/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadTypes
+
+struct ExponentialBackoff: IteratorProtocol {
+ private var _next: REST.Duration
+ private let multiplier: UInt64
+ private let maxDelay: REST.Duration?
+
+ init(initial: REST.Duration, multiplier: UInt64, maxDelay: REST.Duration? = nil) {
+ _next = initial
+ self.multiplier = multiplier
+ self.maxDelay = maxDelay
+ }
+
+ mutating func next() -> REST.Duration? {
+ let next = _next
+
+ if let maxDelay = maxDelay, next > maxDelay {
+ return maxDelay
+ }
+
+ _next = next * multiplier
+
+ return next
+ }
+}
+
+struct Jittered<InnerIterator: IteratorProtocol>: IteratorProtocol
+ where InnerIterator.Element == REST.Duration
+{
+ private var inner: InnerIterator
+
+ init(_ inner: InnerIterator) {
+ self.inner = inner
+ }
+
+ mutating func next() -> REST.Duration? {
+ guard let interval = inner.next() else { return nil }
+
+ let jitter = Double.random(in: 0.0 ... 1.0)
+ let millis = interval.milliseconds
+ let millisWithJitter = millis.saturatingAddition(UInt64(Double(millis) * jitter))
+
+ return .milliseconds(millisWithJitter)
+ }
+}
diff --git a/ios/MullvadREST/RESTAPIProxy.swift b/ios/MullvadREST/RESTAPIProxy.swift
index 2cf3ee4c86..5defa28a24 100644
--- a/ios/MullvadREST/RESTAPIProxy.swift
+++ b/ios/MullvadREST/RESTAPIProxy.swift
@@ -8,7 +8,6 @@
import Foundation
import MullvadTypes
-import Network
import struct WireGuardKitTypes.IPAddressRange
import class WireGuardKitTypes.PublicKey
diff --git a/ios/MullvadREST/RESTNetworkOperation.swift b/ios/MullvadREST/RESTNetworkOperation.swift
index 0db21bfd05..3e188094c9 100644
--- a/ios/MullvadREST/RESTNetworkOperation.swift
+++ b/ios/MullvadREST/RESTNetworkOperation.swift
@@ -27,6 +27,7 @@ extension REST {
private var retryInvalidAccessTokenError = true
private let retryStrategy: RetryStrategy
+ private var retryDelayIterator: AnyIterator<Duration>
private var retryTimer: DispatchSourceTimer?
private var retryCount = 0
@@ -42,6 +43,7 @@ extension REST {
addressCacheStore = configuration.addressCacheStore
transportRegistry = configuration.transportRegistry
self.retryStrategy = retryStrategy
+ retryDelayIterator = retryStrategy.makeDelayIterator()
self.requestHandler = requestHandler
self.responseHandler = responseHandler
@@ -270,23 +272,28 @@ extension REST {
if retryStrategy.maxRetryCount > 0 {
logger.debug("Ran out of retry attempts (\(retryStrategy.maxRetryCount))")
}
-
- let restError: REST.Error = (error as? URLError).map { .network($0) }
- ?? .transport(error)
-
- finish(completion: .failure(restError))
+ finish(completion: .failure(wrapRequestError(error)))
return
}
// Increment retry count.
retryCount += 1
- // Retry immediatly if retry delay is set to never.
- guard retryStrategy.retryDelay != .never else {
+ // Retry immediately if retry delay is set to never.
+ guard retryStrategy.delay != .never else {
startRequest()
return
}
+ guard let waitDelay = retryDelayIterator.next() else {
+ logger.debug("Retry delay iterator failed to produce next value.")
+
+ finish(completion: .failure(wrapRequestError(error)))
+ return
+ }
+
+ logger.debug("Retry in \(waitDelay.format()).")
+
// Create timer to delay retry.
let timer = DispatchSource.makeTimerSource(queue: dispatchQueue)
@@ -298,10 +305,18 @@ extension REST {
self?.finish(completion: .cancelled)
}
- timer.schedule(wallDeadline: .now() + retryStrategy.retryDelay)
+ timer.schedule(wallDeadline: .now() + waitDelay.timeInterval)
timer.activate()
retryTimer = timer
}
+
+ private func wrapRequestError(_ error: Swift.Error) -> REST.Error {
+ if let error = error as? URLError {
+ return .network(error)
+ } else {
+ return .transport(error)
+ }
+ }
}
}
diff --git a/ios/MullvadREST/RESTRetryStrategy.swift b/ios/MullvadREST/RESTRetryStrategy.swift
index e88ebf072d..a3b4873de1 100644
--- a/ios/MullvadREST/RESTRetryStrategy.swift
+++ b/ios/MullvadREST/RESTRetryStrategy.swift
@@ -11,15 +11,83 @@ import Foundation
extension REST {
public struct RetryStrategy {
public var maxRetryCount: Int
- public var retryDelay: DispatchTimeInterval
+ public var delay: RetryDelay
+ public var applyJitter: Bool
+
+ public init(maxRetryCount: Int, delay: RetryDelay, applyJitter: Bool) {
+ self.maxRetryCount = maxRetryCount
+ self.delay = delay
+ self.applyJitter = applyJitter
+ }
+
+ public func makeDelayIterator() -> AnyIterator<Duration> {
+ let inner = delay.makeIterator()
+
+ if applyJitter {
+ return AnyIterator(Jittered(inner))
+ } else {
+ return AnyIterator(inner)
+ }
+ }
/// Strategy configured to never retry.
- public static var noRetry = RetryStrategy(maxRetryCount: 0, retryDelay: .never)
+ public static var noRetry = RetryStrategy(
+ maxRetryCount: 0,
+ delay: .never,
+ applyJitter: false
+ )
+
+ /// Startegy configured with 3 retry attempts and exponential backoff.
+ public static var `default` = RetryStrategy(
+ maxRetryCount: 3,
+ delay: defaultRetryDelay,
+ applyJitter: true
+ )
+
+ /// Strategy configured with 10 retry attempts and exponential backoff.
+ public static var aggressive = RetryStrategy(
+ maxRetryCount: 10,
+ delay: defaultRetryDelay,
+ applyJitter: true
+ )
+
+ /// Default retry delay.
+ public static var defaultRetryDelay: RetryDelay = .exponentialBackoff(
+ initial: .seconds(2),
+ multiplier: 2,
+ maxDelay: .seconds(8)
+ )
+ }
+
+ public enum RetryDelay: Equatable {
+ /// Never wait to retry.
+ case never
+
+ /// Constant delay.
+ case constant(Duration)
+
+ /// Exponential backoff.
+ case exponentialBackoff(initial: Duration, multiplier: UInt64, maxDelay: Duration?)
+
+ func makeIterator() -> AnyIterator<Duration> {
+ switch self {
+ case .never:
+ return AnyIterator {
+ return nil
+ }
- /// Startegy configured with 3 retry attempts with 2 seconds delay between.
- public static var `default` = RetryStrategy(maxRetryCount: 3, retryDelay: .seconds(2))
+ case let .constant(duration):
+ return AnyIterator {
+ return duration
+ }
- /// Strategy configured with 10 retry attempts with 2 seconds delay between.
- public static var aggressive = RetryStrategy(maxRetryCount: 10, retryDelay: .seconds(2))
+ case let .exponentialBackoff(initial, multiplier, maxDelay):
+ return AnyIterator(ExponentialBackoff(
+ initial: initial,
+ multiplier: multiplier,
+ maxDelay: maxDelay
+ ))
+ }
+ }
}
}
diff --git a/ios/MullvadREST/ServerRelaysResponse.swift b/ios/MullvadREST/ServerRelaysResponse.swift
index be446cfbf0..1327b596af 100644
--- a/ios/MullvadREST/ServerRelaysResponse.swift
+++ b/ios/MullvadREST/ServerRelaysResponse.swift
@@ -1,6 +1,6 @@
//
// ServerRelaysResponse.swift
-// ServerRelaysResponse
+// MullvadREST
//
// Created by pronebird on 27/07/2021.
// Copyright © 2021 Mullvad VPN AB. All rights reserved.
diff --git a/ios/MullvadREST/URLSessionTransport.swift b/ios/MullvadREST/URLSessionTransport.swift
index f8bd4cca24..4acc155d58 100644
--- a/ios/MullvadREST/URLSessionTransport.swift
+++ b/ios/MullvadREST/URLSessionTransport.swift
@@ -11,23 +11,25 @@ import MullvadTypes
extension URLSessionTask: Cancellable {}
-public final class URLSessionTransport: RESTTransport {
- public var name: String {
- return "url-session"
- }
+extension REST {
+ public final class URLSessionTransport: RESTTransport {
+ public var name: String {
+ return "url-session"
+ }
- public let urlSession: URLSession
+ public let urlSession: URLSession
- public init(urlSession: URLSession) {
- self.urlSession = urlSession
- }
+ public init(urlSession: URLSession) {
+ self.urlSession = urlSession
+ }
- public func sendRequest(
- _ request: URLRequest,
- completion: @escaping (Data?, URLResponse?, Error?) -> Void
- ) throws -> Cancellable {
- let dataTask = urlSession.dataTask(with: request, completionHandler: completion)
- dataTask.resume()
- return dataTask
+ public func sendRequest(
+ _ request: URLRequest,
+ completion: @escaping (Data?, URLResponse?, Swift.Error?) -> Void
+ ) throws -> Cancellable {
+ let dataTask = urlSession.dataTask(with: request, completionHandler: completion)
+ dataTask.resume()
+ return dataTask
+ }
}
}
diff --git a/ios/MullvadRESTTests/DurationTests.swift b/ios/MullvadRESTTests/DurationTests.swift
new file mode 100644
index 0000000000..c5b5906c02
--- /dev/null
+++ b/ios/MullvadRESTTests/DurationTests.swift
@@ -0,0 +1,35 @@
+//
+// DurationTests.swift
+// MullvadRESTTests
+//
+// Created by pronebird on 05/11/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadREST
+import XCTest
+
+final class DurationTests: XCTestCase {
+ func testComparable() throws {
+ XCTAssertEqual(REST.Duration.milliseconds(1000), .seconds(1))
+ XCTAssertEqual(REST.Duration.milliseconds(.max), .seconds(.max))
+
+ XCTAssertGreaterThan(REST.Duration.milliseconds(1001), .seconds(1))
+ XCTAssertGreaterThanOrEqual(REST.Duration.seconds(1), .milliseconds(1000))
+
+ XCTAssertLessThan(REST.Duration.milliseconds(999), .seconds(1))
+ XCTAssertLessThanOrEqual(REST.Duration.seconds(1), .milliseconds(1000))
+ }
+
+ func testMultiplication() throws {
+ XCTAssertEqual(REST.Duration.seconds(4) * 4, .seconds(16))
+ XCTAssertEqual(REST.Duration.seconds(4) * 4, .seconds(16))
+ XCTAssertEqual(REST.Duration.milliseconds(.max - 1) * 2, .milliseconds(.max))
+ }
+
+ func testFormat() throws {
+ XCTAssertEqual(REST.Duration.milliseconds(999).format(), "999ms")
+ XCTAssertEqual(REST.Duration.milliseconds(1000).format(), "1s")
+ XCTAssertEqual(REST.Duration.milliseconds(1200).format(), "1.20s")
+ }
+}
diff --git a/ios/MullvadRESTTests/ExponentialBackoffTests.swift b/ios/MullvadRESTTests/ExponentialBackoffTests.swift
new file mode 100644
index 0000000000..85529e260a
--- /dev/null
+++ b/ios/MullvadRESTTests/ExponentialBackoffTests.swift
@@ -0,0 +1,59 @@
+//
+// ExponentialBackoffTests.swift
+// ExponentialBackoffTests
+//
+// Created by pronebird on 05/11/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadREST
+import XCTest
+
+final class ExponentialBackoffTests: XCTestCase {
+ func testExponentialBackoff() {
+ var backoff = ExponentialBackoff(initial: .seconds(2), multiplier: 3)
+
+ XCTAssertEqual(backoff.next(), .seconds(2))
+ XCTAssertEqual(backoff.next(), .seconds(6))
+ XCTAssertEqual(backoff.next(), .seconds(18))
+ }
+
+ func testAtMaximumValue() {
+ var backoff = ExponentialBackoff(initial: .milliseconds(.max - 1), multiplier: 2)
+
+ XCTAssertEqual(backoff.next(), .milliseconds(.max - 1))
+ XCTAssertEqual(backoff.next(), .seconds(.max))
+ XCTAssertEqual(backoff.next(), .seconds(.max))
+ }
+
+ func testMaximumBound() {
+ var backoff = ExponentialBackoff(
+ initial: .milliseconds(2),
+ multiplier: 3,
+ maxDelay: .milliseconds(7)
+ )
+
+ XCTAssertEqual(backoff.next(), .milliseconds(2))
+ XCTAssertEqual(backoff.next(), .milliseconds(6))
+ XCTAssertEqual(backoff.next(), .milliseconds(7))
+ }
+
+ func testMinimumValue() {
+ var backoff = ExponentialBackoff(initial: .milliseconds(0), multiplier: 10)
+
+ XCTAssertEqual(backoff.next(), .milliseconds(0))
+ XCTAssertEqual(backoff.next(), .milliseconds(0))
+
+ backoff = ExponentialBackoff(initial: .milliseconds(1), multiplier: 0)
+
+ XCTAssertEqual(backoff.next(), .milliseconds(1))
+ XCTAssertEqual(backoff.next(), .milliseconds(0))
+ }
+
+ func testJitter() {
+ let initial = REST.Duration.milliseconds(500)
+ var iterator = Jittered(ExponentialBackoff(initial: initial, multiplier: 3))
+
+ XCTAssertGreaterThanOrEqual(iterator.next()!, initial)
+ }
+}
diff --git a/ios/PacketTunnel/FixedWidthInteger+Arithmetics.swift b/ios/MullvadTypes/FixedWidthInteger+Arithmetics.swift
index 71d1ace4d2..31913775b1 100644
--- a/ios/PacketTunnel/FixedWidthInteger+Arithmetics.swift
+++ b/ios/MullvadTypes/FixedWidthInteger+Arithmetics.swift
@@ -11,7 +11,7 @@ import Foundation
extension FixedWidthInteger {
/// Saturating integer multiplication. Computes `self * rhs`, saturating at the numeric bounds
/// instead of overflowing.
- func saturatingMultiplication(_ rhs: Self) -> Self {
+ public func saturatingMultiplication(_ rhs: Self) -> Self {
let (partialValue, isOverflow) = multipliedReportingOverflow(by: rhs)
if isOverflow {
@@ -23,7 +23,7 @@ extension FixedWidthInteger {
/// Saturating integer addition. Computes `self + rhs`, saturating at the numeric bounds
/// instead of overflowing.
- func saturatingAddition(_ rhs: Self) -> Self {
+ public func saturatingAddition(_ rhs: Self) -> Self {
let (partialValue, isOverflow) = addingReportingOverflow(rhs)
if isOverflow {
@@ -35,7 +35,7 @@ extension FixedWidthInteger {
/// Saturating integer subtraction. Computes `self - rhs`, saturating at the numeric bounds
/// instead of overflowing.
- func saturatingSubtraction(_ rhs: Self) -> Self {
+ public func saturatingSubtraction(_ rhs: Self) -> Self {
let (partialValue, isOverflow) = subtractingReportingOverflow(rhs)
if isOverflow {
@@ -47,7 +47,7 @@ extension FixedWidthInteger {
/// Saturating integer exponentiation. Computes `self ** exp`, saturating at the numeric
/// bounds instead of overflowing.
- func saturatingPow(_ exp: UInt32) -> Self {
+ public func saturatingPow(_ exp: UInt32) -> Self {
let result = pow(Double(self), Double(exp))
if result.isFinite {
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index b8d6b404c8..a1cea3ff0a 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -101,7 +101,6 @@
58293FB3251241B4005D0BB5 /* CustomTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB2251241B3005D0BB5 /* CustomTextView.swift */; };
58293FB725138B88005D0BB5 /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB625138B88005D0BB5 /* CustomNavigationController.swift */; };
582A8A3A28BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */; };
- 582A8A3B28BCE1AB00D0F9FB /* FixedWidthInteger+Arithmetics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */; };
582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */; };
582AE3122440CA0D00E6733A /* AccountTokenInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */; };
582AE3132440CA2700E6733A /* AccountTokenInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */; };
@@ -193,7 +192,6 @@
5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD82227B11080051EB06 /* SelectLocationCell.swift */; };
5888AD87227B17950051EB06 /* SelectLocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD86227B17950051EB06 /* SelectLocationViewController.swift */; };
588E4EAE28FEEDD8008046E3 /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
- 58900D0328BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */; };
58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */; };
58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */; };
5891BF1C25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */; };
@@ -205,6 +203,8 @@
5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */; };
5896AE88246D7FAF005B36CB /* CustomDateComponentsFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */; };
5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896CEF126972DEB00B0FAE8 /* AccountContentView.swift */; };
+ 5897F1742913EAF800AF5695 /* ExponentialBackoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5897F1732913EAF800AF5695 /* ExponentialBackoff.swift */; };
+ 5897F1762914E62E00AF5695 /* Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5897F1752914E62E00AF5695 /* Duration.swift */; };
5898D29029017BEE00EB5EBA /* PacketTunnelOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587C575226D2615F005EF767 /* PacketTunnelOptions.swift */; };
5898D29129017C3100EB5EBA /* TunnelProviderMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */; };
5898D29229017CA000EB5EBA /* ProxyURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063687AF28EB083800BE7161 /* ProxyURLRequest.swift */; };
@@ -231,6 +231,7 @@
589A455F28E094BF00565204 /* OperationConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580CBFB72848D503007878F0 /* OperationConditionTests.swift */; };
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */; };
58A3BDB028A1821A00C8C2C6 /* WgStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */; };
+ 58A8B0842913C6F7004B59B1 /* FixedWidthInteger+Arithmetics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */; };
58A99ED3240014A0006599E9 /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */; };
58AC829428F803A200181C40 /* libMullvadLogging.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 581943D628F800C900B0CB5E /* libMullvadLogging.a */; };
58AC829528F803A200181C40 /* libMullvadTypes.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 581943F128F8014500B0CB5E /* libMullvadTypes.a */; };
@@ -303,6 +304,9 @@
58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */; };
58FB865526E8BF3100F188BC /* StorePaymentManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */; };
58FB865A26EA214400F188BC /* RelayCacheTrackerObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865926EA214400F188BC /* RelayCacheTrackerObserver.swift */; };
+ 58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */; };
+ 58FBFBEA291622580020E046 /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
+ 58FBFBF1291630700020E046 /* DurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBF0291630700020E046 /* DurationTests.swift */; };
58FC040A27B3EE03001C21F0 /* TunnelMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */; };
58FD5BE724192A2C00112C88 /* StoreReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BE624192A2B00112C88 /* StoreReceipt.swift */; };
58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; };
@@ -433,6 +437,13 @@
remoteGlobalIDString = 58FBDA9722A519BC00EB69A3;
remoteInfo = WireGuardGoBridge;
};
+ 58FBFBEB291622580020E046 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 06799ABB28F98E1D00ACD94E;
+ remoteInfo = MullvadREST;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -691,6 +702,8 @@
5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDateComponentsFormatting.swift; sourceTree = "<group>"; };
5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDateComponentsFormattingTests.swift; sourceTree = "<group>"; };
5896CEF126972DEB00B0FAE8 /* AccountContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountContentView.swift; sourceTree = "<group>"; };
+ 5897F1732913EAF800AF5695 /* ExponentialBackoff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExponentialBackoff.swift; sourceTree = "<group>"; };
+ 5897F1752914E62E00AF5695 /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = "<group>"; };
5898D28929017BD400EB5EBA /* libTunnelProviderMessaging.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTunnelProviderMessaging.a; sourceTree = BUILT_PRODUCTS_DIR; };
5898D29829017DAC00EB5EBA /* libRelaySelector.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRelaySelector.a; sourceTree = BUILT_PRODUCTS_DIR; };
5898D2A7290182B000EB5EBA /* TunnelProviderReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelProviderReply.swift; sourceTree = "<group>"; };
@@ -784,6 +797,9 @@
58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportReviewViewController.swift; sourceTree = "<group>"; };
58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentManagerError.swift; sourceTree = "<group>"; };
58FB865926EA214400F188BC /* RelayCacheTrackerObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTrackerObserver.swift; sourceTree = "<group>"; };
+ 58FBFBE6291622580020E046 /* MullvadRESTTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadRESTTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExponentialBackoffTests.swift; sourceTree = "<group>"; };
+ 58FBFBF0291630700020E046 /* DurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DurationTests.swift; sourceTree = "<group>"; };
58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitor.swift; sourceTree = "<group>"; };
58FD5BE624192A2B00112C88 /* StoreReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreReceipt.swift; sourceTree = "<group>"; };
58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+Formatting.swift"; sourceTree = "<group>"; };
@@ -916,6 +932,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58FBFBE3291622580020E046 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 58FBFBEA291622580020E046 /* MullvadREST.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -972,6 +996,8 @@
06FAE67428F83CA40033DD93 /* RESTRequestHandler.swift */,
06FAE66628F83CA30033DD93 /* RESTResponseHandler.swift */,
06FAE67628F83CA40033DD93 /* RESTRetryStrategy.swift */,
+ 5897F1752914E62E00AF5695 /* Duration.swift */,
+ 5897F1732913EAF800AF5695 /* ExponentialBackoff.swift */,
06FAE67528F83CA40033DD93 /* RESTTaskIdentifier.swift */,
06FAE67D28F83CA50033DD93 /* RESTTransport.swift */,
06FAE66D28F83CA40033DD93 /* RESTTransportRegistry.swift */,
@@ -1029,6 +1055,7 @@
5898D2AF2902A67C00EB5EBA /* RelayLocation.swift */,
585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */,
5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */,
+ 58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */,
);
path = MullvadTypes;
sourceTree = "<group>";
@@ -1194,6 +1221,7 @@
581943D728F800C900B0CB5E /* MullvadLogging */,
581943F228F8014500B0CB5E /* MullvadTypes */,
06799ABD28F98E1D00ACD94E /* MullvadREST */,
+ 58FBFBE7291622580020E046 /* MullvadRESTTests */,
063F02742902B63F001FA09F /* RelayCache */,
5898D29929017DAC00EB5EBA /* RelaySelector */,
5898D28A29017BD400EB5EBA /* TunnelProviderMessaging */,
@@ -1220,6 +1248,7 @@
063F02732902B63F001FA09F /* RelayCache.framework */,
5898D28929017BD400EB5EBA /* libTunnelProviderMessaging.a */,
5898D29829017DAC00EB5EBA /* libRelaySelector.a */,
+ 58FBFBE6291622580020E046 /* MullvadRESTTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -1366,7 +1395,6 @@
58E0729C28814AAE008902F8 /* PacketTunnelConfiguration.swift */,
58CE5E7B224146470008646E /* PacketTunnelProvider.swift */,
58E072A028814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift */,
- 58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */,
58E072A228814B96008902F8 /* TunnelMonitor */,
58E07298288031D5008902F8 /* WireGuardAdapterError+Localization.swift */,
58E0729E28814ACC008902F8 /* WireGuardLogLevel+Logging.swift */,
@@ -1442,6 +1470,15 @@
path = Assets;
sourceTree = "<group>";
};
+ 58FBFBE7291622580020E046 /* MullvadRESTTests */ = {
+ isa = PBXGroup;
+ children = (
+ 58FBFBF0291630700020E046 /* DurationTests.swift */,
+ 58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */,
+ );
+ path = MullvadRESTTests;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -1728,6 +1765,24 @@
productReference = 58E5126528DDF04200B0BCDE /* libOperations.a */;
productType = "com.apple.product-type.library.static";
};
+ 58FBFBE5291622580020E046 /* MullvadRESTTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 58FBFBEF291622580020E046 /* Build configuration list for PBXNativeTarget "MullvadRESTTests" */;
+ buildPhases = (
+ 58FBFBE2291622580020E046 /* Sources */,
+ 58FBFBE3291622580020E046 /* Frameworks */,
+ 58FBFBE4291622580020E046 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 58FBFBEC291622580020E046 /* PBXTargetDependency */,
+ );
+ name = MullvadRESTTests;
+ productName = MullvadRESTTests;
+ productReference = 58FBFBE6291622580020E046 /* MullvadRESTTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -1794,6 +1849,9 @@
58FBDA9722A519BC00EB69A3 = {
CreatedOnToolsVersion = 10.2.1;
};
+ 58FBFBE5291622580020E046 = {
+ CreatedOnToolsVersion = 14.1;
+ };
};
};
buildConfigurationList = 58CE5E5B224146200008646E /* Build configuration list for PBXProject "MullvadVPN" */;
@@ -1823,6 +1881,7 @@
581943D528F800C900B0CB5E /* MullvadLogging */,
581943F028F8014500B0CB5E /* MullvadTypes */,
06799ABB28F98E1D00ACD94E /* MullvadREST */,
+ 58FBFBE5291622580020E046 /* MullvadRESTTests */,
063F02722902B63F001FA09F /* RelayCache */,
5898D29729017DAC00EB5EBA /* RelaySelector */,
5898D28829017BD300EB5EBA /* TunnelProviderMessaging */,
@@ -1887,6 +1946,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58FBFBE4291622580020E046 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -1965,11 +2031,13 @@
58505FFA290A7F0F00118C23 /* ApplicationConfiguration.swift in Sources */,
06799AE028F98E4800ACD94E /* RESTCoding.swift in Sources */,
06799AFC28F98EE300ACD94E /* AddressCache.swift in Sources */,
+ 5897F1762914E62E00AF5695 /* Duration.swift in Sources */,
06799AF028F98E4800ACD94E /* REST.swift in Sources */,
06799ADF28F98E4800ACD94E /* RESTDevicesProxy.swift in Sources */,
06799ADA28F98E4800ACD94E /* RESTResponseHandler.swift in Sources */,
062B45BC28FD8C3B00746E77 /* RESTDefaults.swift in Sources */,
06799AE428F98E4800ACD94E /* RESTAccountsProxy.swift in Sources */,
+ 5897F1742913EAF800AF5695 /* ExponentialBackoff.swift in Sources */,
06799AE328F98E4800ACD94E /* RESTNetworkOperation.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1997,6 +2065,7 @@
5898D2B82902ABC400EB5EBA /* KeychainError.swift in Sources */,
581943FC28F8020500B0CB5E /* Error+Chain.swift in Sources */,
5898D29329017CFD00EB5EBA /* Location.swift in Sources */,
+ 58A8B0842913C6F7004B59B1 /* FixedWidthInteger+Arithmetics.swift in Sources */,
581943FB28F801D500B0CB5E /* CustomErrorDescriptionProtocol.swift in Sources */,
5898D2B52902A8F000EB5EBA /* RelayConstraint.swift in Sources */,
5898D2B42902A8F000EB5EBA /* RelayConstraints.swift in Sources */,
@@ -2057,7 +2126,6 @@
5807E2C3243203E700F5FF30 /* String+Split.swift in Sources */,
58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */,
5819C2152726CC9400D6EC38 /* DataSourceSnapshot.swift in Sources */,
- 582A8A3B28BCE1AB00D0F9FB /* FixedWidthInteger+Arithmetics.swift in Sources */,
5896AE88246D7FAF005B36CB /* CustomDateComponentsFormatting.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2241,7 +2309,6 @@
580F8B872819795C002E0998 /* DNSSettings.swift in Sources */,
58E072A128814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift in Sources */,
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */,
- 58900D0328BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift in Sources */,
58A3BDB028A1821A00C8C2C6 /* WgStats.swift in Sources */,
5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */,
58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */,
@@ -2284,6 +2351,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58FBFBE2291622580020E046 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */,
+ 58FBFBF1291630700020E046 /* DurationTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -2367,6 +2443,11 @@
target = 58FBDA9722A519BC00EB69A3 /* WireGuardGoBridge */;
targetProxy = 58FBDAA122A52A6800EB69A3 /* PBXContainerItemProxy */;
};
+ 58FBFBEC291622580020E046 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 06799ABB28F98E1D00ACD94E /* MullvadREST */;
+ targetProxy = 58FBFBEB291622580020E046 /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -3008,6 +3089,42 @@
};
name = Release;
};
+ 58FBFBED291622580020E046 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = CKG9MXH72F;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.MullvadRESTTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 58FBFBEE291622580020E046 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = CKG9MXH72F;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.MullvadRESTTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -3137,6 +3254,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 58FBFBEF291622580020E046 /* Build configuration list for PBXNativeTarget "MullvadRESTTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 58FBFBED291622580020E046 /* Debug */,
+ 58FBFBEE291622580020E046 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
index 5f4b4b5a25..dc70ad4ec7 100644
--- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
@@ -72,6 +72,16 @@
ReferencedContainer = "container:MullvadVPN.xcodeproj">
</BuildableReference>
</TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58FBFBE5291622580020E046"
+ BuildableName = "MullvadRESTTests.xctest"
+ BlueprintName = "MullvadRESTTests"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
</Testables>
</TestAction>
<LaunchAction
diff --git a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
index 140a9b3027..7b0e49f70b 100644
--- a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
+++ b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
@@ -12,13 +12,13 @@ import MullvadREST
class TransportMonitor: TunnelObserver {
private let tunnelManager: TunnelManager
private let packetTunnelTransport: PacketTunnelTransport
- private let urlSessionTransport: URLSessionTransport
+ private let urlSessionTransport: REST.URLSessionTransport
init(tunnelManager: TunnelManager = .shared) {
self.tunnelManager = tunnelManager
packetTunnelTransport = PacketTunnelTransport(tunnelManager: tunnelManager)
- urlSessionTransport = URLSessionTransport(urlSession: REST.makeURLSession())
+ urlSessionTransport = REST.URLSessionTransport(urlSession: REST.makeURLSession())
tunnelManager.addObserver(self)
diff --git a/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift b/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift
index 00a43108d9..4614c8e733 100644
--- a/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift
+++ b/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift
@@ -8,6 +8,7 @@
import Foundation
import MullvadLogging
+import MullvadTypes
import NetworkExtension
import WireGuardKit