From 7cc36215cb99145e547f27dad23fab6210323c0c Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Thu, 7 Sep 2023 15:46:23 +0200 Subject: [PATCH] Fix Swiftlint warnings (file length) in files related to simulator tunnel --- ios/MullvadVPN.xcodeproj/project.pbxproj | 12 + .../SimulatorTunnelInfo.swift | 56 +++ .../SimulatorTunnelProvider.swift | 330 +----------------- .../SimulatorTunnelProviderManager.swift | 179 ++++++++++ .../SimulatorVPNConnection.swift | 118 +++++++ 5 files changed, 369 insertions(+), 326 deletions(-) create mode 100644 ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift create mode 100644 ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift create mode 100644 ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index ccf1018d0526..9fc62308e84e 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -395,6 +395,9 @@ 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 */; }; + 7A33538F2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */; }; + 7A3353912AAA014400F0A71C /* SimulatorVPNConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */; }; + 7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */; }; 7A3353972AAA0F8600F0A71C /* OperationBlockObserverSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */; }; 7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; }; 7A42DECD2A09064C00B209BE /* SelectableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */; }; @@ -1312,6 +1315,9 @@ 7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Appearance.swift"; sourceTree = ""; }; 7A307AD82A8CD8DA0017618B /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = ""; }; 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duration+Extensions.swift"; sourceTree = ""; }; + 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderManager.swift; sourceTree = ""; }; + 7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorVPNConnection.swift; sourceTree = ""; }; + 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelInfo.swift; sourceTree = ""; }; 7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationBlockObserverSupport.swift; sourceTree = ""; }; 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = ""; }; 7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsCell.swift; sourceTree = ""; }; @@ -1985,8 +1991,11 @@ 583FE02629C1ADB6006E85F9 /* SimulatorTunnelProvider */ = { isa = PBXGroup; children = ( + 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */, 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */, 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */, + 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */, + 7A3353902AAA014400F0A71C /* SimulatorVPNConnection.swift */, ); path = SimulatorTunnelProvider; sourceTree = ""; @@ -3736,6 +3745,7 @@ 587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */, F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */, 5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */, + 7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */, 5867771429097BCD006F721F /* PaymentState.swift in Sources */, F0EF50D32A8FA47E0031E8DF /* ChangeLogInteractor.swift in Sources */, F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */, @@ -3835,6 +3845,7 @@ 585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */, E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */, 58CC40EF24A601900019D96E /* ObserverList.swift in Sources */, + 7A33538F2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift in Sources */, 7A1A26432A2612AE00B978AA /* PaymentAlertPresenter.swift in Sources */, 7A9CCCBB2A96302800DD6A34 /* InAppPurchaseCoordinator.swift in Sources */, 58CCA01822426713004F3011 /* AccountViewController.swift in Sources */, @@ -3868,6 +3879,7 @@ 5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */, 5891BF1C25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift in Sources */, 5878A26F2907E7E00096FC88 /* ProblemReportInteractor.swift in Sources */, + 7A3353912AAA014400F0A71C /* SimulatorVPNConnection.swift in Sources */, 7AE47E522A17972A000418DA /* CustomAlertViewController.swift in Sources */, F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */, 58E11188292FA11F009FCA84 /* SettingsMigrationUIHandler.swift in Sources */, diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift new file mode 100644 index 000000000000..a1324df09fe7 --- /dev/null +++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelInfo.swift @@ -0,0 +1,56 @@ +// +// SimulatorTunnelInfo.swift +// MullvadVPN +// +// Created by Jon Petersson on 2023-09-07. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +#if targetEnvironment(simulator) + +import Foundation +import NetworkExtension + +final class SimulatorTunnelProviderSession: SimulatorVPNConnection, VPNTunnelProviderSessionProtocol { + func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws { + SimulatorTunnelProvider.shared.handleAppMessage( + messageData, + completionHandler: responseHandler + ) + } +} + +/// A mock struct for tunnel configuration and connection +struct SimulatorTunnelInfo { + /// A unique identifier for the configuration + var identifier = UUID().uuidString + + /// An associated VPN connection. + /// Intentionally initialized with a `SimulatorTunnelProviderSession` subclass which + /// implements the necessary protocol + var connection: SimulatorVPNConnection = SimulatorTunnelProviderSession() + + /// Whether configuration is enabled + var isEnabled = false + + /// Whether on-demand VPN is enabled + var isOnDemandEnabled = false + + /// On-demand VPN rules + var onDemandRules = [NEOnDemandRule]() + + /// Protocol configuration + var protocolConfiguration: NEVPNProtocol? { + didSet { + connection.protocolConfiguration = protocolConfiguration ?? NEVPNProtocol() + } + } + + /// Tunnel description + var localizedDescription: String? + + /// Designated initializer + init() {} +} + +#endif diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift index a2eb3d5f4314..debaf16949f8 100644 --- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift +++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProvider.swift @@ -6,11 +6,11 @@ // Copyright © 2020 Mullvad VPN AB. All rights reserved. // +#if targetEnvironment(simulator) + import Foundation import NetworkExtension -// MARK: - Formal conformances - protocol VPNConnectionProtocol: NSObject { var status: NEVPNStatus { get } var connectedDate: Date? { get } @@ -46,12 +46,8 @@ extension NEVPNConnection: VPNConnectionProtocol {} extension NETunnelProviderSession: VPNTunnelProviderSessionProtocol {} extension NETunnelProviderManager: VPNTunnelProviderManagerProtocol {} -#if targetEnvironment(simulator) - -// MARK: - NEPacketTunnelProvider stubs - class SimulatorTunnelProviderDelegate { - fileprivate(set) var connection: SimulatorVPNConnection? + var connection: SimulatorVPNConnection? var protocolConfiguration: NEVPNProtocol { connection?.protocolConfiguration ?? NEVPNProtocol() @@ -101,327 +97,9 @@ final class SimulatorTunnelProvider { private init() {} - fileprivate func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { + func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { delegate.handleAppMessage(messageData, completionHandler: completionHandler) } } -// MARK: - NEVPNConnection stubs - -class SimulatorVPNConnection: NSObject, VPNConnectionProtocol { - // Protocol configuration is automatically synced by `SimulatorTunnelInfo` - fileprivate var protocolConfiguration = NEVPNProtocol() - - private let lock = NSRecursiveLock() - private var _status: NEVPNStatus = .disconnected - private var _reasserting = false - private var _connectedDate: Date? - - private(set) var status: NEVPNStatus { - get { - lock.lock() - defer { lock.unlock() } - - return _status - } - set { - lock.lock() - - if _status != newValue { - _status = newValue - - // Send notification while holding the lock. This should enable the receiver - // to fetch the `SimulatorVPNConnection.status` before the concurrent code gets - // opportunity to change it again. - postStatusDidChangeNotification() - } - - lock.unlock() - } - } - - var reasserting: Bool { - get { - lock.lock() - defer { lock.unlock() } - - return _reasserting - } - set { - lock.lock() - - if _reasserting != newValue { - _reasserting = newValue - - if newValue { - status = .reasserting - } else { - status = .connected - } - } - - lock.unlock() - } - } - - private(set) var connectedDate: Date? { - get { - lock.lock() - defer { lock.unlock() } - - return _connectedDate - } - set { - lock.lock() - _connectedDate = newValue - lock.unlock() - } - } - - func startVPNTunnel() throws { - try startVPNTunnel(options: nil) - } - - func startVPNTunnel(options: [String: NSObject]?) throws { - SimulatorTunnelProvider.shared.delegate.connection = self - - status = .connecting - - SimulatorTunnelProvider.shared.delegate.startTunnel(options: options) { error in - if error == nil { - self.status = .connected - self.connectedDate = Date() - } else { - self.status = .disconnected - self.connectedDate = nil - } - } - } - - func stopVPNTunnel() { - status = .disconnecting - - SimulatorTunnelProvider.shared.delegate.stopTunnel(with: .userInitiated) { - self.status = .disconnected - self.connectedDate = nil - } - } - - private func postStatusDidChangeNotification() { - NotificationCenter.default.post(name: .NEVPNStatusDidChange, object: self) - } -} - -// MARK: - NETunnelProviderSession stubs - -final class SimulatorTunnelProviderSession: SimulatorVPNConnection, VPNTunnelProviderSessionProtocol { - func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws { - SimulatorTunnelProvider.shared.handleAppMessage( - messageData, - completionHandler: responseHandler - ) - } -} - -// MARK: - NETunnelProviderManager stubs - -/// A mock struct for tunnel configuration and connection -private struct SimulatorTunnelInfo { - /// A unique identifier for the configuration - var identifier = UUID().uuidString - - /// An associated VPN connection. - /// Intentionally initialized with a `SimulatorTunnelProviderSession` subclass which - /// implements the necessary protocol - var connection: SimulatorVPNConnection = SimulatorTunnelProviderSession() - - /// Whether configuration is enabled - var isEnabled = false - - /// Whether on-demand VPN is enabled - var isOnDemandEnabled = false - - /// On-demand VPN rules - var onDemandRules = [NEOnDemandRule]() - - /// Protocol configuration - var protocolConfiguration: NEVPNProtocol? { - didSet { - connection.protocolConfiguration = protocolConfiguration ?? NEVPNProtocol() - } - } - - /// Tunnel description - var localizedDescription: String? - - /// Designated initializer - init() {} -} - -final class SimulatorTunnelProviderManager: NSObject, VPNTunnelProviderManagerProtocol { - static let tunnelsLock = NSRecursiveLock() - fileprivate static var tunnels = [SimulatorTunnelInfo]() - - private let lock = NSLock() - private var tunnelInfo: SimulatorTunnelInfo - private var identifier: String { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.identifier - } - - var isOnDemandEnabled: Bool { - get { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.isOnDemandEnabled - } - set { - lock.lock() - tunnelInfo.isOnDemandEnabled = newValue - lock.unlock() - } - } - - var onDemandRules: [NEOnDemandRule] { - get { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.onDemandRules - } - set { - lock.lock() - tunnelInfo.onDemandRules = newValue - lock.unlock() - } - } - - var isEnabled: Bool { - get { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.isEnabled - } - set { - lock.lock() - tunnelInfo.isEnabled = newValue - lock.unlock() - } - } - - var protocolConfiguration: NEVPNProtocol? { - get { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.protocolConfiguration - } - set { - lock.lock() - tunnelInfo.protocolConfiguration = newValue - lock.unlock() - } - } - - var localizedDescription: String? { - get { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.localizedDescription - } - set { - lock.lock() - tunnelInfo.localizedDescription = newValue - lock.unlock() - } - } - - var connection: SimulatorVPNConnection { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.connection - } - - static func loadAllFromPreferences(completionHandler: ( - [SimulatorTunnelProviderManager]?, - Error? - ) -> Void) { - Self.tunnelsLock.lock() - let tunnelProviders = tunnels.map { tunnelInfo in - SimulatorTunnelProviderManager(tunnelInfo: tunnelInfo) - } - Self.tunnelsLock.unlock() - - completionHandler(tunnelProviders, nil) - } - - override required init() { - tunnelInfo = SimulatorTunnelInfo() - super.init() - } - - private init(tunnelInfo: SimulatorTunnelInfo) { - self.tunnelInfo = tunnelInfo - super.init() - } - - func loadFromPreferences(completionHandler: (Error?) -> Void) { - var error: NEVPNError? - - Self.tunnelsLock.lock() - - if let savedTunnel = Self.tunnels.first(where: { $0.identifier == self.identifier }) { - tunnelInfo = savedTunnel - } else { - error = NEVPNError(.configurationInvalid) - } - - Self.tunnelsLock.unlock() - - completionHandler(error) - } - - func saveToPreferences(completionHandler: ((Error?) -> Void)?) { - Self.tunnelsLock.lock() - - if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { - Self.tunnels[index] = tunnelInfo - } else { - Self.tunnels.append(tunnelInfo) - } - - Self.tunnelsLock.unlock() - - completionHandler?(nil) - } - - func removeFromPreferences(completionHandler: ((Error?) -> Void)?) { - var error: NEVPNError? - - Self.tunnelsLock.lock() - - if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { - Self.tunnels.remove(at: index) - } else { - error = NEVPNError(.configurationReadWriteFailed) - } - - Self.tunnelsLock.unlock() - - completionHandler?(error) - } - - override func isEqual(_ object: Any?) -> Bool { - guard let other = object as? Self else { return false } - return self.identifier == other.identifier - } -} - -// swiftlint:disable:next file_length #endif diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift new file mode 100644 index 000000000000..c8bc1fe7fe93 --- /dev/null +++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderManager.swift @@ -0,0 +1,179 @@ +// +// SimulatorTunnelProviderManager.swift +// MullvadVPN +// +// Created by Jon Petersson on 2023-09-07. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +#if targetEnvironment(simulator) + +import Foundation +import NetworkExtension + +final class SimulatorTunnelProviderManager: NSObject, VPNTunnelProviderManagerProtocol { + static let tunnelsLock = NSRecursiveLock() + fileprivate static var tunnels = [SimulatorTunnelInfo]() + + private let lock = NSLock() + private var tunnelInfo: SimulatorTunnelInfo + private var identifier: String { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.identifier + } + + var isOnDemandEnabled: Bool { + get { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.isOnDemandEnabled + } + set { + lock.lock() + tunnelInfo.isOnDemandEnabled = newValue + lock.unlock() + } + } + + var onDemandRules: [NEOnDemandRule] { + get { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.onDemandRules + } + set { + lock.lock() + tunnelInfo.onDemandRules = newValue + lock.unlock() + } + } + + var isEnabled: Bool { + get { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.isEnabled + } + set { + lock.lock() + tunnelInfo.isEnabled = newValue + lock.unlock() + } + } + + var protocolConfiguration: NEVPNProtocol? { + get { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.protocolConfiguration + } + set { + lock.lock() + tunnelInfo.protocolConfiguration = newValue + lock.unlock() + } + } + + var localizedDescription: String? { + get { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.localizedDescription + } + set { + lock.lock() + tunnelInfo.localizedDescription = newValue + lock.unlock() + } + } + + var connection: SimulatorVPNConnection { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.connection + } + + static func loadAllFromPreferences(completionHandler: ( + [SimulatorTunnelProviderManager]?, + Error? + ) -> Void) { + Self.tunnelsLock.lock() + let tunnelProviders = tunnels.map { tunnelInfo in + SimulatorTunnelProviderManager(tunnelInfo: tunnelInfo) + } + Self.tunnelsLock.unlock() + + completionHandler(tunnelProviders, nil) + } + + override required init() { + tunnelInfo = SimulatorTunnelInfo() + super.init() + } + + private init(tunnelInfo: SimulatorTunnelInfo) { + self.tunnelInfo = tunnelInfo + super.init() + } + + func loadFromPreferences(completionHandler: (Error?) -> Void) { + var error: NEVPNError? + + Self.tunnelsLock.lock() + + if let savedTunnel = Self.tunnels.first(where: { $0.identifier == self.identifier }) { + tunnelInfo = savedTunnel + } else { + error = NEVPNError(.configurationInvalid) + } + + Self.tunnelsLock.unlock() + + completionHandler(error) + } + + func saveToPreferences(completionHandler: ((Error?) -> Void)?) { + Self.tunnelsLock.lock() + + if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { + Self.tunnels[index] = tunnelInfo + } else { + Self.tunnels.append(tunnelInfo) + } + + Self.tunnelsLock.unlock() + + completionHandler?(nil) + } + + func removeFromPreferences(completionHandler: ((Error?) -> Void)?) { + var error: NEVPNError? + + Self.tunnelsLock.lock() + + if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { + Self.tunnels.remove(at: index) + } else { + error = NEVPNError(.configurationReadWriteFailed) + } + + Self.tunnelsLock.unlock() + + completionHandler?(error) + } + + override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? Self else { return false } + return self.identifier == other.identifier + } +} + +#endif diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift new file mode 100644 index 000000000000..6b235d285c9a --- /dev/null +++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift @@ -0,0 +1,118 @@ +// +// SimulatorVPNConnection.swift +// MullvadVPN +// +// Created by Jon Petersson on 2023-09-07. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +#if targetEnvironment(simulator) + +import Foundation +import NetworkExtension + +class SimulatorVPNConnection: NSObject, VPNConnectionProtocol { + // Protocol configuration is automatically synced by `SimulatorTunnelInfo` + var protocolConfiguration = NEVPNProtocol() + + private let lock = NSRecursiveLock() + private var _status: NEVPNStatus = .disconnected + private var _reasserting = false + private var _connectedDate: Date? + + private(set) var status: NEVPNStatus { + get { + lock.lock() + defer { lock.unlock() } + + return _status + } + set { + lock.lock() + + if _status != newValue { + _status = newValue + + // Send notification while holding the lock. This should enable the receiver + // to fetch the `SimulatorVPNConnection.status` before the concurrent code gets + // opportunity to change it again. + postStatusDidChangeNotification() + } + + lock.unlock() + } + } + + var reasserting: Bool { + get { + lock.lock() + defer { lock.unlock() } + + return _reasserting + } + set { + lock.lock() + + if _reasserting != newValue { + _reasserting = newValue + + if newValue { + status = .reasserting + } else { + status = .connected + } + } + + lock.unlock() + } + } + + private(set) var connectedDate: Date? { + get { + lock.lock() + defer { lock.unlock() } + + return _connectedDate + } + set { + lock.lock() + _connectedDate = newValue + lock.unlock() + } + } + + func startVPNTunnel() throws { + try startVPNTunnel(options: nil) + } + + func startVPNTunnel(options: [String: NSObject]?) throws { + SimulatorTunnelProvider.shared.delegate.connection = self + + status = .connecting + + SimulatorTunnelProvider.shared.delegate.startTunnel(options: options) { error in + if error == nil { + self.status = .connected + self.connectedDate = Date() + } else { + self.status = .disconnected + self.connectedDate = nil + } + } + } + + func stopVPNTunnel() { + status = .disconnecting + + SimulatorTunnelProvider.shared.delegate.stopTunnel(with: .userInitiated) { + self.status = .disconnected + self.connectedDate = nil + } + } + + private func postStatusDidChangeNotification() { + NotificationCenter.default.post(name: .NEVPNStatusDidChange, object: self) + } +} + +#endif