summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2023-08-11 16:26:48 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-08-28 17:16:06 +0200
commitbbb542e202b70bf921b050e6022cabb067988410 (patch)
tree28f702fb3f710364d45048ea57c2b66ca935d9dd /ios
parent6fd0f1213ce886b149516ed0d0a722988f77b277 (diff)
downloadmullvadvpn-bbb542e202b70bf921b050e6022cabb067988410.tar.xz
mullvadvpn-bbb542e202b70bf921b050e6022cabb067988410.zip
Introduce Duration type that mimicks native iOS16 Duration
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadLogging/LogFileOutputStream.swift5
-rw-r--r--ios/MullvadREST/Duration.swift62
-rw-r--r--ios/MullvadREST/ExponentialBackoff.swift16
-rw-r--r--ios/MullvadREST/RESTDefaults.swift2
-rw-r--r--ios/MullvadREST/RESTNetworkOperation.swift2
-rw-r--r--ios/MullvadREST/RESTRequestFactory.swift6
-rw-r--r--ios/MullvadREST/RESTRetryStrategy.swift1
-rw-r--r--ios/MullvadRESTTests/DurationTests.swift35
-rw-r--r--ios/MullvadRESTTests/ExponentialBackoffTests.swift7
-rw-r--r--ios/MullvadTypes/Duration+Extensions.swift76
-rw-r--r--ios/MullvadTypes/Duration.swift93
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj18
-rw-r--r--ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift12
-rw-r--r--ios/MullvadVPN/Classes/AccountDataThrottling.swift7
-rw-r--r--ios/MullvadVPN/Classes/DeviceDataThrottling.swift5
-rw-r--r--ios/MullvadVPN/Coordinators/LoginCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift3
-rw-r--r--ios/MullvadVPN/Operations/ProductsRequestOperation.swift3
-rw-r--r--ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift7
-rw-r--r--ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift16
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/WgKeyRotation.swift21
-rw-r--r--ios/MullvadVPN/UI appearance/UIMetrics.swift5
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift8
-rw-r--r--ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift9
-rw-r--r--ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift7
-rw-r--r--ios/MullvadVPNTests/DeviceCheckOperationTests.swift6
-rw-r--r--ios/MullvadVPNTests/DurationTests.swift71
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift10
-rw-r--r--ios/PacketTunnel/WgAdapterDeviceInfo.swift3
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift27
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/MockPinger.swift5
33 files changed, 367 insertions, 199 deletions
diff --git a/ios/MullvadLogging/LogFileOutputStream.swift b/ios/MullvadLogging/LogFileOutputStream.swift
index 0ffd434c65..a3b7ca4caa 100644
--- a/ios/MullvadLogging/LogFileOutputStream.swift
+++ b/ios/MullvadLogging/LogFileOutputStream.swift
@@ -7,10 +7,11 @@
//
import Foundation
+import MullvadTypes
/// Interval used for reopening the log file descriptor in the event of failure to open it in
/// the first place, or when writing to it.
-private let reopenFileLogInterval: TimeInterval = 5
+private let reopenFileLogInterval: Duration = .seconds(5)
class LogFileOutputStream: TextOutputStream {
private let queue = DispatchQueue(label: "LogFileOutputStreamQueue", qos: .utility)
@@ -124,7 +125,7 @@ class LogFileOutputStream: TextOutputStream {
}
timer.schedule(
wallDeadline: .now() + reopenFileLogInterval,
- repeating: reopenFileLogInterval
+ repeating: reopenFileLogInterval.timeInterval
)
timer.activate()
diff --git a/ios/MullvadREST/Duration.swift b/ios/MullvadREST/Duration.swift
deleted file mode 100644
index 3ce42719d8..0000000000
--- a/ios/MullvadREST/Duration.swift
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// 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 {
- milliseconds / 1000
- }
-
- public var timeInterval: TimeInterval {
- 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 {
- Duration(milliseconds: seconds.saturatingMultiplication(1000))
- }
-
- public static func milliseconds(_ milliseconds: UInt64) -> Duration {
- Duration(milliseconds: milliseconds)
- }
-
- public static func < (lhs: Duration, rhs: Duration) -> Bool {
- lhs.milliseconds < rhs.milliseconds
- }
-
- public static func == (lhs: Duration, rhs: Duration) -> Bool {
- lhs.milliseconds == rhs.milliseconds
- }
-
- public static func * (lhs: Duration, factor: UInt64) -> Duration {
- Duration(milliseconds: lhs.milliseconds.saturatingMultiplication(factor))
- }
- }
-}
diff --git a/ios/MullvadREST/ExponentialBackoff.swift b/ios/MullvadREST/ExponentialBackoff.swift
index 39c9e01923..5c94735eaa 100644
--- a/ios/MullvadREST/ExponentialBackoff.swift
+++ b/ios/MullvadREST/ExponentialBackoff.swift
@@ -10,43 +10,43 @@ import Foundation
import MullvadTypes
struct ExponentialBackoff: IteratorProtocol {
- private var _next: REST.Duration
+ private var _next: Duration
private let multiplier: UInt64
- private let maxDelay: REST.Duration?
+ private let maxDelay: Duration?
- init(initial: REST.Duration, multiplier: UInt64, maxDelay: REST.Duration? = nil) {
+ init(initial: Duration, multiplier: UInt64, maxDelay: Duration? = nil) {
_next = initial
self.multiplier = multiplier
self.maxDelay = maxDelay
}
- mutating func next() -> REST.Duration? {
+ mutating func next() -> Duration? {
let next = _next
if let maxDelay, next > maxDelay {
return maxDelay
}
- _next = next * multiplier
+ _next = next * Int(multiplier)
return next
}
}
struct Jittered<InnerIterator: IteratorProtocol>: IteratorProtocol
- where InnerIterator.Element == REST.Duration {
+ where InnerIterator.Element == Duration {
private var inner: InnerIterator
init(_ inner: InnerIterator) {
self.inner = inner
}
- mutating func next() -> REST.Duration? {
+ mutating func next() -> 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))
+ let millisWithJitter = millis.saturatingAddition(Int(Double(millis) * jitter))
return .milliseconds(millisWithJitter)
}
diff --git a/ios/MullvadREST/RESTDefaults.swift b/ios/MullvadREST/RESTDefaults.swift
index 8c0afe1f00..e9e8c50e13 100644
--- a/ios/MullvadREST/RESTDefaults.swift
+++ b/ios/MullvadREST/RESTDefaults.swift
@@ -20,5 +20,5 @@ extension REST {
public static let isStagingEnvironment = false
/// Default network timeout for API requests.
- public static let defaultAPINetworkTimeout: TimeInterval = 10
+ public static let defaultAPINetworkTimeout: Duration = .seconds(10)
}
diff --git a/ios/MullvadREST/RESTNetworkOperation.swift b/ios/MullvadREST/RESTNetworkOperation.swift
index 447be5776a..c3dc45b263 100644
--- a/ios/MullvadREST/RESTNetworkOperation.swift
+++ b/ios/MullvadREST/RESTNetworkOperation.swift
@@ -251,7 +251,7 @@ extension REST {
return
}
- logger.debug("Retry in \(waitDelay.format()).")
+ logger.debug("Retry in \(waitDelay.logFormat()).")
// Create timer to delay retry.
let timer = DispatchSource.makeTimerSource(queue: dispatchQueue)
diff --git a/ios/MullvadREST/RESTRequestFactory.swift b/ios/MullvadREST/RESTRequestFactory.swift
index 40db97f4f0..00ed23e9ae 100644
--- a/ios/MullvadREST/RESTRequestFactory.swift
+++ b/ios/MullvadREST/RESTRequestFactory.swift
@@ -13,7 +13,7 @@ extension REST {
final class RequestFactory {
let hostname: String
let pathPrefix: String
- let networkTimeout: TimeInterval
+ let networkTimeout: Duration
let bodyEncoder: JSONEncoder
class func withDefaultAPICredentials(
@@ -31,7 +31,7 @@ extension REST {
init(
hostname: String,
pathPrefix: String,
- networkTimeout: TimeInterval,
+ networkTimeout: Duration,
bodyEncoder: JSONEncoder
) {
self.hostname = hostname
@@ -57,7 +57,7 @@ extension REST {
var request = URLRequest(
url: requestURL,
cachePolicy: .useProtocolCachePolicy,
- timeoutInterval: networkTimeout
+ timeoutInterval: networkTimeout.timeInterval
)
request.httpShouldHandleCookies = false
request.addValue(hostname, forHTTPHeaderField: HTTPHeader.host)
diff --git a/ios/MullvadREST/RESTRetryStrategy.swift b/ios/MullvadREST/RESTRetryStrategy.swift
index 99613811be..2cd72ea53b 100644
--- a/ios/MullvadREST/RESTRetryStrategy.swift
+++ b/ios/MullvadREST/RESTRetryStrategy.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadTypes
extension REST {
public struct RetryStrategy {
diff --git a/ios/MullvadRESTTests/DurationTests.swift b/ios/MullvadRESTTests/DurationTests.swift
deleted file mode 100644
index c5b5906c02..0000000000
--- a/ios/MullvadRESTTests/DurationTests.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// 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
index 85529e260a..4fc5a99bb6 100644
--- a/ios/MullvadRESTTests/ExponentialBackoffTests.swift
+++ b/ios/MullvadRESTTests/ExponentialBackoffTests.swift
@@ -7,6 +7,7 @@
//
@testable import MullvadREST
+import MullvadTypes
import XCTest
final class ExponentialBackoffTests: XCTestCase {
@@ -22,8 +23,8 @@ final class ExponentialBackoffTests: XCTestCase {
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))
+ XCTAssertEqual(backoff.next(), .milliseconds(.max))
+ XCTAssertEqual(backoff.next(), .milliseconds(.max))
}
func testMaximumBound() {
@@ -51,7 +52,7 @@ final class ExponentialBackoffTests: XCTestCase {
}
func testJitter() {
- let initial = REST.Duration.milliseconds(500)
+ let initial: Duration = .milliseconds(500)
var iterator = Jittered(ExponentialBackoff(initial: initial, multiplier: 3))
XCTAssertGreaterThanOrEqual(iterator.next()!, initial)
diff --git a/ios/MullvadTypes/Duration+Extensions.swift b/ios/MullvadTypes/Duration+Extensions.swift
new file mode 100644
index 0000000000..3613f7ef1c
--- /dev/null
+++ b/ios/MullvadTypes/Duration+Extensions.swift
@@ -0,0 +1,76 @@
+//
+// Duration+Extensions.swift
+// MullvadTypes
+//
+// Created by Jon Petersson on 2023-08-18.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+// Extends Duration with convenience accessors and functions.
+extension Duration {
+ public var isFinite: Bool {
+ return timeInterval.isFinite
+ }
+
+ public var timeInterval: TimeInterval {
+ return TimeInterval(components.seconds) + (TimeInterval(components.attoseconds) * 1e-18)
+ }
+
+ public var milliseconds: Int {
+ return Int(components.seconds.saturatingMultiplication(1000)) + Int(Double(components.attoseconds) * 1e-15)
+ }
+
+ public static func minutes(_ minutes: Int) -> Duration {
+ return .seconds(minutes.saturatingMultiplication(60))
+ }
+
+ public static func hours(_ hours: Int) -> Duration {
+ return .seconds(hours.saturatingMultiplication(3600))
+ }
+
+ public static func days(_ days: Int) -> Duration {
+ return .seconds(days.saturatingMultiplication(86400))
+ }
+}
+
+// Extends Duration with custom operators.
+extension Duration {
+ public static func + (lhs: DispatchWallTime, rhs: Duration) -> DispatchWallTime {
+ return lhs + rhs.timeInterval
+ }
+
+ public static func * (lhs: Duration, rhs: Double) -> Duration {
+ let milliseconds = lhs.timeInterval * rhs * 1000
+
+ let maxTruncated = min(milliseconds, Double(Int.max).nextDown)
+ let bothTruncated = max(maxTruncated, Double(Int.min).nextUp)
+
+ return .milliseconds(Int(bothTruncated))
+ }
+
+ public static func + (lhs: Duration, rhs: TimeInterval) -> Duration {
+ return .milliseconds(lhs.milliseconds.saturatingAddition(Int(rhs * 1000)))
+ }
+
+ public static func - (lhs: Duration, rhs: TimeInterval) -> Duration {
+ return .milliseconds(lhs.milliseconds.saturatingSubtraction(Int(rhs * 1000)))
+ }
+
+ public static func >= (lhs: TimeInterval, rhs: Duration) -> Bool {
+ return lhs >= rhs.timeInterval
+ }
+
+ public static func <= (lhs: TimeInterval, rhs: Duration) -> Bool {
+ return lhs <= rhs.timeInterval
+ }
+
+ public static func < (lhs: TimeInterval, rhs: Duration) -> Bool {
+ return lhs < rhs.timeInterval
+ }
+
+ public static func > (lhs: TimeInterval, rhs: Duration) -> Bool {
+ return lhs > rhs.timeInterval
+ }
+}
diff --git a/ios/MullvadTypes/Duration.swift b/ios/MullvadTypes/Duration.swift
new file mode 100644
index 0000000000..a2d30c5249
--- /dev/null
+++ b/ios/MullvadTypes/Duration.swift
@@ -0,0 +1,93 @@
+//
+// Duration.swift
+// MullvadTypes
+//
+// Created by Jon Petersson on 2023-08-16.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Custom implementation of iOS native `Duration` (available from iOS16). Meant as a
+/// drop-in replacement until the app supports iOS16. Ideally this whole file can
+/// then be deleted without affecting the rest of the code base.
+@available(iOS, introduced: 14.0, obsoleted: 16.0, message: "Replace with native Duration type.")
+public struct Duration {
+ private(set) var components: (seconds: Int64, attoseconds: Int64)
+
+ public init(secondsComponent: Int64, attosecondsComponent: Int64 = 0) {
+ components = (
+ seconds: Int64(secondsComponent),
+ attoseconds: Int64(attosecondsComponent)
+ )
+ }
+
+ public static func milliseconds(_ milliseconds: Int) -> Duration {
+ let subSeconds = milliseconds % 1000
+ let seconds = (milliseconds - subSeconds) / 1000
+
+ return Duration(
+ secondsComponent: Int64(seconds),
+ attosecondsComponent: Int64(subSeconds) * Int64(1e15)
+ )
+ }
+
+ public static func seconds(_ seconds: Int) -> Duration {
+ return Duration(secondsComponent: Int64(seconds))
+ }
+
+ public func logFormat() -> String {
+ let timeInterval = timeInterval
+
+ guard timeInterval >= 1 else {
+ return "\(milliseconds)ms"
+ }
+
+ let trailingZeroesSuffix = ".00"
+ var string = String(format: "%.2f", timeInterval)
+
+ if string.hasSuffix(trailingZeroesSuffix) {
+ string.removeLast(trailingZeroesSuffix.count)
+ }
+
+ return "\(string)s"
+ }
+}
+
+extension Duration: DurationProtocol {
+ public static var zero: Duration {
+ return .seconds(0)
+ }
+
+ public static func / (lhs: Duration, rhs: Int) -> Duration {
+ return .milliseconds(lhs.milliseconds / max(rhs, 1))
+ }
+
+ public static func * (lhs: Duration, rhs: Int) -> Duration {
+ return .milliseconds(lhs.milliseconds.saturatingMultiplication(rhs))
+ }
+
+ public static func / (lhs: Duration, rhs: Duration) -> Double {
+ guard rhs != .zero else {
+ return lhs.timeInterval
+ }
+
+ return lhs.timeInterval / rhs.timeInterval
+ }
+
+ public static func + (lhs: Duration, rhs: Duration) -> Duration {
+ return .milliseconds(lhs.milliseconds.saturatingAddition(rhs.milliseconds))
+ }
+
+ public static func - (lhs: Duration, rhs: Duration) -> Duration {
+ return .milliseconds(lhs.milliseconds.saturatingSubtraction(rhs.milliseconds))
+ }
+
+ public static func < (lhs: Duration, rhs: Duration) -> Bool {
+ return lhs.timeInterval < rhs.timeInterval
+ }
+
+ public static func == (lhs: Duration, rhs: Duration) -> Bool {
+ return lhs.timeInterval == rhs.timeInterval
+ }
+}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 0f4214bc37..1795a2f8e4 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -198,7 +198,6 @@
5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.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 */; };
@@ -379,7 +378,6 @@
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 */; };
58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; };
58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */; };
58FDF2D92A0BA11A00C2B061 /* DeviceCheckOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FDF2D82A0BA11900C2B061 /* DeviceCheckOperation.swift */; };
@@ -389,6 +387,8 @@
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */; };
7A1A26432A2612AE00B978AA /* PaymentAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */; };
7A21DACF2A30AA3700A787A9 /* UITextField+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */; };
+ 7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307AD82A8CD8DA0017618B /* Duration.swift */; };
+ 7A307ADB2A8F56DF0017618B /* Duration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */; };
7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; };
7A42DECD2A09064C00B209BE /* SelectableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */; };
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; };
@@ -430,6 +430,7 @@
7AE044BB2A935726003915D8 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A88DCD02A8FABBE00D2FF0E /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; };
7AE47E522A17972A000418DA /* CustomAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE47E512A17972A000418DA /* CustomAlertViewController.swift */; };
7AF6E5F02A95051E00F2679D /* RouterBlockDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF6E5EF2A95051E00F2679D /* RouterBlockDelegate.swift */; };
+ 7AF6E5F12A95F4A500F2679D /* DurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBF0291630700020E046 /* DurationTests.swift */; };
7AF9BE992A4E0FE900DBFEDB /* MarkdownStylingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */; };
A917351F29FAA9C400D5DCFD /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */; };
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; };
@@ -1157,7 +1158,6 @@
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>"; };
@@ -1294,6 +1294,8 @@
7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = "<group>"; };
7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAlertPresenter.swift; sourceTree = "<group>"; };
7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Appearance.swift"; sourceTree = "<group>"; };
+ 7A307AD82A8CD8DA0017618B /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = "<group>"; };
+ 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duration+Extensions.swift"; sourceTree = "<group>"; };
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsCell.swift; sourceTree = "<group>"; };
7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeLaunch.swift; sourceTree = "<group>"; };
@@ -1621,7 +1623,6 @@
06FAE67428F83CA40033DD93 /* RESTRequestHandler.swift */,
06FAE66628F83CA30033DD93 /* RESTResponseHandler.swift */,
06FAE67628F83CA40033DD93 /* RESTRetryStrategy.swift */,
- 5897F1752914E62E00AF5695 /* Duration.swift */,
5897F1732913EAF800AF5695 /* ExponentialBackoff.swift */,
06FAE67528F83CA40033DD93 /* RESTTaskIdentifier.swift */,
06FAE67D28F83CA50033DD93 /* RESTTransport.swift */,
@@ -1673,6 +1674,8 @@
A9A8A8EA2A262AB30086D569 /* FileCache.swift */,
58E511E328DDDE8900B0BCDE /* CustomErrorDescriptionProtocol.swift */,
586168682976F6BD00EF8598 /* DisplayError.swift */,
+ 7A307AD82A8CD8DA0017618B /* Duration.swift */,
+ 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */,
58E511EA28DDE18400B0BCDE /* Error+Chain.swift */,
58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */,
06AC115628F848D00037AF9A /* IPAddress+Codable.swift */,
@@ -2171,6 +2174,7 @@
A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */,
5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */,
58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */,
+ 58FBFBF0291630700020E046 /* DurationTests.swift */,
58C3FA672A385C89006A450A /* FileCacheTests.swift */,
582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */,
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */,
@@ -2501,7 +2505,6 @@
58FBFBE7291622580020E046 /* MullvadRESTTests */ = {
isa = PBXGroup;
children = (
- 58FBFBF0291630700020E046 /* DurationTests.swift */,
58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */,
A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */,
);
@@ -3553,7 +3556,6 @@
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.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 */,
@@ -3632,6 +3634,7 @@
580810E62A30E13D00B74552 /* DeviceStateAccessorProtocol.swift in Sources */,
58C3FA662A38549D006A450A /* MockFileCache.swift in Sources */,
58915D642A25F8B30066445B /* DeviceCheckOperation.swift in Sources */,
+ 7AF6E5F12A95F4A500F2679D /* DurationTests.swift in Sources */,
58915D652A25F9E20066445B /* TunnelSettingsV2.swift in Sources */,
A9467E7F2A29DEFE000DC21F /* RelayCacheTests.swift in Sources */,
582A8A3A28BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift in Sources */,
@@ -3983,11 +3986,13 @@
buildActionMask = 2147483647;
files = (
58D22406294C90210029F5F8 /* IPv4Endpoint.swift in Sources */,
+ 7A307ADB2A8F56DF0017618B /* Duration+Extensions.swift in Sources */,
58D22407294C90210029F5F8 /* IPv6Endpoint.swift in Sources */,
58CAFA032985367600BE19F7 /* Promise.swift in Sources */,
A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */,
58D22408294C90210029F5F8 /* AnyIPEndpoint.swift in Sources */,
58D22409294C90210029F5F8 /* AnyIPAddress.swift in Sources */,
+ 7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */,
58D2240A294C90210029F5F8 /* IPAddress+Codable.swift in Sources */,
58E45A5729F12C5100281ECF /* Result+Extensions.swift in Sources */,
58D2240B294C90210029F5F8 /* Cancellable.swift in Sources */,
@@ -4031,7 +4036,6 @@
files = (
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */,
58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */,
- 58FBFBF1291630700020E046 /* DurationTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
index 9b9621010f..8c1dc2b258 100644
--- a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
+++ b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift
@@ -13,11 +13,11 @@ import Operations
import UIKit
final class AddressCacheTracker {
- /// Update interval (in seconds).
- private static let updateInterval: TimeInterval = 60 * 60 * 24
+ /// Update interval.
+ private static let updateInterval: Duration = .days(1)
- /// Retry interval (in seconds).
- private static let retryInterval: TimeInterval = 60 * 15
+ /// Retry interval.
+ private static let retryInterval: Duration = .minutes(15)
/// Logger.
private let logger = Logger(label: "AddressCache.Tracker")
@@ -174,11 +174,11 @@ final class AddressCacheTracker {
private func _nextScheduleDate() -> Date {
let nextDate = lastFailureAttemptDate.map { date in
Date(
- timeInterval: Self.retryInterval,
+ timeInterval: Self.retryInterval.timeInterval,
since: date
)
} ?? Date(
- timeInterval: Self.updateInterval,
+ timeInterval: Self.updateInterval.timeInterval,
since: store.getLastUpdateDate()
)
diff --git a/ios/MullvadVPN/Classes/AccountDataThrottling.swift b/ios/MullvadVPN/Classes/AccountDataThrottling.swift
index 3f8ae0daf4..4d880b277c 100644
--- a/ios/MullvadVPN/Classes/AccountDataThrottling.swift
+++ b/ios/MullvadVPN/Classes/AccountDataThrottling.swift
@@ -7,14 +7,15 @@
//
import Foundation
+import MullvadTypes
/// Struct used for throttling UI calls to update account data via tunnel manager.
struct AccountDataThrottling {
/// Default cooldown interval between requests.
- private static let defaultWaitInterval: TimeInterval = 60
+ private static let defaultWaitInterval: Duration = .minutes(1)
/// Cooldown interval used when account has already expired.
- private static let waitIntervalForExpiredAccount: TimeInterval = 10
+ private static let waitIntervalForExpiredAccount: Duration = .seconds(10)
/// Interval in days when account is considered to be close to expiry.
private static let closeToExpiryDays = 4
@@ -61,7 +62,7 @@ struct AccountDataThrottling {
? Self.defaultWaitInterval
: Self.waitIntervalForExpiredAccount
- let nextUpdateAfter = lastUpdate?.addingTimeInterval(waitInterval)
+ let nextUpdateAfter = lastUpdate?.addingTimeInterval(waitInterval.timeInterval)
let comparisonResult = nextUpdateAfter?.compare(now) ?? .orderedAscending
switch comparisonResult {
diff --git a/ios/MullvadVPN/Classes/DeviceDataThrottling.swift b/ios/MullvadVPN/Classes/DeviceDataThrottling.swift
index cb30b130ff..de378c56cb 100644
--- a/ios/MullvadVPN/Classes/DeviceDataThrottling.swift
+++ b/ios/MullvadVPN/Classes/DeviceDataThrottling.swift
@@ -7,11 +7,12 @@
//
import Foundation
+import MullvadTypes
/// Struct used for throttling UI calls to update device data via tunnel manager.
struct DeviceDataThrottling {
/// Default cooldown interval between requests.
- private static let defaultWaitInterval: TimeInterval = 60
+ private static let defaultWaitInterval: Duration = .minutes(1)
let tunnelManager: TunnelManager
private(set) var lastUpdate: Date?
@@ -32,7 +33,7 @@ struct DeviceDataThrottling {
return
}
- let nextUpdateAfter = lastUpdate?.addingTimeInterval(Self.defaultWaitInterval)
+ let nextUpdateAfter = lastUpdate?.addingTimeInterval(Self.defaultWaitInterval.timeInterval)
let comparisonResult = nextUpdateAfter?.compare(now) ?? .orderedAscending
switch comparisonResult {
diff --git a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
index 0751845e62..444913b059 100644
--- a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
@@ -84,7 +84,7 @@ final class LoginCoordinator: Coordinator, DeviceManagementViewControllerDelegat
}
private func callDidFinishAfterDelay() {
- DispatchQueue.main.asyncAfter(wallDeadline: .now() + .seconds(1)) { [weak self] in
+ DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { [weak self] in
guard let self else { return }
didFinish?(self)
}
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
index a87facc46e..0d0550401a 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadTypes
final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppNotificationProvider {
private var accountExpiry: Date?
@@ -49,7 +50,7 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
formatter.maximumUnitCount = 1
let duration: String?
- if accountExpiry.timeIntervalSince(now) < 60 {
+ if accountExpiry.timeIntervalSince(now) < .minutes(1) {
duration = NSLocalizedString(
"ACCOUNT_EXPIRY_INAPP_NOTIFICATION_LESS_THAN_ONE_MINUTE",
value: "Less than a minute",
diff --git a/ios/MullvadVPN/Operations/ProductsRequestOperation.swift b/ios/MullvadVPN/Operations/ProductsRequestOperation.swift
index faede2a70c..fbe02adb2c 100644
--- a/ios/MullvadVPN/Operations/ProductsRequestOperation.swift
+++ b/ios/MullvadVPN/Operations/ProductsRequestOperation.swift
@@ -6,6 +6,7 @@
// Copyright © 2021 Mullvad VPN AB. All rights reserved.
//
+import MullvadTypes
import Operations
import StoreKit
@@ -14,7 +15,7 @@ final class ProductsRequestOperation: ResultOperation<SKProductsResponse>,
private let productIdentifiers: Set<String>
private let maxRetryCount = 10
- private let retryDelay: DispatchTimeInterval = .seconds(2)
+ private let retryDelay: Duration = .seconds(2)
private var retryCount = 0
private var retryTimer: DispatchSourceTimer?
diff --git a/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift b/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift
index 8a91fa6264..016153f906 100644
--- a/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift
+++ b/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift
@@ -6,6 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
+import MullvadTypes
import UIKit
struct FormSheetPresentationOptions {
/**
@@ -231,7 +232,7 @@ class FormSheetTransitioningDelegate: NSObject, UIViewControllerTransitioningDel
class FormSheetPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?)
-> TimeInterval {
- (transitionContext?.isAnimated ?? true) ? UIMetrics.FormSheetTransition.duration : 0
+ (transitionContext?.isAnimated ?? true) ? UIMetrics.FormSheetTransition.duration.timeInterval : 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
@@ -259,7 +260,7 @@ class FormSheetPresentationAnimator: NSObject, UIViewControllerAnimatedTransitio
if transitionContext.isAnimated {
UIView.animate(
withDuration: duration,
- delay: UIMetrics.FormSheetTransition.delay,
+ delay: UIMetrics.FormSheetTransition.delay.timeInterval,
options: UIMetrics.FormSheetTransition.animationOptions,
animations: {
destinationView.frame = transitionContext.finalFrame(for: destinationController)
@@ -285,7 +286,7 @@ class FormSheetPresentationAnimator: NSObject, UIViewControllerAnimatedTransitio
if transitionContext.isAnimated {
UIView.animate(
withDuration: duration,
- delay: UIMetrics.FormSheetTransition.delay,
+ delay: UIMetrics.FormSheetTransition.delay.timeInterval,
options: UIMetrics.FormSheetTransition.animationOptions,
animations: {
sourceView.frame = initialFrame
diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
index 34fb9a9e8f..5a16d473a1 100644
--- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
+++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
@@ -15,8 +15,8 @@ import RelayCache
import UIKit
final class RelayCacheTracker {
- /// Relay update interval (in seconds).
- static let relayUpdateInterval: TimeInterval = 60 * 60
+ /// Relay update interval.
+ static let relayUpdateInterval: Duration = .hours(1)
/// Tracker log.
private let logger = Logger(label: "RelayCacheTracker")
@@ -161,7 +161,7 @@ final class RelayCacheTracker {
return now
}
- let nextUpdate = cachedRelays.updatedAt.addingTimeInterval(Self.relayUpdateInterval)
+ let nextUpdate = cachedRelays.updatedAt.addingTimeInterval(Self.relayUpdateInterval.timeInterval)
return max(nextUpdate, Date())
}
@@ -220,7 +220,7 @@ final class RelayCacheTracker {
timerSource.schedule(
wallDeadline: startTime,
- repeating: .seconds(Int(Self.relayUpdateInterval))
+ repeating: Self.relayUpdateInterval.timeInterval
)
timerSource.activate()
diff --git a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
index 654274f78b..f1c7a24d9e 100644
--- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift
@@ -15,10 +15,10 @@ import UIKit
/// Delay for sending tunnel provider messages to the tunnel when in connecting state.
/// Used to workaround a bug when talking to the tunnel too early during startup may cause it
/// to freeze.
-private let connectingStateWaitDelay: TimeInterval = 5
+private let connectingStateWaitDelay: Duration = .seconds(5)
/// Default timeout in seconds.
-private let defaultTimeout: TimeInterval = 5
+private let defaultTimeout: Duration = .seconds(5)
final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output> {
typealias DecoderHandler = (Data?) throws -> Output
@@ -26,7 +26,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
private let application: UIApplication
private let tunnel: Tunnel
private let message: TunnelProviderMessage
- private let timeout: TimeInterval
+ private let timeout: Duration
private let decoderHandler: DecoderHandler
@@ -41,7 +41,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
application: UIApplication,
tunnel: Tunnel,
message: TunnelProviderMessage,
- timeout: TimeInterval? = nil,
+ timeout: Duration? = nil,
decoderHandler: @escaping DecoderHandler,
completionHandler: CompletionHandler?
) {
@@ -68,7 +68,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
}
override func main() {
- setTimeoutTimer(connectingStateWaitDelay: 0)
+ setTimeoutTimer(connectingStateWaitDelay: .zero)
statusObserver = tunnel.addBlockObserver(queue: dispatchQueue) { [weak self] _, status in
self?.handleVPNStatus(status)
@@ -98,7 +98,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output>
statusObserver = nil
}
- private func setTimeoutTimer(connectingStateWaitDelay delay: TimeInterval) {
+ private func setTimeoutTimer(connectingStateWaitDelay delay: Duration) {
let workItem = DispatchWorkItem { [weak self] in
self?.finish(result: .failure(SendTunnelProviderMessageError.timeout))
}
@@ -221,7 +221,7 @@ extension SendTunnelProviderMessageOperation where Output: Codable {
application: UIApplication,
tunnel: Tunnel,
message: TunnelProviderMessage,
- timeout: TimeInterval? = nil,
+ timeout: Duration? = nil,
completionHandler: @escaping CompletionHandler
) {
self.init(
@@ -248,7 +248,7 @@ extension SendTunnelProviderMessageOperation where Output == Void {
application: UIApplication,
tunnel: Tunnel,
message: TunnelProviderMessage,
- timeout: TimeInterval? = nil,
+ timeout: Duration? = nil,
completionHandler: CompletionHandler?
) {
self.init(
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index 71f2f60b4b..905aec8445 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -21,11 +21,11 @@ import class WireGuardKitTypes.PublicKey
/// Interval used for periodic polling of tunnel relay status when tunnel is establishing
/// connection.
-private let establishingTunnelStatusPollInterval: TimeInterval = 3
+private let establishingTunnelStatusPollInterval: Duration = .seconds(3)
/// Interval used for periodic polling of tunnel connectivity status once the tunnel connection
/// is established.
-private let establishedTunnelStatusPollInterval: TimeInterval = 5
+private let establishedTunnelStatusPollInterval: Duration = .seconds(5)
/// A class that provides a convenient interface for VPN tunnels configuration, manipulation and
/// monitoring.
@@ -1065,7 +1065,7 @@ final class TunnelManager: StorePaymentObserver {
// MARK: - Tunnel status polling
- private func startPollingTunnelStatus(interval: TimeInterval) {
+ private func startPollingTunnelStatus(interval: Duration) {
guard !isPolling else { return }
isPolling = true
@@ -1078,7 +1078,7 @@ final class TunnelManager: StorePaymentObserver {
timer.setEventHandler { [weak self] in
self?.refreshTunnelStatus()
}
- timer.schedule(wallDeadline: .now() + interval, repeating: interval)
+ timer.schedule(wallDeadline: .now() + interval, repeating: interval.timeInterval)
timer.activate()
tunnelStatusPollTimer?.cancel()
diff --git a/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift b/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift
index 21356091ab..d7aec80b2e 100644
--- a/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift
+++ b/ios/MullvadVPN/TunnelManager/WgKeyRotation.swift
@@ -7,7 +7,7 @@
//
import Foundation
-import struct MullvadTypes.Device
+import MullvadTypes
import class WireGuardKitTypes.PrivateKey
import class WireGuardKitTypes.PublicKey
@@ -16,17 +16,17 @@ import class WireGuardKitTypes.PublicKey
key rotation.
*/
struct WgKeyRotation {
- /// Private key rotation interval measured in seconds and counted from the time when the key was successfully pushed
+ /// Private key rotation interval counted from the time when the key was successfully pushed
/// to the backend.
- public static let rotationInterval: TimeInterval = 60 * 60 * 24 * 14
+ public static let rotationInterval: Duration = .days(14)
- /// Private key rotation retry interval measured in seconds and counted from the time when the last rotation
+ /// Private key rotation retry interval counted from the time when the last rotation
/// attempt took place.
- public static let retryInterval: TimeInterval = 60 * 60 * 24
+ public static let retryInterval: Duration = .days(1)
- /// Cooldown interval measured in seconds used to prevent packet tunnel from forcefully pushing the key to our
+ /// Cooldown interval used to prevent packet tunnel from forcefully pushing the key to our
/// backend in the event of restart loop.
- public static let packetTunnelCooldownInterval: TimeInterval = 15
+ public static let packetTunnelCooldownInterval: Duration = .seconds(15)
/// Mutated device data value.
private(set) var data: StoredDeviceData
@@ -95,8 +95,9 @@ struct WgKeyRotation {
If the date produced is in the past then `Date()` is returned instead.
*/
var nextRotationDate: Date {
- let nextRotationDate = data.wgKeyData.lastRotationAttemptDate?.addingTimeInterval(Self.retryInterval)
- ?? data.wgKeyData.creationDate.addingTimeInterval(Self.rotationInterval)
+ let nextRotationDate = data.wgKeyData.lastRotationAttemptDate?
+ .addingTimeInterval(Self.retryInterval.timeInterval)
+ ?? data.wgKeyData.creationDate.addingTimeInterval(Self.rotationInterval.timeInterval)
return max(nextRotationDate, Date())
}
@@ -128,7 +129,7 @@ struct WgKeyRotation {
return true
}
- let nextRotationAttempt = max(now, lastRotationAttemptDate.addingTimeInterval(Self.retryInterval))
+ let nextRotationAttempt = max(now, lastRotationAttemptDate.addingTimeInterval(Self.retryInterval.timeInterval))
if nextRotationAttempt <= now {
return true
}
diff --git a/ios/MullvadVPN/UI appearance/UIMetrics.swift b/ios/MullvadVPN/UI appearance/UIMetrics.swift
index 6595a410fd..d3145a821f 100644
--- a/ios/MullvadVPN/UI appearance/UIMetrics.swift
+++ b/ios/MullvadVPN/UI appearance/UIMetrics.swift
@@ -6,6 +6,7 @@
// Copyright © 2021 Mullvad VPN AB. All rights reserved.
//
+import MullvadTypes
import UIKit
enum UIMetrics {
@@ -29,8 +30,8 @@ enum UIMetrics {
}
enum FormSheetTransition {
- static let duration: TimeInterval = 0.5
- static let delay: TimeInterval = .zero
+ static let duration: Duration = .milliseconds(500)
+ static let delay: Duration = .zero
static let animationOptions: UIView.AnimationOptions = [.curveEaseInOut]
}
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift
index 7c6356a3ca..80be852f65 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeInteractor.swift
@@ -8,6 +8,7 @@
import Foundation
import MullvadLogging
+import MullvadTypes
import StoreKit
final class WelcomeInteractor {
@@ -15,7 +16,7 @@ final class WelcomeInteractor {
private let tunnelManager: TunnelManager
/// Interval used for periodic polling account updates.
- private let accountUpdateTimerInterval: TimeInterval = 60
+ private let accountUpdateTimerInterval: Duration = .minutes(1)
private var accountUpdateTimer: DispatchSourceTimer?
private let logger = Logger(label: "\(WelcomeInteractor.self)")
@@ -105,7 +106,10 @@ final class WelcomeInteractor {
accountUpdateTimer?.cancel()
accountUpdateTimer = timer
- timer.schedule(wallDeadline: .now() + accountUpdateTimerInterval, repeating: accountUpdateTimerInterval)
+ timer.schedule(
+ wallDeadline: .now() + accountUpdateTimerInterval,
+ repeating: accountUpdateTimerInterval.timeInterval
+ )
timer.activate()
}
diff --git a/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift b/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift
index a16cd3c9d2..ac0c218315 100644
--- a/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift
+++ b/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift
@@ -6,9 +6,10 @@
// Copyright © 2019 Mullvad VPN AB. All rights reserved.
//
+import MullvadTypes
import UIKit
-private let animationDuration: TimeInterval = 0.25
+private let animationDuration: Duration = .milliseconds(250)
final class AccountInputGroupView: UIView {
private let minimumAccountTokenLength = 10
@@ -479,7 +480,7 @@ final class AccountInputGroupView: UIView {
if animated {
actions()
- UIView.animate(withDuration: animationDuration) {
+ UIView.animate(withDuration: animationDuration.timeInterval) {
self.layoutIfNeeded()
}
} else {
@@ -523,7 +524,7 @@ final class AccountInputGroupView: UIView {
}
if animated {
- UIView.animate(withDuration: animationDuration) {
+ UIView.animate(withDuration: animationDuration.timeInterval) {
actions()
}
} else {
@@ -575,7 +576,7 @@ private class AccountInputBorderLayer: CAShapeLayer {
override class func defaultAction(forKey event: String) -> CAAction? {
if event == "path" {
let action = CABasicAnimation(keyPath: event)
- action.duration = animationDuration
+ action.duration = animationDuration.timeInterval
action.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
return action
diff --git a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift
index cd2c4dd15a..a6676009ff 100644
--- a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift
+++ b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeInteractor.swift
@@ -22,7 +22,7 @@ final class OutOfTimeInteractor {
private let logger = Logger(label: "OutOfTimeInteractor")
- private let accountUpdateTimerInterval: TimeInterval = 60
+ private let accountUpdateTimerInterval: Duration = .minutes(1)
private var accountUpdateTimer: DispatchSourceTimer?
var didReceivePaymentEvent: ((StorePaymentEvent) -> Void)?
@@ -108,7 +108,10 @@ final class OutOfTimeInteractor {
accountUpdateTimer?.cancel()
accountUpdateTimer = timer
- timer.schedule(wallDeadline: .now() + accountUpdateTimerInterval, repeating: accountUpdateTimerInterval)
+ timer.schedule(
+ wallDeadline: .now() + accountUpdateTimerInterval,
+ repeating: accountUpdateTimerInterval.timeInterval
+ )
timer.activate()
}
diff --git a/ios/MullvadVPNTests/DeviceCheckOperationTests.swift b/ios/MullvadVPNTests/DeviceCheckOperationTests.swift
index c728697e72..07efc3a6e0 100644
--- a/ios/MullvadVPNTests/DeviceCheckOperationTests.swift
+++ b/ios/MullvadVPNTests/DeviceCheckOperationTests.swift
@@ -453,11 +453,11 @@ private enum TimeSinceLastKeyRotation {
case .zero:
return .zero
case .retryInterval:
- return -WgKeyRotation.retryInterval
+ return -WgKeyRotation.retryInterval.timeInterval
case .closeToRetryInterval:
- return -WgKeyRotation.retryInterval + 1
+ return -WgKeyRotation.retryInterval.timeInterval + 1
case .packetTunnelCooldownInterval:
- return -WgKeyRotation.packetTunnelCooldownInterval
+ return -WgKeyRotation.packetTunnelCooldownInterval.timeInterval
}
}
}
diff --git a/ios/MullvadVPNTests/DurationTests.swift b/ios/MullvadVPNTests/DurationTests.swift
new file mode 100644
index 0000000000..13c0c5a41d
--- /dev/null
+++ b/ios/MullvadVPNTests/DurationTests.swift
@@ -0,0 +1,71 @@
+//
+// DurationTests.swift
+// MullvadRESTTests
+//
+// Created by pronebird on 05/11/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadREST
+import MullvadTypes
+import XCTest
+
+final class DurationTests: XCTestCase {
+ func testComparable() throws {
+ XCTAssertEqual(Duration.milliseconds(1000), .seconds(1))
+ XCTAssertEqual(Duration.seconds(60), .minutes(1))
+ XCTAssertEqual(Duration.minutes(60), .hours(1))
+ XCTAssertEqual(Duration.hours(24), .days(1))
+
+ XCTAssertTrue(Duration.days(1) == 86400.0)
+ XCTAssertTrue(Duration.milliseconds(1234) == 1.234)
+
+ XCTAssertTrue(Duration.milliseconds(.max) == 9.223372036854775e+15)
+ XCTAssertEqual(Duration.milliseconds(.max).milliseconds, 9223372036854775807)
+ XCTAssertEqual(Duration.seconds(.max).milliseconds, 9223372036854775807)
+
+ XCTAssertLessThan(Duration.milliseconds(999), .seconds(1))
+ XCTAssertGreaterThan(Duration.milliseconds(1001), .seconds(1))
+ XCTAssertTrue(1.1 > Duration.milliseconds(1001))
+ XCTAssertTrue(1.0 < Duration.milliseconds(1001))
+ }
+
+ func testAddition() throws {
+ XCTAssertEqual(Duration.seconds(4) + .seconds(116), .minutes(2))
+ XCTAssertEqual(Duration.minutes(4) + .seconds(.max), .milliseconds(.max))
+ }
+
+ func testSubtraction() throws {
+ XCTAssertEqual(Duration.minutes(4) - .minutes(1), .seconds(180))
+ XCTAssertEqual(Duration.seconds(4) - .seconds(64), .minutes(-1))
+ }
+
+ func testMultiplication() throws {
+ XCTAssertEqual(Duration.seconds(4) * 2.0, .seconds(8))
+ XCTAssertEqual(Duration.seconds(4) * 4, .seconds(16))
+ XCTAssertEqual(Duration.milliseconds(20000) * 3, .minutes(1))
+ XCTAssertEqual(Duration.milliseconds(.max - 1) * 2, .milliseconds(.max))
+ XCTAssertEqual(
+ Duration.milliseconds(.max) * (Double(Int.max) + 1.0),
+ .milliseconds(Int(Double(Int.max).nextDown))
+ )
+ }
+
+ func testDivision() throws {
+ XCTAssertEqual(Duration.seconds(4) / 4, .seconds(1))
+ XCTAssertEqual(Duration.milliseconds(21000) / 3, .seconds(7))
+ XCTAssertEqual(Duration.minutes(3) / 3, .seconds(60))
+ }
+
+ func testLogFormat() throws {
+ XCTAssertEqual(Duration.milliseconds(999).logFormat(), "999ms")
+ XCTAssertEqual(Duration.milliseconds(1000).logFormat(), "1s")
+ XCTAssertEqual(Duration.milliseconds(1200).logFormat(), "1.20s")
+ }
+}
+
+private extension Duration {
+ static func == (lhs: Duration, rhs: Double) -> Bool {
+ return lhs.timeInterval == rhs
+ }
+}
diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift
index 09d7bd8992..f06f1c0b4e 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -21,10 +21,10 @@ import TunnelProviderMessaging
import WireGuardKit
/// Restart interval (in seconds) for the tunnel that failed to start early on.
-private let tunnelStartupFailureRestartInterval: TimeInterval = 2
+private let tunnelStartupFailureRestartInterval: Duration = .seconds(2)
/// Delay before trying to reconnect tunnel after private key rotation.
-private let keyRotationTunnelReconnectionDelay = 60 * 2
+private let keyRotationTunnelReconnectionDelay: Duration = .minutes(2)
class PacketTunnelProvider: NEPacketTunnelProvider {
/// Tunnel provider logger.
@@ -419,7 +419,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
// MARK: - Private
- private func startTunnelReconnectionTimer(reconnectionDelay: Int) {
+ private func startTunnelReconnectionTimer(reconnectionDelay: Duration) {
dispatchPrecondition(condition: .onQueue(dispatchQueue))
providerLogger.debug("Delaying tunnel reconnection by \(reconnectionDelay) seconds...")
@@ -441,7 +441,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
self?.useCachedDeviceState = false
}
- timer.schedule(deadline: .now() + .seconds(reconnectionDelay))
+ timer.schedule(wallDeadline: .now() + reconnectionDelay)
timer.activate()
tunnelReconnectionTimer?.cancel()
@@ -472,7 +472,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
timer.schedule(
wallDeadline: .now() + tunnelStartupFailureRestartInterval,
- repeating: tunnelStartupFailureRestartInterval
+ repeating: tunnelStartupFailureRestartInterval.timeInterval
)
timer.activate()
diff --git a/ios/PacketTunnel/WgAdapterDeviceInfo.swift b/ios/PacketTunnel/WgAdapterDeviceInfo.swift
index c32a37b111..11fca33b94 100644
--- a/ios/PacketTunnel/WgAdapterDeviceInfo.swift
+++ b/ios/PacketTunnel/WgAdapterDeviceInfo.swift
@@ -27,7 +27,8 @@ struct WgAdapterDeviceInfo: TunnelDeviceInfoProtocol {
dispatchGroup.leave()
}
- guard case .success = dispatchGroup.wait(wallTimeout: .now() + .seconds(1)) else { throw StatsError.timeout }
+ guard case .success = dispatchGroup.wait(wallTimeout: .now() + .seconds(1))
+ else { throw StatsError.timeout }
guard let result else { throw StatsError.nilValue }
guard let newStats = WgStats(from: result) else { throw StatsError.parse }
diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
index 36f6c2f1d4..fc116fd7e1 100644
--- a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
@@ -8,44 +8,45 @@
import Foundation
import MullvadLogging
+import MullvadTypes
import protocol Network.IPAddress
import struct Network.IPv4Address
/// Interval for periodic heartbeat ping issued when traffic is flowing.
/// Should help to detect connectivity issues on networks that drop traffic in one of directions,
/// regardless if tx/rx counters are being updated.
-private let heartbeatPingInterval: TimeInterval = 10
+private let heartbeatPingInterval: Duration = .seconds(10)
/// Heartbeat timeout that once exceeded triggers next heartbeat to be sent.
-private let heartbeatReplyTimeout: TimeInterval = 3
+private let heartbeatReplyTimeout: Duration = .seconds(3)
/// Timeout used to determine if there was a network activity lately.
-private let trafficFlowTimeout: TimeInterval = heartbeatPingInterval * 0.5
+private let trafficFlowTimeout: Duration = heartbeatPingInterval * 0.5
/// Ping timeout.
-private let pingTimeout: TimeInterval = 15
+private let pingTimeout: Duration = .seconds(15)
/// Interval to wait before sending next ping.
-private let pingDelay: TimeInterval = 3
+private let pingDelay: Duration = .seconds(3)
/// Initial timeout when establishing connection.
-private let initialEstablishTimeout: TimeInterval = 4
+private let initialEstablishTimeout: Duration = .seconds(4)
/// Multiplier applied to `establishTimeout` on each failed connection attempt.
private let establishTimeoutMultiplier: UInt32 = 2
/// Maximum timeout when establishing connection.
-private let maxEstablishTimeout: TimeInterval = pingTimeout
+private let maxEstablishTimeout = pingTimeout
/// Connectivity check periodicity.
-private let connectivityCheckInterval: TimeInterval = 1
+private let connectivityCheckInterval: Duration = .seconds(1)
/// Inbound traffic timeout used when outbound traffic was registered prior to inbound traffic.
-private let inboundTrafficTimeout: TimeInterval = 5
+private let inboundTrafficTimeout: Duration = .seconds(5)
/// Traffic timeout applied when both tx/rx counters remain stale, i.e no traffic flowing.
/// Ping is issued after that timeout is exceeded.s
-private let trafficTimeout: TimeInterval = 120
+private let trafficTimeout: Duration = .minutes(2)
public final class TunnelMonitor: TunnelMonitorProtocol {
/// Connection state.
@@ -97,7 +98,7 @@ public final class TunnelMonitor: TunnelMonitorProtocol {
/// Retry attempt.
var retryAttempt: UInt32 = 0
- func evaluateConnection(now: Date, pingTimeout: TimeInterval) -> ConnectionEvaluation {
+ func evaluateConnection(now: Date, pingTimeout: Duration) -> ConnectionEvaluation {
switch connectionState {
case .connecting:
if now.timeIntervalSince(timeoutReference) >= pingTimeout {
@@ -161,7 +162,7 @@ public final class TunnelMonitor: TunnelMonitorProtocol {
return .ok
}
- func getPingTimeout() -> TimeInterval {
+ func getPingTimeout() -> Duration {
switch connectionState {
case .connecting:
let multiplier = establishTimeoutMultiplier.saturatingPow(retryAttempt)
@@ -575,7 +576,7 @@ public final class TunnelMonitor: TunnelMonitorProtocol {
timer.setEventHandler { [weak self] in
self?.checkConnectivity()
}
- timer.schedule(wallDeadline: .now(), repeating: connectivityCheckInterval)
+ timer.schedule(wallDeadline: .now(), repeating: connectivityCheckInterval.timeInterval)
timer.activate()
self.timer?.cancel()
diff --git a/ios/PacketTunnelCoreTests/Mocks/MockPinger.swift b/ios/PacketTunnelCoreTests/Mocks/MockPinger.swift
index e75918d0ad..1ff173b886 100644
--- a/ios/PacketTunnelCoreTests/Mocks/MockPinger.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/MockPinger.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadTypes
import Network
@testable import PacketTunnelCore
@@ -58,7 +59,7 @@ class MockPinger: PingerProtocol {
switch decideOutcome(address, nextSequenceId) {
case let .sendReply(reply, delay):
- DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
+ DispatchQueue.main.asyncAfter(wallDeadline: .now() + delay) { [weak self] in
guard let self else { return }
networkStatsReporting.reportBytesReceived(UInt64(icmpPacketSize))
@@ -109,7 +110,7 @@ class MockPinger: PingerProtocol {
/// The outcome of ping request simulation.
enum Outcome {
/// Simulate ping reply transmission.
- case sendReply(reply: Reply = .normal, afterDelay: TimeInterval = 0.1)
+ case sendReply(reply: Reply = .normal, afterDelay: Duration = .milliseconds(100))
/// Simulate packet that was lost or left unanswered.
case ignore