From 3a59ac76847e271ab977d4949ce7249e7912eaf4 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sun, 28 May 2023 09:56:51 +0200 Subject: [PATCH] Invoke VPNManager methods with a Profile object (#309) The VPNConfiguration parameter is opaque and tightly coupled to TunnelKit. Connecting to a Profile makes infinitely more sense, beyond simplifying the VPNManager class. Configuration building is fully delegated to the strategy (as it has to be). - VPNManager takes Profile and produces VPNConfigurationParameters - VPNManagerStrategy takes VPNConfigurationParameters (abstract) - TunnelKitVPNManagerStrategy takes VPNConfigurationParameters and produces TunnelKitVPNConfiguration internally --- PassepartoutLibrary/Package.swift | 2 +- .../PassepartoutCore/Utils/Utils+Dates.swift | 6 + .../Domain/Profile+OpenVPNSettings.swift | 16 +++ .../Domain/Profile+WireGuardSettings.swift | 16 +++ .../PassepartoutVPN/Domain/Profile.swift | 34 ----- .../Domain/VPNConfiguration.swift | 29 ---- .../Domain/VPNConfigurationParameters.swift | 6 +- .../Managers/VPNManager+Extensions.swift | 9 +- .../PassepartoutVPN/Managers/VPNManager.swift | 107 +++++++-------- .../Strategies/VPNManagerStrategy.swift | 11 +- .../OpenVPNSettings+TunnelKit.swift | 2 +- .../WireGuardSettings+TunnelKit.swift | 2 +- .../TunnelKitVPNManagerStrategy.swift | 125 ++++++++++-------- .../ProvidersTests.swift | 1 - 14 files changed, 176 insertions(+), 190 deletions(-) delete mode 100644 PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfiguration.swift diff --git a/PassepartoutLibrary/Package.swift b/PassepartoutLibrary/Package.swift index 7c90df34..e8987693 100644 --- a/PassepartoutLibrary/Package.swift +++ b/PassepartoutLibrary/Package.swift @@ -85,7 +85,7 @@ let package = Package( .target( name: "PassepartoutCore", dependencies: [ - .product(name: "GenericJSON", package: "generic-json-swift") // FIXME: arch, drop this + .product(name: "GenericJSON", package: "generic-json-swift") ]), // MARK: App extensions diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Dates.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Dates.swift index 7ea04d50..514b9ac7 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Dates.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Dates.swift @@ -52,3 +52,9 @@ extension TimeInterval { return str } } + +extension TimeInterval { + public var dispatchTimeInterval: DispatchTimeInterval { + .nanoseconds(Int(self * Double(NSEC_PER_SEC))) + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OpenVPNSettings.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OpenVPNSettings.swift index a117dfd3..3e8e8b0e 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OpenVPNSettings.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OpenVPNSettings.swift @@ -43,4 +43,20 @@ extension Profile { self.configuration = configuration } } + + init(_ id: UUID = UUID(), name: String, configuration: OpenVPN.Configuration) { + let header = Header( + uuid: id, + name: name, + providerName: nil + ) + self.init(header, configuration: configuration) + } + + public init(_ header: Header, configuration: OpenVPN.Configuration) { + self.header = header + currentVPNProtocol = .openVPN + host = Host() + host?.ovpnSettings = OpenVPNSettings(configuration: configuration) + } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+WireGuardSettings.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+WireGuardSettings.swift index ddd8cb90..19cce0fd 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+WireGuardSettings.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+WireGuardSettings.swift @@ -39,4 +39,20 @@ extension Profile { self.configuration = configuration } } + + init(_ id: UUID = UUID(), name: String, configuration: WireGuard.Configuration) { + let header = Header( + uuid: id, + name: name, + providerName: nil + ) + self.init(header, configuration: configuration) + } + + public init(_ header: Header, configuration: WireGuard.Configuration) { + self.header = header + currentVPNProtocol = .wireGuard + host = Host() + host?.wgSettings = WireGuardSettings(configuration: configuration) + } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile.swift index c32d73a2..f5c21846 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile.swift @@ -25,8 +25,6 @@ import Foundation import PassepartoutCore -import TunnelKitOpenVPN -import TunnelKitWireGuard public protocol ProfileSubtype { var vpnProtocols: [VPNProtocolType] { get } @@ -59,24 +57,6 @@ public struct Profile: Identifiable, Codable, Equatable { currentVPNProtocol = .openVPN } - init(_ id: UUID = UUID(), name: String, configuration: OpenVPN.Configuration) { - let header = Header( - uuid: id, - name: name, - providerName: nil - ) - self.init(header, configuration: configuration) - } - - init(_ id: UUID = UUID(), name: String, configuration: WireGuard.Configuration) { - let header = Header( - uuid: id, - name: name, - providerName: nil - ) - self.init(header, configuration: configuration) - } - init(_ id: UUID = UUID(), name: String, provider: Profile.Provider) { let header = Header( uuid: id, @@ -86,20 +66,6 @@ public struct Profile: Identifiable, Codable, Equatable { self.init(header, provider: provider) } - public init(_ header: Header, configuration: OpenVPN.Configuration) { - self.header = header - currentVPNProtocol = .openVPN - host = Host() - host?.ovpnSettings = OpenVPNSettings(configuration: configuration) - } - - public init(_ header: Header, configuration: WireGuard.Configuration) { - self.header = header - currentVPNProtocol = .wireGuard - host = Host() - host?.wgSettings = WireGuardSettings(configuration: configuration) - } - public init(_ header: Header, provider: Profile.Provider) { guard let firstVPNProtocol = provider.vpnSettings.keys.first else { fatalError("No VPN protocols defined in provider") diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfiguration.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfiguration.swift deleted file mode 100644 index 6f7215bb..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfiguration.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// VPNConfiguration.swift -// Passepartout -// -// Created by Davide De Rosa on 6/22/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import TunnelKitManager - -public typealias VPNConfiguration = (neConfiguration: NetworkExtensionConfiguration, neExtra: NetworkExtensionExtra) diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfigurationParameters.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfigurationParameters.swift index d7b94af1..bd39428b 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfigurationParameters.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfigurationParameters.swift @@ -25,11 +25,13 @@ import Foundation import PassepartoutCore -import TunnelKitManager +import PassepartoutProviders public struct VPNConfigurationParameters { public let profile: Profile + public let providerManager: ProviderManager + public var title: String { profile.header.name } @@ -52,12 +54,14 @@ public struct VPNConfigurationParameters { init( _ profile: Profile, + providerManager: ProviderManager, preferences: VPNPreferences, passwordReference: Data?, withNetworkSettings: Bool, withCustomRules: Bool ) { self.profile = profile + self.providerManager = providerManager self.preferences = preferences self.passwordReference = passwordReference self.withNetworkSettings = withNetworkSettings diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift index 4d454b1b..87e0573d 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift @@ -65,10 +65,9 @@ extension VPNManager { if let newPassword { profile.account.password = newPassword } - let cfg = try vpnConfiguration(withProfile: profile) profileManager.activateProfile(profile) - await reconnect(cfg) + await reconnect(profile) return profile } @@ -99,14 +98,13 @@ extension VPNManager { pp_log.info("Connecting to: \(profile.logDescription) @ \(newServer.logDescription)") profile.setProviderServer(newServer) - let cfg = try vpnConfiguration(withProfile: profile) profileManager.activateProfile(profile) guard !profileManager.isCurrentProfile(profileId) else { pp_log.debug("Active profile is current, will reconnect via observation") return profile } - await reconnect(cfg) + await reconnect(profile) return profile } @@ -118,13 +116,12 @@ extension VPNManager { pp_log.info("Modifying active profile") block(&profile) - let cfg = try vpnConfiguration(withProfile: profile) profileManager.activateProfile(profile) guard !profileManager.isCurrentProfile(profile.id) else { pp_log.debug("Active profile is current, will reinstate via observation") return } - await reinstate(cfg) + await reinstate(profile) } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift index 6559e21d..4754c0e8 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift @@ -84,16 +84,28 @@ public final class VPNManager: ObservableObject { currentState = ObservableVPNState() } - func reinstate(_ configuration: VPNConfiguration) async { + func reinstate(_ profile: Profile) async { pp_log.info("Reinstating VPN") clearLastError() - await strategy.reinstate(configuration: configuration) + do { + let parameters = try vpnConfigurationParameters(withProfile: profile) + await strategy.reinstate(parameters) + } catch { + pp_log.error("Unable to build configuration: \(error)") + configurationError.send((profile, error)) + } } - func reconnect(_ configuration: VPNConfiguration) async { + func reconnect(_ profile: Profile) async { pp_log.info("Reconnecting VPN (with new configuration)") clearLastError() - await strategy.connect(configuration: configuration) + do { + let parameters = try vpnConfigurationParameters(withProfile: profile) + await strategy.connect(parameters) + } catch { + pp_log.error("Unable to build configuration: \(error)") + configurationError.send((profile, error)) + } } public func reconnect() async { @@ -139,7 +151,11 @@ extension VPNManager { } private func observeStrategy() { - strategy.observe(into: MutableObservableVPNState(currentState)) + strategy.observe(into: MutableObservableVPNState(currentState)) { profile, error in + + // UI is certainly interested in configuration errors + self.configurationError.send((profile, error)) + } } private func observeProfileManager() { @@ -233,72 +249,51 @@ extension VPNManager { guard isHandled else { return } - guard let cfg = vpnConfigurationWithCurrentProfile() else { + guard profileManager.isActiveProfile(newProfile.id) else { + pp_log.info("Skipping VPN reaction, current profile is not active") return } if shouldReconnect { - await reconnect(cfg) + await reconnect(newProfile) } else { - await reinstate(cfg) + await reinstate(newProfile) } } } // MARK: Configuration -extension VPNManager { - func vpnConfigurationWithCurrentProfile() -> VPNConfiguration? { - do { - guard profileManager.isCurrentProfileActive() else { - pp_log.info("Skipping VPN configuration, current profile is not active") - return nil +private extension VPNManager { + func vpnConfigurationParameters(withProfile profile: Profile) throws -> VPNConfigurationParameters { + if profile.requiresCredentials { + guard !profile.account.isEmpty else { + throw PassepartoutError.missingAccount } - return try vpnConfiguration(withProfile: profileManager.currentProfile.value) - } catch { - return nil } - } - func vpnConfiguration(withProfile profile: Profile) throws -> VPNConfiguration { - do { - if profile.requiresCredentials { - guard !profile.account.isEmpty else { - throw PassepartoutError.missingAccount - } + // specific provider customizations + var newPassword: String? + if let providerName = profile.providerName { + switch providerName { + case .mullvad: + newPassword = "m" + + default: + break } - - // specific provider customizations - var newPassword: String? - if let providerName = profile.providerName { - switch providerName { - case .mullvad: - newPassword = "m" - - default: - break - } - } - - // IMPORTANT: must commit password to keychain (tunnel needs a password reference) - profileManager.savePassword(forProfile: profile, newPassword: newPassword) - - let parameters = VPNConfigurationParameters( - profile, - preferences: vpnPreferences, - passwordReference: profileManager.passwordReference(forProfile: profile), - withNetworkSettings: isNetworkSettingsSupported(), - withCustomRules: isOnDemandRulesSupported() - ) - - return try strategy.vpnConfiguration(parameters, providerManager: providerManager) - } catch { - pp_log.error("Unable to build VPNConfiguration: \(error)") - - // UI is certainly interested in configuration errors - configurationError.send((profile, error)) - - throw error } + + // IMPORTANT: must commit password to keychain (tunnel needs a password reference) + profileManager.savePassword(forProfile: profile, newPassword: newPassword) + + return VPNConfigurationParameters( + profile, + providerManager: providerManager, + preferences: vpnPreferences, + passwordReference: profileManager.passwordReference(forProfile: profile), + withNetworkSettings: isNetworkSettingsSupported(), + withCustomRules: isOnDemandRulesSupported() + ) } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift index 150c8898..4a04c1ac 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift @@ -29,11 +29,11 @@ import PassepartoutCore import PassepartoutProviders public protocol VPNManagerStrategy { - func observe(into state: MutableObservableVPNState) + func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void) - func reinstate(configuration: VPNConfiguration) async + func reinstate(_ parameters: VPNConfigurationParameters) async - func connect(configuration: VPNConfiguration) async + func connect(_ parameters: VPNConfigurationParameters) async func reconnect() async @@ -44,9 +44,4 @@ public protocol VPNManagerStrategy { func serverConfiguration(forProtocol vpnProtocol: VPNProtocolType) -> Any? func debugLogURL(forProtocol vpnProtocol: VPNProtocolType) -> URL? - - func vpnConfiguration( - _ parameters: VPNConfigurationParameters, - providerManager: ProviderManager - ) throws -> VPNConfiguration } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift index 59bc4923..8366ce3f 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift @@ -30,7 +30,7 @@ import TunnelKitManager import TunnelKitOpenVPN extension Profile.OpenVPNSettings: TunnelKitConfigurationProviding { - func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> VPNConfiguration { + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration { var customBuilder = configuration.builder() // tolerate widest range of certificates diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift index 4cfe3115..13914938 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift @@ -30,7 +30,7 @@ import TunnelKitManager import TunnelKitWireGuard extension Profile.WireGuardSettings: TunnelKitConfigurationProviding { - func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> VPNConfiguration { + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration { var customBuilder = configuration.builder() // network settings diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift index 5d7cd82c..9cc266e4 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift @@ -33,8 +33,10 @@ import TunnelKitCore import TunnelKitManager import TunnelKitOpenVPNCore +typealias TunnelKitVPNConfiguration = (neConfiguration: NetworkExtensionConfiguration, neExtra: NetworkExtensionExtra) + protocol TunnelKitConfigurationProviding { - func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> VPNConfiguration + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration } public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy where VPNType.Configuration == NetworkExtensionConfiguration, VPNType.Extra == NetworkExtensionExtra { @@ -49,8 +51,6 @@ public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy } } - private let reconnectionSeconds = 2 - private let appGroup: String private let tunnelBundleIdentifier: (VPNProtocolType) -> String @@ -59,12 +59,16 @@ public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy private let vpn: VPNType + private let reconnectionInterval: TimeInterval + private let dataCountInterval: TimeInterval // MARK: State private var currentState: MutableObservableVPNState? + private var onConfigurationError: ((Profile, Error) -> Void)? + private let vpnState = CurrentValueSubject(.init()) private var dataCountTimer: AnyCancellable? @@ -79,6 +83,7 @@ public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy appGroup: String, tunnelBundleIdentifier: @escaping (VPNProtocolType) -> String, vpn: VPNType, + reconnectionInterval: TimeInterval = 2.0, dataCountInterval: TimeInterval = 3.0 ) { self.appGroup = appGroup @@ -88,6 +93,7 @@ public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy } self.defaults = defaults self.vpn = vpn + self.reconnectionInterval = reconnectionInterval self.dataCountInterval = dataCountInterval registerNotification(withName: VPNNotification.didReinstall) { @@ -115,8 +121,9 @@ public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy // MARK: Actions extension TunnelKitVPNManagerStrategy { - public func observe(into state: MutableObservableVPNState) { + public func observe(into state: MutableObservableVPNState, onConfigurationError: @escaping (Profile, Error) -> Void) { currentState = state + self.onConfigurationError = onConfigurationError // use this to drop redundant NE notifications vpnState @@ -128,7 +135,10 @@ extension TunnelKitVPNManagerStrategy { }.store(in: &cancellables) } - public func reinstate(configuration: VPNConfiguration) async { + public func reinstate(_ parameters: VPNConfigurationParameters) async { + guard let configuration = try? vpnConfiguration(withParameters: parameters) else { + return + } guard let vpnType = configuration.neConfiguration as? VPNProtocolProviding else { fatalError("Configuration must implement VPNProtocolProviding") } @@ -148,7 +158,10 @@ extension TunnelKitVPNManagerStrategy { } } - public func connect(configuration: VPNConfiguration) async { + public func connect(_ parameters: VPNConfigurationParameters) async { + guard let configuration = try? vpnConfiguration(withParameters: parameters) else { + return + } guard let vpnType = configuration.neConfiguration as? VPNProtocolProviding else { fatalError("Configuration must implement VPNProtocolProviding") } @@ -162,7 +175,7 @@ extension TunnelKitVPNManagerStrategy { bundleIdentifier, configuration: configuration.neConfiguration, extra: configuration.neExtra, - after: .seconds(reconnectionSeconds) + after: reconnectionInterval.dispatchTimeInterval ) } catch { pp_log.error("Unable to connect: \(error)") @@ -171,7 +184,7 @@ extension TunnelKitVPNManagerStrategy { public func reconnect() async { try? await vpn.reconnect( - after: .seconds(reconnectionSeconds) + after: reconnectionInterval.dispatchTimeInterval ) } @@ -192,8 +205,8 @@ extension TunnelKitVPNManagerStrategy { // MARK: Notifications -extension TunnelKitVPNManagerStrategy { - private func onVPNReinstall(_ notification: Notification) { +private extension TunnelKitVPNManagerStrategy { + func onVPNReinstall(_ notification: Notification) { guard isRelevantNotification(notification) else { return } @@ -204,7 +217,7 @@ extension TunnelKitVPNManagerStrategy { )) } - private func onVPNStatus(_ notification: Notification) { + func onVPNStatus(_ notification: Notification) { // assume first notified identifier to be the relevant one // in order to restore VPN status on app launch @@ -239,7 +252,7 @@ extension TunnelKitVPNManagerStrategy { currentState?.lastError = error } - private func onVPNFail(_ notification: Notification) { + func onVPNFail(_ notification: Notification) { vpnState.send(AtomicState( isEnabled: notification.vpnIsEnabled, vpnStatus: vpnState.value.vpnStatus @@ -247,7 +260,7 @@ extension TunnelKitVPNManagerStrategy { currentState?.lastError = notification.vpnError } - private func isRelevantNotification(_ notification: Notification) -> Bool { + func isRelevantNotification(_ notification: Notification) -> Bool { guard let notificationTunnelIdentifier = notification.vpnBundleIdentifier else { return false } @@ -260,7 +273,7 @@ extension TunnelKitVPNManagerStrategy { // MARK: Data count - private func onDataCount(_: Date) { + func onDataCount(_: Date) { switch vpnState.value.vpnStatus { case .connected: guard let currentDataCount = currentDataCount else { @@ -273,7 +286,7 @@ extension TunnelKitVPNManagerStrategy { } } - private func startCountingData() { + func startCountingData() { guard dataCountTimer == nil else { return } @@ -284,7 +297,7 @@ extension TunnelKitVPNManagerStrategy { } } - private func stopCountingData() { + func stopCountingData() { dataCountTimer?.cancel() dataCountTimer = nil @@ -292,6 +305,45 @@ extension TunnelKitVPNManagerStrategy { } } +// MARK: Configuration + +private extension TunnelKitVPNManagerStrategy { + func vpnConfiguration(withParameters parameters: VPNConfigurationParameters) throws -> TunnelKitVPNConfiguration { + let profile = parameters.profile + do { + switch profile.currentVPNProtocol { + case .openVPN: + let settings: Profile.OpenVPNSettings + if profile.isProvider { + settings = try profile.providerOpenVPNSettings(withManager: parameters.providerManager) + } else { + guard let hostSettings = profile.hostOpenVPNSettings else { + fatalError("Profile currentVPNProtocol is OpenVPN, but host has no OpenVPN settings") + } + settings = hostSettings + } + return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) + + case .wireGuard: + let settings: Profile.WireGuardSettings + if profile.isProvider { + settings = try profile.providerWireGuardSettings(withManager: parameters.providerManager) + } else { + guard let hostSettings = profile.hostWireGuardSettings else { + fatalError("Profile currentVPNProtocol is WireGuard, but host has no WireGuard settings") + } + settings = hostSettings + } + return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) + } + } catch { + pp_log.error("Unable to build TunnelKitVPNConfiguration: \(error)") + onConfigurationError?(profile, error) + throw error + } + } +} + // MARK: Pulled extension TunnelKitVPNManagerStrategy { @@ -314,10 +366,12 @@ extension TunnelKitVPNManagerStrategy { return defaults.wireGuardURLForDebugLog(appGroup: appGroup) } } +} - // MARK: Callbacks +// MARK: Callbacks - private func lastError(withBundleIdentifier bundleIdentifier: String?) -> Error? { +private extension TunnelKitVPNManagerStrategy { + func lastError(withBundleIdentifier bundleIdentifier: String?) -> Error? { switch bundleIdentifier { case tunnelBundleIdentifier(.openVPN): return defaults.openVPNLastError @@ -330,7 +384,7 @@ extension TunnelKitVPNManagerStrategy { } } - private var currentDataCount: DataCount? { + var currentDataCount: DataCount? { switch currentBundleIdentifier { case tunnelBundleIdentifier(.openVPN): return defaults.openVPNDataCount @@ -340,36 +394,3 @@ extension TunnelKitVPNManagerStrategy { } } } - -// MARK: Configuration - -extension TunnelKitVPNManagerStrategy { - public func vpnConfiguration(_ parameters: VPNConfigurationParameters, providerManager: ProviderManager) throws -> VPNConfiguration { - let profile = parameters.profile - switch profile.currentVPNProtocol { - case .openVPN: - let settings: Profile.OpenVPNSettings - if profile.isProvider { - settings = try profile.providerOpenVPNSettings(withManager: providerManager) - } else { - guard let hostSettings = profile.hostOpenVPNSettings else { - fatalError("Profile currentVPNProtocol is OpenVPN, but host has no OpenVPN settings") - } - settings = hostSettings - } - return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) - - case .wireGuard: - let settings: Profile.WireGuardSettings - if profile.isProvider { - settings = try profile.providerWireGuardSettings(withManager: providerManager) - } else { - guard let hostSettings = profile.hostWireGuardSettings else { - fatalError("Profile currentVPNProtocol is WireGuard, but host has no WireGuard settings") - } - settings = hostSettings - } - return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) - } - } -} diff --git a/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift b/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift index 0a1df4d9..b59484e9 100644 --- a/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift +++ b/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift @@ -38,7 +38,6 @@ final class ProvidersTests: XCTestCase { private var cancellables: Set = [] override func setUp() { - let model = NSManagedObjectModel.mergedModel(from: [.module])! persistence = ProvidersPersistence(withName: "ProvidersTests", cloudKit: false, author: nil) let remoteStrategy = APIRemoteProvidersStrategy(