summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorAndrew Bulhak <andrew.bulhak@mullvad.net>2024-03-14 15:10:10 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-03-15 13:15:20 +0100
commitdcefe5829105efa137feb3128017dc0e3ce946f2 (patch)
treeefaadc2a1c51dcfd4919d0c97377f3adddb5359b /ios
parent7a74b8493ba7b2334a4bbed306d5ef32868bbab5 (diff)
downloadmullvadvpn-dcefe5829105efa137feb3128017dc0e3ce946f2.tar.xz
mullvadvpn-dcefe5829105efa137feb3128017dc0e3ce946f2.zip
Move CommandChannel into the PacketTunnelActor namespace for consistency with Command
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj8
-rw-r--r--ios/PacketTunnelCore/Actor/Command.swift70
-rw-r--r--ios/PacketTunnelCore/Actor/CommandChannel.swift233
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift3
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift78
-rw-r--r--ios/PacketTunnelCoreTests/CommandChannelTests.swift10
6 files changed, 208 insertions, 194 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 7eba46f8dd..c7427d9ef1 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -106,7 +106,7 @@
583832232AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832222AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift */; };
583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832242AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift */; };
583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832262AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift */; };
- 583832292AC3DF1300EA2071 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832282AC3DF1300EA2071 /* Command.swift */; };
+ 583832292AC3DF1300EA2071 /* PacketTunnelActorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832282AC3DF1300EA2071 /* PacketTunnelActorCommand.swift */; };
5838322B2AC3EF9600EA2071 /* CommandChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838322A2AC3EF9600EA2071 /* CommandChannel.swift */; };
583D86482A2678DC0060D63B /* DeviceStateAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */; };
583DA21425FA4B5C00318683 /* LocationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583DA21325FA4B5C00318683 /* LocationDataSource.swift */; };
@@ -1407,7 +1407,7 @@
583832222AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+ErrorState.swift"; sourceTree = "<group>"; };
583832242AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+ConnectionMonitoring.swift"; sourceTree = "<group>"; };
583832262AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+SleepCycle.swift"; sourceTree = "<group>"; };
- 583832282AC3DF1300EA2071 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
+ 583832282AC3DF1300EA2071 /* PacketTunnelActorCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorCommand.swift; sourceTree = "<group>"; };
5838322A2AC3EF9600EA2071 /* CommandChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandChannel.swift; sourceTree = "<group>"; };
583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStateAccessor.swift; sourceTree = "<group>"; };
583DA21325FA4B5C00318683 /* LocationDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataSource.swift; sourceTree = "<group>"; };
@@ -2752,7 +2752,7 @@
children = (
58BDEBA02A9CA14B00F578F2 /* AnyTask.swift */,
58F3F3652AA086A400D3B0A4 /* AutoCancellingTask.swift */,
- 583832282AC3DF1300EA2071 /* Command.swift */,
+ 583832282AC3DF1300EA2071 /* PacketTunnelActorCommand.swift */,
5838322A2AC3EF9600EA2071 /* CommandChannel.swift */,
583E60952A9F6D0800DC61EF /* ConfigurationBuilder.swift */,
580D6B892AB31AB400B2D6E0 /* NetworkPath+NetworkReachability.swift */,
@@ -4995,7 +4995,7 @@
58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */,
58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */,
58C7A4512A863FB50060C66F /* PingerProtocol.swift in Sources */,
- 583832292AC3DF1300EA2071 /* Command.swift in Sources */,
+ 583832292AC3DF1300EA2071 /* PacketTunnelActorCommand.swift in Sources */,
58CF95A22AD6F35800B59F5D /* ObservedState.swift in Sources */,
583832232AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift in Sources */,
58C7AF112ABD8480007EDD7A /* TunnelProviderMessage.swift in Sources */,
diff --git a/ios/PacketTunnelCore/Actor/Command.swift b/ios/PacketTunnelCore/Actor/Command.swift
deleted file mode 100644
index 668f444d49..0000000000
--- a/ios/PacketTunnelCore/Actor/Command.swift
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-// Command.swift
-// PacketTunnelCore
-//
-// Created by pronebird on 27/09/2023.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-/// Describes action that actor can perform.
-enum Command {
- /// Start tunnel.
- case start(StartOptions)
-
- /// Stop tunnel.
- case stop
-
- /// Reconnect tunnel.
- case reconnect(NextRelay, reason: ReconnectReason = .userInitiated)
-
- /// Enter blocked state.
- case error(BlockedStateReason)
-
- /// Notify that key rotation took place
- case notifyKeyRotated(Date?)
-
- /// Switch to using the recently pushed WG key.
- case switchKey
-
- /// Monitor events.
- case monitorEvent(_ event: TunnelMonitorEvent)
-
- /// Network reachability events.
- case networkReachability(NetworkPath)
-
- /// Format command for log output.
- func logFormat() -> String {
- switch self {
- case .start:
- return "start"
- case .stop:
- return "stop"
- case let .reconnect(nextRelay, stopTunnelMonitor):
- switch nextRelay {
- case .current:
- return "reconnect(current, \(stopTunnelMonitor))"
- case .random:
- return "reconnect(random, \(stopTunnelMonitor))"
- case let .preSelected(selectedRelay):
- return "reconnect(\(selectedRelay.hostname), \(stopTunnelMonitor))"
- }
- case let .error(reason):
- return "error(\(reason))"
- case .notifyKeyRotated:
- return "notifyKeyRotated"
- case let .monitorEvent(event):
- switch event {
- case .connectionEstablished:
- return "monitorEvent(connectionEstablished)"
- case .connectionLost:
- return "monitorEvent(connectionLost)"
- }
- case .networkReachability:
- return "networkReachability"
- case .switchKey:
- return "switchKey"
- }
- }
-}
diff --git a/ios/PacketTunnelCore/Actor/CommandChannel.swift b/ios/PacketTunnelCore/Actor/CommandChannel.swift
index ca19794b96..e159100c34 100644
--- a/ios/PacketTunnelCore/Actor/CommandChannel.swift
+++ b/ios/PacketTunnelCore/Actor/CommandChannel.swift
@@ -47,167 +47,170 @@ import Foundation
.reduce(into: [String]()) { $0.append($1) }
```
*/
-final class CommandChannel: @unchecked Sendable {
- private enum State {
- /// Channel is active and running.
- case active
+extension PacketTunnelActor {
+ final class CommandChannel: @unchecked Sendable {
+ typealias Command = PacketTunnelActor.Command
+ private enum State {
+ /// Channel is active and running.
+ case active
- /// Channel is awaiting for the buffer to be exhausted before ending all async iterations.
- /// Publishing new values in this state is impossible.
- case pendingEnd
+ /// Channel is awaiting for the buffer to be exhausted before ending all async iterations.
+ /// Publishing new values in this state is impossible.
+ case pendingEnd
- /// Channel finished its work.
- /// Publishing new values in this state is impossible.
- /// An attempt to iterate over the channel in this state is equivalent to iterating over an empty array.
- case finished
- }
+ /// Channel finished its work.
+ /// Publishing new values in this state is impossible.
+ /// An attempt to iterate over the channel in this state is equivalent to iterating over an empty array.
+ case finished
+ }
- /// A buffer of commands received but not consumed yet.
- private var buffer: [Command] = []
+ /// A buffer of commands received but not consumed yet.
+ private var buffer: [Command] = []
- /// Async continuations awaiting to receive the new value.
- /// Continuations are stored here when there is no new value available for immediate delivery.
- private var pendingContinuations: [CheckedContinuation<Command?, Never>] = []
+ /// Async continuations awaiting to receive the new value.
+ /// Continuations are stored here when there is no new value available for immediate delivery.
+ private var pendingContinuations: [CheckedContinuation<Command?, Never>] = []
- private var state: State = .active
- private var stateLock = NSLock()
+ private var state: State = .active
+ private var stateLock = NSLock()
- init() {}
+ init() {}
- deinit {
- // Resume all continuations
- finish()
- }
+ deinit {
+ // Resume all continuations
+ finish()
+ }
- /// Send command to consumer.
- ///
- /// - Parameter value: a new command.
- func send(_ value: Command) {
- stateLock.withLock {
- guard case .active = state else { return }
+ /// Send command to consumer.
+ ///
+ /// - Parameter value: a new command.
+ func send(_ value: Command) {
+ stateLock.withLock {
+ guard case .active = state else { return }
- buffer.append(value)
+ buffer.append(value)
- if !pendingContinuations.isEmpty, let nextValue = consumeFirst() {
- let continuation = pendingContinuations.removeFirst()
- continuation.resume(returning: nextValue)
+ if !pendingContinuations.isEmpty, let nextValue = consumeFirst() {
+ let continuation = pendingContinuations.removeFirst()
+ continuation.resume(returning: nextValue)
+ }
}
}
- }
- /// Mark the end of channel but let consumers exchaust the buffer before declaring the end of iteration.
- /// If the buffer is empty then it should resume all pending continuations and send them `nil` to mark the end of iteration.
- func sendEnd() {
- stateLock.withLock {
- if case .active = state {
- state = .pendingEnd
+ /// Mark the end of channel but let consumers exchaust the buffer before declaring the end of iteration.
+ /// If the buffer is empty then it should resume all pending continuations and send them `nil` to mark the end of iteration.
+ func sendEnd() {
+ stateLock.withLock {
+ if case .active = state {
+ state = .pendingEnd
- if buffer.isEmpty {
- state = .finished
- sendEndToPendingContinuations()
+ if buffer.isEmpty {
+ state = .finished
+ sendEndToPendingContinuations()
+ }
}
}
}
- }
- /// Flush buffered commands and resume all pending continuations sending them `nil` to mark the end of iteration.
- func finish() {
- stateLock.withLock {
- switch state {
- case .active, .pendingEnd:
- state = .finished
- buffer.removeAll()
+ /// Flush buffered commands and resume all pending continuations sending them `nil` to mark the end of iteration.
+ func finish() {
+ stateLock.withLock {
+ switch state {
+ case .active, .pendingEnd:
+ state = .finished
+ buffer.removeAll()
- sendEndToPendingContinuations()
+ sendEndToPendingContinuations()
- case .finished:
- break
+ case .finished:
+ break
+ }
}
}
- }
- /// Send `nil` to mark the end of iteration to all pending continuations.
- private func sendEndToPendingContinuations() {
- for continuation in pendingContinuations {
- continuation.resume(returning: nil)
+ /// Send `nil` to mark the end of iteration to all pending continuations.
+ private func sendEndToPendingContinuations() {
+ for continuation in pendingContinuations {
+ continuation.resume(returning: nil)
+ }
+ pendingContinuations.removeAll()
}
- pendingContinuations.removeAll()
- }
- /// Consume first message in the buffer.
- /// Returns `nil` if the buffer is empty, otherwise if attempts to coalesce buffered commands before consuming the first comand in the list.
- private func consumeFirst() -> Command? {
- guard !buffer.isEmpty else { return nil }
+ /// Consume first message in the buffer.
+ /// Returns `nil` if the buffer is empty, otherwise if attempts to coalesce buffered commands before consuming the first comand in the list.
+ private func consumeFirst() -> Command? {
+ guard !buffer.isEmpty else { return nil }
- coalesce()
- return buffer.removeFirst()
- }
+ coalesce()
+ return buffer.removeFirst()
+ }
- /// Coalesce buffered commands to prevent future execution when the outcome is considered to be similar.
- /// Mutates internal `buffer`.
- private func coalesce() {
- var i = buffer.count - 1
- while i > 0 {
- defer { i -= 1 }
+ /// Coalesce buffered commands to prevent future execution when the outcome is considered to be similar.
+ /// Mutates internal `buffer`.
+ private func coalesce() {
+ var i = buffer.count - 1
+ while i > 0 {
+ defer { i -= 1 }
- assert(i < buffer.count)
- let current = buffer[i]
+ assert(i < buffer.count)
+ let current = buffer[i]
- // Remove all preceding commands when encountered "stop".
- if case .stop = current {
- buffer.removeFirst(i)
- return
- }
+ // Remove all preceding commands when encountered "stop".
+ if case .stop = current {
+ buffer.removeFirst(i)
+ return
+ }
- // Coalesce earlier reconnection attempts into the most recent.
- // This will rearrange the command buffer but hopefully should have no side effects.
- if case .reconnect = current {
- // Walk backwards starting with the preceding element.
- for j in (0 ..< i).reversed() {
- let preceding = buffer[j]
- // Remove preceding reconnect and adjust the index of the outer loop.
- if case .reconnect = preceding {
- buffer.remove(at: j)
- i -= 1
+ // Coalesce earlier reconnection attempts into the most recent.
+ // This will rearrange the command buffer but hopefully should have no side effects.
+ if case .reconnect = current {
+ // Walk backwards starting with the preceding element.
+ for j in (0 ..< i).reversed() {
+ let preceding = buffer[j]
+ // Remove preceding reconnect and adjust the index of the outer loop.
+ if case .reconnect = preceding {
+ buffer.remove(at: j)
+ i -= 1
+ }
}
}
}
}
- }
- private func next() async -> Command? {
- return await withCheckedContinuation { continuation in
- stateLock.withLock {
- switch state {
- case .pendingEnd:
- if buffer.isEmpty {
- state = .finished
- continuation.resume(returning: nil)
- } else {
- // Keep consuming until the buffer is exhausted.
- fallthrough
- }
+ private func next() async -> Command? {
+ return await withCheckedContinuation { continuation in
+ stateLock.withLock {
+ switch state {
+ case .pendingEnd:
+ if buffer.isEmpty {
+ state = .finished
+ continuation.resume(returning: nil)
+ } else {
+ // Keep consuming until the buffer is exhausted.
+ fallthrough
+ }
- case .active:
- if let value = consumeFirst() {
- continuation.resume(returning: value)
- } else {
- pendingContinuations.append(continuation)
- }
+ case .active:
+ if let value = consumeFirst() {
+ continuation.resume(returning: value)
+ } else {
+ pendingContinuations.append(continuation)
+ }
- case .finished:
- continuation.resume(returning: nil)
+ case .finished:
+ continuation.resume(returning: nil)
+ }
}
}
}
}
}
-extension CommandChannel: AsyncSequence {
+extension PacketTunnelActor.CommandChannel: AsyncSequence {
typealias Element = Command
struct AsyncIterator: AsyncIteratorProtocol {
- let channel: CommandChannel
+ let channel: PacketTunnelActor.CommandChannel
func next() async -> Command? {
return await channel.next()
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index 4d40fdf1e6..e7a384d8b6 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -111,6 +111,9 @@ public actor PacketTunnelActor {
case let .networkReachability(defaultPath):
await handleDefaultPathChange(defaultPath)
+
+ case .replaceDevicePrivateKey:
+ self.logger.warning("Not yet implemented")
}
}
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
new file mode 100644
index 0000000000..be325fc7b6
--- /dev/null
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
@@ -0,0 +1,78 @@
+//
+// Command.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 27/09/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import WireGuardKitTypes
+
+extension PacketTunnelActor {
+ /// Describes action that actor can perform.
+ enum Command {
+ /// Start tunnel.
+ case start(StartOptions)
+
+ /// Stop tunnel.
+ case stop
+
+ /// Reconnect tunnel.
+ case reconnect(NextRelay, reason: ReconnectReason = .userInitiated)
+
+ /// Enter blocked state.
+ case error(BlockedStateReason)
+
+ /// Notify that key rotation took place
+ case notifyKeyRotated(Date?)
+
+ /// Switch to using the recently pushed WG key.
+ case switchKey
+
+ /// Monitor events.
+ case monitorEvent(_ event: TunnelMonitorEvent)
+
+ /// Network reachability events.
+ case networkReachability(NetworkPath)
+
+ /// Update the device private key, as per post-quantum protocols
+ case replaceDevicePrivateKey(PreSharedKey)
+
+ /// Format command for log output.
+ func logFormat() -> String {
+ switch self {
+ case .start:
+ return "start"
+ case .stop:
+ return "stop"
+ case let .reconnect(nextRelay, stopTunnelMonitor):
+ switch nextRelay {
+ case .current:
+ return "reconnect(current, \(stopTunnelMonitor))"
+ case .random:
+ return "reconnect(random, \(stopTunnelMonitor))"
+ case let .preSelected(selectedRelay):
+ return "reconnect(\(selectedRelay.hostname), \(stopTunnelMonitor))"
+ }
+ case let .error(reason):
+ return "error(\(reason))"
+ case .notifyKeyRotated:
+ return "notifyKeyRotated"
+ case let .monitorEvent(event):
+ switch event {
+ case .connectionEstablished:
+ return "monitorEvent(connectionEstablished)"
+ case .connectionLost:
+ return "monitorEvent(connectionLost)"
+ }
+ case .networkReachability:
+ return "networkReachability"
+ case .switchKey:
+ return "switchKey"
+ case .replaceDevicePrivateKey:
+ return "replaceDevicePrivateKey"
+ }
+ }
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/CommandChannelTests.swift b/ios/PacketTunnelCoreTests/CommandChannelTests.swift
index dc622434b9..974eca29f2 100644
--- a/ios/PacketTunnelCoreTests/CommandChannelTests.swift
+++ b/ios/PacketTunnelCoreTests/CommandChannelTests.swift
@@ -11,7 +11,7 @@ import XCTest
final class CommandChannelTests: XCTestCase {
func testCoalescingReconnect() async {
- let channel = CommandChannel()
+ let channel = PacketTunnelActor.CommandChannel()
channel.send(.start(StartOptions(launchSource: .app)))
channel.send(.reconnect(.random))
@@ -27,7 +27,7 @@ final class CommandChannelTests: XCTestCase {
/// Test that stops cancels all preceding tasks.
func testCoalescingStop() async {
- let channel = CommandChannel()
+ let channel = PacketTunnelActor.CommandChannel()
channel.send(.start(StartOptions(launchSource: .app)))
channel.send(.reconnect(.random))
@@ -44,7 +44,7 @@ final class CommandChannelTests: XCTestCase {
/// Test that iterations over the finished channel yield `nil`.
func testFinishFlushingUnconsumedValues() async {
- let channel = CommandChannel()
+ let channel = PacketTunnelActor.CommandChannel()
channel.send(.stop)
channel.finish()
@@ -54,7 +54,7 @@ final class CommandChannelTests: XCTestCase {
/// Test that the call to `finish()` ends the iteration that began prior to that.
func testFinishEndsAsyncIterator() async throws {
- let channel = CommandChannel()
+ let channel = PacketTunnelActor.CommandChannel()
let expectFinish = expectation(description: "Call to finish()")
let expectEndIteration = expectation(description: "Iteration over channel should end upon call to finish()")
@@ -91,7 +91,7 @@ enum PrimitiveCommand: Equatable {
case start, stop, reconnect(NextRelay), switchKey, other
}
-extension Command {
+extension PacketTunnelActor.Command {
var primitiveCommand: PrimitiveCommand {
switch self {
case .start: