diff --git a/AppCheck.xcodeproj/project.pbxproj b/AppCheck.xcodeproj/project.pbxproj index d714e60..4019293 100644 --- a/AppCheck.xcodeproj/project.pbxproj +++ b/AppCheck.xcodeproj/project.pbxproj @@ -27,7 +27,6 @@ 54448A2E2486464F00771C96 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A2D2486464F00771C96 /* Array.swift */; }; 54448A30248647D900771C96 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A2F248647D900771C96 /* Time.swift */; }; 54448A3224899A4000771C96 /* SearchBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A3124899A4000771C96 /* SearchBarManager.swift */; }; - 544C95262407B1C700AB89D0 /* SharedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544C95252407B1C700AB89D0 /* SharedState.swift */; }; 544F912024A67EC5001D4B00 /* TVCOccurrenceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544F911F24A67EC5001D4B00 /* TVCOccurrenceContext.swift */; }; 5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */; }; 545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDCE243E6267003B6544 /* TutorialSheet.swift */; }; @@ -143,6 +142,7 @@ 54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F92482414800F7C34A /* SyncUpdate.swift */; }; 54E67E4624A8B0FE0025D261 /* PrefsShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4524A8B0FE0025D261 /* PrefsShared.swift */; }; 54E67E4924A8B1280025D261 /* Prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4824A8B1280025D261 /* Prefs.swift */; }; + 54E67E4B24A8C6370025D261 /* GlassVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4A24A8C6370025D261 /* GlassVPN.swift */; }; 54EFA4E82491A16A0022D618 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EFA4E72491A16A0022D618 /* Font.swift */; }; /* End PBXBuildFile section */ @@ -195,7 +195,6 @@ 54448A2D2486464F00771C96 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; 54448A2F248647D900771C96 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = ""; }; 54448A3124899A4000771C96 /* SearchBarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarManager.swift; sourceTree = ""; }; - 544C95252407B1C700AB89D0 /* SharedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedState.swift; sourceTree = ""; }; 544F911F24A67EC5001D4B00 /* TVCOccurrenceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCOccurrenceContext.swift; sourceTree = ""; }; 5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCRecordingDetails.swift; sourceTree = ""; }; 545DDDCE243E6267003B6544 /* TutorialSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialSheet.swift; sourceTree = ""; }; @@ -312,6 +311,7 @@ 54E540F92482414800F7C34A /* SyncUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpdate.swift; sourceTree = ""; }; 54E67E4524A8B0FE0025D261 /* PrefsShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsShared.swift; sourceTree = ""; }; 54E67E4824A8B1280025D261 /* Prefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prefs.swift; sourceTree = ""; }; + 54E67E4A24A8C6370025D261 /* GlassVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassVPN.swift; sourceTree = ""; }; 54EFA4E72491A16A0022D618 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -393,6 +393,7 @@ 545DDDD224436A03003B6544 /* Common Classes */, 548B1F9423D338EC005B047C /* main.entitlements */, 541AC5D72399498A00A769D7 /* AppDelegate.swift */, + 54E67E4A24A8C6370025D261 /* GlassVPN.swift */, 542E2A972404973F001462DC /* TBCMain.swift */, 540C6454240D5BAE00E948F9 /* Requests */, 540E677E242D2CD200871BBE /* Recordings */, @@ -469,7 +470,6 @@ 54B345A4241BB975004C53CC /* Extensions */ = { isa = PBXGroup; children = ( - 544C95252407B1C700AB89D0 /* SharedState.swift */, 54B345A8241BBA0B004C53CC /* Generic.swift */, 54B345A5241BB982004C53CC /* Notifications.swift */, 54B345AA241BBA5B004C53CC /* AlertSheet.swift */, @@ -857,9 +857,9 @@ 540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */, 54B345A6241BB982004C53CC /* Notifications.swift in Sources */, 54448A2E2486464F00771C96 /* Array.swift in Sources */, + 54E67E4B24A8C6370025D261 /* GlassVPN.swift in Sources */, 541FC47824A1453F009154D8 /* VCCoOccurrence.swift in Sources */, 54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */, - 544C95262407B1C700AB89D0 /* SharedState.swift in Sources */, 541DCA6124A6B0F6005F1A4B /* Color.swift in Sources */, 545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */, 54B345A9241BBA0B004C53CC /* Generic.swift in Sources */, diff --git a/main/AppDelegate.swift b/main/AppDelegate.swift index 6cdfa00..c41d970 100644 --- a/main/AppDelegate.swift +++ b/main/AppDelegate.swift @@ -1,13 +1,9 @@ import UIKit -import NetworkExtension - -let VPNConfigBundleIdentifier = "de.uni-bamberg.psi.AppCheck.VPN" @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - var managerVPN: NETunnelProviderManager? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if UserDefaults.standard.bool(forKey: "kill_db") { @@ -23,119 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { TestDataSource.load() #endif - loadVPN { mgr in - self.managerVPN = mgr - self.postVPNState() - } - NSNotification.Name.NEVPNStatusDidChange.observe(call: #selector(vpnStatusChanged(_:)), on: self) - NotifyDNSFilterChanged.observe(call: #selector(didChangeDomainFilter), on: self) - sync.start() return true } - - @objc private func vpnStatusChanged(_ notification: Notification) { - postRawVPNState((notification.object as? NETunnelProviderSession)?.status ?? .invalid) - } - - @objc private func didChangeDomainFilter() { - // Notify VPN extension about changes - if let session = self.managerVPN?.connection as? NETunnelProviderSession, - session.status == .connected { - try? session.sendProviderMessage("filter-update".data(using: .ascii)!, responseHandler: nil) - } - } - - func setProxyEnabled(_ newState: Bool) { - guard let mgr = self.managerVPN else { - self.createNewVPN { manager in - self.managerVPN = manager - self.setProxyEnabled(newState) - } - return - } - let state = mgr.isEnabled && (mgr.connection.status == .connected) - if state != newState { - self.updateVPN({ mgr.isEnabled = true }) { - newState ? try? mgr.connection.startVPNTunnel() : mgr.connection.stopVPNTunnel() - } - } - } - - // MARK: VPN - - private func createNewVPN(_ success: @escaping (_ manager: NETunnelProviderManager) -> Void) { - let mgr = NETunnelProviderManager() - mgr.localizedDescription = "AppCheck Monitor" - let proto = NETunnelProviderProtocol() - proto.providerBundleIdentifier = VPNConfigBundleIdentifier - proto.serverAddress = "127.0.0.1" - mgr.protocolConfiguration = proto - mgr.isEnabled = true - mgr.saveToPreferences { error in - guard error == nil else { - self.postProcessedVPNState(.off) - //ErrorAlert(error!).presentIn(self.window?.rootViewController) - return - } - success(mgr) - } - } - - private func loadVPN(_ finally: @escaping (_ manager: NETunnelProviderManager?) -> Void) { - NETunnelProviderManager.loadAllFromPreferences { managers, error in - guard let mgrs = managers, mgrs.count > 0 else { - finally(nil) - return - } - for mgr in mgrs { - if let proto = (mgr.protocolConfiguration as? NETunnelProviderProtocol) { - if proto.providerBundleIdentifier == VPNConfigBundleIdentifier { - finally(mgr) - return - } - } - } - finally(nil) - } - } - - private func updateVPN(_ body: @escaping () -> Void, _ onSuccess: @escaping () -> Void) { - self.managerVPN?.loadFromPreferences { error in - guard error == nil else { return } - body() - self.managerVPN?.saveToPreferences { error in - guard error == nil else { return } - onSuccess() - } - } - } - - private func postVPNState() { - guard let mgr = self.managerVPN else { - self.postRawVPNState(.invalid) - return - } - mgr.loadFromPreferences { _ in - self.postRawVPNState(mgr.connection.status) - } - } - - // MARK: Notifications - - private func postRawVPNState(_ origState: NEVPNStatus) { - let state: VPNState - switch origState { - case .connected: state = .on - case .connecting, .disconnecting, .reasserting: state = .inbetween - case .invalid, .disconnected: fallthrough - @unknown default: state = .off - } - postProcessedVPNState(state) - } - - private func postProcessedVPNState(_ state: VPNState) { - currentVPNState = state - NotifyVPNStateChanged.post(state) - } } diff --git a/main/Data Source/SyncUpdate.swift b/main/Data Source/SyncUpdate.swift index 74b58d1..40fe26e 100644 --- a/main/Data Source/SyncUpdate.swift +++ b/main/Data Source/SyncUpdate.swift @@ -1,5 +1,7 @@ import UIKit +let sync = SyncUpdate(periodic: 7) + class SyncUpdate { private var lastSync: TimeInterval = 0 private var timer: Timer! @@ -18,7 +20,7 @@ class SyncUpdate { private(set) var tsLatest: Timestamp? // as set per user, not actual latest - init(periodic interval: TimeInterval) { + fileprivate init(periodic interval: TimeInterval) { (filterType, tsEarliest, tsLatest) = Prefs.DateFilter.restrictions() reloadRangeFromDB() diff --git a/main/Extensions/SharedState.swift b/main/Extensions/SharedState.swift deleted file mode 100644 index d1a3334..0000000 --- a/main/Extensions/SharedState.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -var currentVPNState: VPNState = .off -let sync = SyncUpdate(periodic: 7) - -public enum VPNState : Int { - case on = 1, inbetween, off -} diff --git a/main/GlassVPN.swift b/main/GlassVPN.swift new file mode 100644 index 0000000..bd3648a --- /dev/null +++ b/main/GlassVPN.swift @@ -0,0 +1,111 @@ +import NetworkExtension + +let GlassVPN = GlassVPNManager() + +enum VPNState : Int { case on = 1, inbetween, off } + +final class GlassVPNManager { + static let bundleIdentifier = "de.uni-bamberg.psi.AppCheck.VPN" + private var managerVPN: NETunnelProviderManager? + private(set) var state: VPNState = .off + + fileprivate init() { + NETunnelProviderManager.loadAllFromPreferences { managers, error in + self.managerVPN = managers?.first { + ($0.protocolConfiguration as? NETunnelProviderProtocol)? + .providerBundleIdentifier == GlassVPNManager.bundleIdentifier + } + guard let mgr = self.managerVPN else { + self.postRawVPNState(.invalid) + return + } + mgr.loadFromPreferences { _ in + self.postRawVPNState(mgr.connection.status) + } + } + NSNotification.Name.NEVPNStatusDidChange.observe(call: #selector(vpnStatusChanged(_:)), on: self) + NotifyDNSFilterChanged.observe(call: #selector(didChangeDomainFilter), on: self) + } + + func setEnabled(_ newState: Bool) { + guard let mgr = self.managerVPN else { + self.createNewVPN { manager in + self.managerVPN = manager + self.setEnabled(newState) + } + return + } + let state = mgr.isEnabled && (mgr.connection.status == .connected) + if state != newState { + self.updateVPN({ mgr.isEnabled = true }) { + newState ? try? mgr.connection.startVPNTunnel() : mgr.connection.stopVPNTunnel() + } + } + } + + + // MARK: - Notify callback + + @objc private func vpnStatusChanged(_ notification: Notification) { + postRawVPNState((notification.object as? NETunnelProviderSession)?.status ?? .invalid) + } + + @objc private func didChangeDomainFilter() { + // Notify VPN extension about changes + if let session = self.managerVPN?.connection as? NETunnelProviderSession, + session.status == .connected { + try? session.sendProviderMessage("filter-update".data(using: .ascii)!, responseHandler: nil) + } + } + + + // MARK: - Manage configuration + + private func createNewVPN(_ success: @escaping (_ manager: NETunnelProviderManager) -> Void) { + let mgr = NETunnelProviderManager() + mgr.localizedDescription = "AppCheck Monitor" + let proto = NETunnelProviderProtocol() + proto.providerBundleIdentifier = GlassVPNManager.bundleIdentifier + proto.serverAddress = "127.0.0.1" + mgr.protocolConfiguration = proto + mgr.isEnabled = true + mgr.saveToPreferences { error in + guard error == nil else { + self.postProcessedVPNState(.off) + //ErrorAlert(error!).presentIn(self.window?.rootViewController) + return + } + success(mgr) + } + } + + private func updateVPN(_ body: @escaping () -> Void, _ onSuccess: @escaping () -> Void) { + self.managerVPN?.loadFromPreferences { error in + guard error == nil else { return } + body() + self.managerVPN?.saveToPreferences { error in + guard error == nil else { return } + onSuccess() + } + } + } + + + // MARK: - Post Notifications + + private func postRawVPNState(_ origState: NEVPNStatus) { + let state: VPNState + switch origState { + case .connected: state = .on + case .connecting, .disconnecting, .reasserting: state = .inbetween + case .invalid, .disconnected: fallthrough + @unknown default: state = .off + } + postProcessedVPNState(state) + } + + private func postProcessedVPNState(_ state: VPNState) { + self.state = state + NotifyVPNStateChanged.post(state) + } +} diff --git a/main/Settings/TVCSettings.swift b/main/Settings/TVCSettings.swift index 45e3a0a..3ddf6ab 100644 --- a/main/Settings/TVCSettings.swift +++ b/main/Settings/TVCSettings.swift @@ -10,32 +10,28 @@ class TVCSettings: UITableViewController { override func viewDidLoad() { super.viewDidLoad() - NotifyVPNStateChanged.observe(call: #selector(vpnStateChanged(_:)), on: self) - changedState(currentVPNState) - NotifyDNSFilterChanged.observe(call: #selector(reloadDataSource), on: self) + reloadToggleState() reloadDataSource() + NotifyVPNStateChanged.observe(call: #selector(reloadToggleState), on: self) + NotifyDNSFilterChanged.observe(call: #selector(reloadDataSource), on: self) } // MARK: - VPN Proxy Settings @IBAction private func toggleVPNProxy(_ sender: UISwitch) { - appDelegate.setProxyEnabled(sender.isOn) + GlassVPN.setEnabled(sender.isOn) } - @objc func vpnStateChanged(_ notification: Notification) { - changedState(notification.object as! VPNState) - } - - func changedState(_ newState: VPNState) { - vpnToggle.isOn = (newState != .off) - vpnToggle.onTintColor = (newState == .inbetween ? .systemYellow : nil) + @objc private func reloadToggleState() { + vpnToggle.isOn = (GlassVPN.state != .off) + vpnToggle.onTintColor = (GlassVPN.state == .inbetween ? .systemYellow : nil) } // MARK: - Logging Filter - @objc func reloadDataSource() { + @objc private func reloadDataSource() { let (blocked, ignored) = DomainFilter.counts() cellDomainsIgnored.detailTextLabel?.text = "\(ignored) Domains" cellDomainsBlocked.detailTextLabel?.text = "\(blocked) Domains" diff --git a/main/TBCMain.swift b/main/TBCMain.swift index d194daa..a5c9845 100644 --- a/main/TBCMain.swift +++ b/main/TBCMain.swift @@ -5,15 +5,31 @@ class TBCMain: UITabBarController { override func viewDidLoad() { super.viewDidLoad() - NotifyVPNStateChanged.observe(call: #selector(vpnStateChanged(_:)), on: self) - changedState(currentVPNState) + reloadTabBarBadge() + NotifyVPNStateChanged.observe(call: #selector(reloadTabBarBadge), on: self) if !Prefs.DidShowTutorial.Welcome { self.perform(#selector(showWelcomeMessage), with: nil, afterDelay: 0.5) } } - @objc func showWelcomeMessage() { + @objc private func reloadTabBarBadge() { + let stateView = self.tabBar.items?.last + switch GlassVPN.state { + case .on: stateView?.badgeValue = "✓" + case .inbetween: stateView?.badgeValue = "⋯" + case .off: stateView?.badgeValue = "✗" + } + if #available(iOS 10.0, *) { + switch GlassVPN.state { + case .on: stateView?.badgeColor = .systemGreen + case .inbetween: stateView?.badgeColor = .systemYellow + case .off: stateView?.badgeColor = .systemRed + } + } + } + + @objc private func showWelcomeMessage() { let x = TutorialSheet() x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString() .h1("Welcome\n") @@ -40,24 +56,4 @@ class TBCMain: UITabBarController { Prefs.DidShowTutorial.Welcome = true } } - - @objc func vpnStateChanged(_ notification: Notification) { - changedState(notification.object as! VPNState) - } - - func changedState(_ newState: VPNState) { - let stateView = self.tabBar.items?.last - switch newState { - case .on: stateView?.badgeValue = "✓" - case .inbetween: stateView?.badgeValue = "⋯" - case .off: stateView?.badgeValue = "✗" - } - if #available(iOS 10.0, *) { - switch newState { - case .on: stateView?.badgeColor = .systemGreen - case .inbetween: stateView?.badgeColor = .systemYellow - case .off: stateView?.badgeColor = .systemRed - } - } - } }