From f852b6f91931fa545968ee38c4b43e1233a6ac43 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Tue, 30 Apr 2019 15:38:38 +0530 Subject: [PATCH] iOS: Keep track of most-recently-activated tunnels Signed-off-by: Roopesh Chander --- WireGuard/WireGuard.xcodeproj/project.pbxproj | 4 ++ .../WireGuard/Tunnel/TunnelsManager.swift | 19 ++++- .../UI/iOS/RecentTunnelsTracker.swift | 72 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 WireGuard/WireGuard/UI/iOS/RecentTunnelsTracker.swift diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index ead1556..d499929 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */; }; 6F2449E8226587B90047B9E9 /* MacAppStoreUpdateDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */; }; 6F3E02E9228000F6001FE7E3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3E02E8228000F6001FE7E3 /* MainMenu.swift */; }; + 6F29A9432278518D00DC6A6B /* RecentTunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */; }; 6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; }; 6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; }; 6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; }; @@ -303,6 +304,7 @@ 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlertPresenter.swift; sourceTree = ""; }; 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAppStoreUpdateDetector.swift; sourceTree = ""; }; 6F3E02E8228000F6001FE7E3 /* MainMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; + 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentTunnelsTracker.swift; sourceTree = ""; }; 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = ""; }; 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = ""; }; 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = ""; }; @@ -568,6 +570,7 @@ 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */, 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */, 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */, + 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */, 6FF4AC23211EC472002C96EB /* Info.plist */, 6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */, ); @@ -1395,6 +1398,7 @@ 6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */, 6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */, 6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */, + 6F29A9432278518D00DC6A6B /* RecentTunnelsTracker.swift in Sources */, 6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */, 6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */, 5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */, diff --git a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift index edf7690..460a9f6 100644 --- a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift +++ b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift @@ -46,7 +46,11 @@ class TunnelsManager { var tunnelManagers = managers ?? [] var refs: Set = [] + var tunnelNames: Set = [] for (index, tunnelManager) in tunnelManagers.enumerated().reversed() { + if let tunnelName = tunnelManager.localizedDescription { + tunnelNames.insert(tunnelName) + } guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue } if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") { tunnelManager.saveToPreferences { _ in } @@ -66,6 +70,7 @@ class TunnelsManager { } } Keychain.deleteReferences(except: refs) + RecentTunnelsTracker.cleanupTunnels(except: tunnelNames) completionHandler(.success(TunnelsManager(tunnelProviders: tunnelManagers))) } #endif @@ -188,7 +193,8 @@ class TunnelsManager { } let tunnelProviderManager = tunnel.tunnelProvider - let isNameChanged = tunnelName != tunnelProviderManager.localizedDescription + let oldName = tunnelProviderManager.localizedDescription ?? "" + let isNameChanged = tunnelName != oldName if isNameChanged { guard !tunnels.contains(where: { $0.name == tunnelName }) else { completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName) @@ -220,6 +226,9 @@ class TunnelsManager { self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) } let newIndex = self.tunnels.firstIndex(of: tunnel)! self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex) + #if os(iOS) + RecentTunnelsTracker.handleTunnelRenamed(oldName: oldName, newName: tunnelName) + #endif } self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!) @@ -266,6 +275,10 @@ class TunnelsManager { self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel) } completionHandler(nil) + + #if os(iOS) + RecentTunnelsTracker.handleTunnelRemoved(tunnelName: tunnel.name) + #endif } } @@ -343,6 +356,10 @@ class TunnelsManager { #else tunnel.startActivation(activationDelegate: activationDelegate) #endif + + #if os(iOS) + RecentTunnelsTracker.handleTunnelActivated(tunnelName: tunnel.name) + #endif } func startDeactivation(of tunnel: TunnelContainer) { diff --git a/WireGuard/WireGuard/UI/iOS/RecentTunnelsTracker.swift b/WireGuard/WireGuard/UI/iOS/RecentTunnelsTracker.swift new file mode 100644 index 0000000..787a624 --- /dev/null +++ b/WireGuard/WireGuard/UI/iOS/RecentTunnelsTracker.swift @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import Foundation + +class RecentTunnelsTracker { + + private static let keyRecentlyActivatedTunnelNames = "recentlyActivatedTunnelNames" + private static let maxNumberOfTunnels = 10 + + private static var userDefaults: UserDefaults? { + guard let appGroupId = FileManager.appGroupId else { + wg_log(.error, staticMessage: "Cannot obtain app group ID from bundle for tracking recently used tunnels") + return nil + } + guard let userDefaults = UserDefaults(suiteName: appGroupId) else { + wg_log(.error, staticMessage: "Cannot obtain shared user defaults for tracking recently used tunnels") + return nil + } + return userDefaults + } + + static func handleTunnelActivated(tunnelName: String) { + guard let userDefaults = RecentTunnelsTracker.userDefaults else { return } + var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? [] + if let existingIndex = recentTunnels.firstIndex(of: tunnelName) { + recentTunnels.remove(at: existingIndex) + } + recentTunnels.insert(tunnelName, at: 0) + if recentTunnels.count > maxNumberOfTunnels { + recentTunnels.removeLast(recentTunnels.count - maxNumberOfTunnels) + } + userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames) + } + + static func handleTunnelRemoved(tunnelName: String) { + guard let userDefaults = RecentTunnelsTracker.userDefaults else { return } + var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? [] + if let existingIndex = recentTunnels.firstIndex(of: tunnelName) { + recentTunnels.remove(at: existingIndex) + userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames) + } + } + + static func handleTunnelRenamed(oldName: String, newName: String) { + guard let userDefaults = RecentTunnelsTracker.userDefaults else { return } + var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? [] + if let existingIndex = recentTunnels.firstIndex(of: oldName) { + recentTunnels[existingIndex] = newName + userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames) + } + } + + static func cleanupTunnels(except tunnelNamesToKeep: Set) { + guard let userDefaults = RecentTunnelsTracker.userDefaults else { return } + var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? [] + let oldCount = recentTunnels.count + recentTunnels.removeAll { !tunnelNamesToKeep.contains($0) } + if oldCount != recentTunnels.count { + userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames) + } + } + + static func recentlyActivatedTunnelNames(limit: Int) -> [String] { + guard let userDefaults = RecentTunnelsTracker.userDefaults else { return [] } + var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? [] + if limit < recentTunnels.count { + recentTunnels.removeLast(recentTunnels.count - limit) + } + return recentTunnels + } +}