diff options
| author | Jon Petersson <jon.petersson@kvadrat.se> | 2023-08-11 16:26:48 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-08-28 17:16:06 +0200 |
| commit | bbb542e202b70bf921b050e6022cabb067988410 (patch) | |
| tree | 28f702fb3f710364d45048ea57c2b66ca935d9dd /ios | |
| parent | 6fd0f1213ce886b149516ed0d0a722988f77b277 (diff) | |
| download | mullvadvpn-bbb542e202b70bf921b050e6022cabb067988410.tar.xz mullvadvpn-bbb542e202b70bf921b050e6022cabb067988410.zip | |
Introduce Duration type that mimicks native iOS16 Duration
Diffstat (limited to 'ios')
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 |
