From 965f66e5e2f260d470f9dffd1b9160aae1e988a1 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 12 Dec 2018 15:33:14 -0600 Subject: [PATCH] More linter warnings fixed, enabled more swiftlint rules, project cleanup --- WireGuard/.swiftlint.yml | 5 +- .../NETunnelProviderProtocol+Extension.swift | 2 +- WireGuard/WireGuard.xcodeproj/project.pbxproj | 4 + .../ConfigFile/WgQuickConfigFileParser.swift | 146 +++--- WireGuard/WireGuard/Crypto/Curve25519.swift | 6 +- WireGuard/WireGuard/UI/TunnelViewModel.swift | 6 +- .../WireGuard/UI/iOS/ErrorPresenter.swift | 4 +- .../WireGuard/UI/iOS/MainViewController.swift | 2 +- .../UI/iOS/QRScanViewController.swift | 14 +- .../WireGuard/UI/iOS/ScrollableLabel.swift | 2 +- .../UI/iOS/SettingsTableViewController.swift | 24 +- .../iOS/TunnelDetailTableViewController.swift | 245 ++++----- .../iOS/TunnelEditTableViewController.swift | 495 +++++++++--------- .../iOS/TunnelsListTableViewController.swift | 45 +- .../UI/iOS/UITableViewCell+Reuse.swift | 21 + .../WireGuard/VPN/InternetReachability.swift | 4 +- WireGuard/WireGuard/VPN/TunnelsManager.swift | 33 +- .../DNSResolver.swift | 41 +- .../ErrorNotifier.swift | 2 +- .../PacketTunnelProvider.swift | 12 +- .../PacketTunnelSettingsGenerator.swift | 2 +- 21 files changed, 583 insertions(+), 532 deletions(-) create mode 100644 WireGuard/WireGuard/UI/iOS/UITableViewCell+Reuse.swift diff --git a/WireGuard/.swiftlint.yml b/WireGuard/.swiftlint.yml index b16b6b3..df6266a 100644 --- a/WireGuard/.swiftlint.yml +++ b/WireGuard/.swiftlint.yml @@ -1,6 +1,9 @@ disabled_rules: - - force_cast - line_length + - trailing_whitespace +opt_in_rules: + - unneeded_parentheses_in_closure_argument +# - trailing_closure file_length: warning: 500 cyclomatic_complexity: diff --git a/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift b/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift index ec8b294..960bf22 100644 --- a/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift +++ b/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift @@ -17,7 +17,7 @@ extension NETunnelProviderProtocol { "tunnelConfigurationVersion": 1 ] - let endpoints = tunnelConfiguration.peers.compactMap({$0.endpoint}) + let endpoints = tunnelConfiguration.peers.compactMap {$0.endpoint} if endpoints.count == 1 { serverAddress = endpoints.first!.stringRepresentation() } else if endpoints.isEmpty { diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 79d606d..4177cbe 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */; }; 6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */; }; 6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0068562191AFD200419BE9 /* ScrollableLabel.swift */; }; 6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; }; @@ -91,6 +92,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Reuse.swift"; sourceTree = ""; }; 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyableLabelTableViewCell.swift; sourceTree = ""; }; 6F0068562191AFD200419BE9 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = ""; }; 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = ""; }; @@ -234,6 +236,7 @@ 6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */, 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */, 6F0068562191AFD200419BE9 /* ScrollableLabel.swift */, + 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */, ); path = iOS; sourceTree = ""; @@ -598,6 +601,7 @@ 6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */, 6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */, 6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */, + 5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */, 6FE254FF219C60290028284D /* ZipExporter.swift in Sources */, 6F693A562179E556008551C1 /* Endpoint.swift in Sources */, 6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */, diff --git a/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift b/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift index 515cd3e..138ca62 100644 --- a/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift +++ b/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift @@ -21,83 +21,16 @@ class WgQuickConfigFileParser { } static func parse(_ text: String, name: String) throws -> TunnelConfiguration { - assert(!name.isEmpty) - func collate(interfaceAttributes attributes: [String: String]) -> InterfaceConfiguration? { - // required wg fields - guard let privateKeyString = attributes["privatekey"] else { return nil } - guard let privateKey = Data(base64Encoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else { return nil } - var interface = InterfaceConfiguration(name: name, privateKey: privateKey) - // other wg fields - if let listenPortString = attributes["listenport"] { - guard let listenPort = UInt16(listenPortString) else { return nil } - interface.listenPort = listenPort - } - // wg-quick fields - if let addressesString = attributes["address"] { - var addresses: [IPAddressRange] = [] - for addressString in addressesString.split(separator: ",") { - let trimmedString = addressString.trimmingCharacters(in: .whitespaces) - guard let address = IPAddressRange(from: trimmedString) else { return nil } - addresses.append(address) - } - interface.addresses = addresses - } - if let dnsString = attributes["dns"] { - var dnsServers: [DNSServer] = [] - for dnsServerString in dnsString.split(separator: ",") { - let trimmedString = dnsServerString.trimmingCharacters(in: .whitespaces) - guard let dnsServer = DNSServer(from: trimmedString) else { return nil } - dnsServers.append(dnsServer) - } - interface.dns = dnsServers - } - if let mtuString = attributes["mtu"] { - guard let mtu = UInt16(mtuString) else { return nil } - interface.mtu = mtu - } - return interface - } - - func collate(peerAttributes attributes: [String: String]) -> PeerConfiguration? { - // required wg fields - guard let publicKeyString = attributes["publickey"] else { return nil } - guard let publicKey = Data(base64Encoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else { return nil } - var peer = PeerConfiguration(publicKey: publicKey) - // wg fields - if let preSharedKeyString = attributes["presharedkey"] { - guard let preSharedKey = Data(base64Encoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength else { return nil } - peer.preSharedKey = preSharedKey - } - if let allowedIPsString = attributes["allowedips"] { - var allowedIPs: [IPAddressRange] = [] - for allowedIPString in allowedIPsString.split(separator: ",") { - let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - guard let allowedIP = IPAddressRange(from: trimmedString) else { return nil } - allowedIPs.append(allowedIP) - } - peer.allowedIPs = allowedIPs - } - if let endpointString = attributes["endpoint"] { - guard let endpoint = Endpoint(from: endpointString) else { return nil } - peer.endpoint = endpoint - } - if let persistentKeepAliveString = attributes["persistentkeepalive"] { - guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { return nil } - peer.persistentKeepAlive = persistentKeepAlive - } - return peer - } - var interfaceConfiguration: InterfaceConfiguration? - var peerConfigurations: [PeerConfiguration] = [] + var peerConfigurations = [PeerConfiguration]() let lines = text.split(separator: "\n") - var parserState: ParserState = .notInASection - var attributes: [String: String] = [:] - + var parserState = ParserState.notInASection + var attributes = [String: String]() + for (lineIndex, line) in lines.enumerated() { var trimmedLine: String if let commentRange = line.range(of: "#") { @@ -127,12 +60,12 @@ class WgQuickConfigFileParser { } } - let isLastLine: Bool = (lineIndex == lines.count - 1) + let isLastLine = (lineIndex == lines.count - 1) if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" { // Previous section has ended; process the attributes collected so far if parserState == .inInterfaceSection { - guard let interface = collate(interfaceAttributes: attributes) else { throw ParseError.invalidInterface } + guard let interface = collate(interfaceAttributes: attributes, name: name) else { throw ParseError.invalidInterface } guard interfaceConfiguration == nil else { throw ParseError.multipleInterfaces } interfaceConfiguration = interface } else if parserState == .inPeerSection { @@ -163,4 +96,71 @@ class WgQuickConfigFileParser { throw ParseError.noInterface } } + + private static func collate(interfaceAttributes attributes: [String: String], name: String) -> InterfaceConfiguration? { + // required wg fields + guard let privateKeyString = attributes["privatekey"] else { return nil } + guard let privateKey = Data(base64Encoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else { return nil } + var interface = InterfaceConfiguration(name: name, privateKey: privateKey) + // other wg fields + if let listenPortString = attributes["listenport"] { + guard let listenPort = UInt16(listenPortString) else { return nil } + interface.listenPort = listenPort + } + // wg-quick fields + if let addressesString = attributes["address"] { + var addresses: [IPAddressRange] = [] + for addressString in addressesString.split(separator: ",") { + let trimmedString = addressString.trimmingCharacters(in: .whitespaces) + guard let address = IPAddressRange(from: trimmedString) else { return nil } + addresses.append(address) + } + interface.addresses = addresses + } + if let dnsString = attributes["dns"] { + var dnsServers: [DNSServer] = [] + for dnsServerString in dnsString.split(separator: ",") { + let trimmedString = dnsServerString.trimmingCharacters(in: .whitespaces) + guard let dnsServer = DNSServer(from: trimmedString) else { return nil } + dnsServers.append(dnsServer) + } + interface.dns = dnsServers + } + if let mtuString = attributes["mtu"] { + guard let mtu = UInt16(mtuString) else { return nil } + interface.mtu = mtu + } + return interface + } + + private static func collate(peerAttributes attributes: [String: String]) -> PeerConfiguration? { + // required wg fields + guard let publicKeyString = attributes["publickey"] else { return nil } + guard let publicKey = Data(base64Encoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else { return nil } + var peer = PeerConfiguration(publicKey: publicKey) + // wg fields + if let preSharedKeyString = attributes["presharedkey"] { + guard let preSharedKey = Data(base64Encoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength else { return nil } + peer.preSharedKey = preSharedKey + } + if let allowedIPsString = attributes["allowedips"] { + var allowedIPs: [IPAddressRange] = [] + for allowedIPString in allowedIPsString.split(separator: ",") { + let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + guard let allowedIP = IPAddressRange(from: trimmedString) else { return nil } + allowedIPs.append(allowedIP) + } + peer.allowedIPs = allowedIPs + } + if let endpointString = attributes["endpoint"] { + guard let endpoint = Endpoint(from: endpointString) else { return nil } + peer.endpoint = endpoint + } + if let persistentKeepAliveString = attributes["persistentkeepalive"] { + guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { return nil } + peer.persistentKeepAlive = persistentKeepAlive + } + return peer + } + } diff --git a/WireGuard/WireGuard/Crypto/Curve25519.swift b/WireGuard/WireGuard/Crypto/Curve25519.swift index 43d9b00..53404cc 100644 --- a/WireGuard/WireGuard/Crypto/Curve25519.swift +++ b/WireGuard/WireGuard/Crypto/Curve25519.swift @@ -9,7 +9,7 @@ struct Curve25519 { static func generatePrivateKey() -> Data { var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength) - privateKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) in + privateKey.withUnsafeMutableBytes { bytes in curve25519_generate_private_key(bytes) } assert(privateKey.count == TunnelConfiguration.keyLength) @@ -19,8 +19,8 @@ struct Curve25519 { static func generatePublicKey(fromPrivateKey privateKey: Data) -> Data { assert(privateKey.count == TunnelConfiguration.keyLength) var publicKey = Data(repeating: 0, count: TunnelConfiguration.keyLength) - privateKey.withUnsafeBytes { (privateKeyBytes: UnsafePointer) in - publicKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) in + privateKey.withUnsafeBytes { privateKeyBytes in + publicKey.withUnsafeMutableBytes { bytes in curve25519_derive_public_key(bytes, privateKeyBytes) } } diff --git a/WireGuard/WireGuard/UI/TunnelViewModel.swift b/WireGuard/WireGuard/UI/TunnelViewModel.swift index 47a13a1..af7ff4c 100644 --- a/WireGuard/WireGuard/UI/TunnelViewModel.swift +++ b/WireGuard/WireGuard/UI/TunnelViewModel.swift @@ -165,7 +165,7 @@ class TunnelViewModel { } func filterFieldsWithValueOrControl(interfaceFields: [InterfaceField]) -> [InterfaceField] { - return interfaceFields.filter { (field) -> Bool in + return interfaceFields.filter { field in if TunnelViewModel.interfaceFieldsWithControl.contains(field) { return true } @@ -291,13 +291,13 @@ class TunnelViewModel { } guard errorMessages.isEmpty else { return .error(errorMessages.first!) } - + validatedConfiguration = config return .saved(config) } func filterFieldsWithValueOrControl(peerFields: [PeerField]) -> [PeerField] { - return peerFields.filter { (field) -> Bool in + return peerFields.filter { field in if TunnelViewModel.peerFieldsWithControl.contains(field) { return true } diff --git a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift index 6cae1e6..7c28495 100644 --- a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift +++ b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift @@ -9,7 +9,7 @@ class ErrorPresenter { onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) { guard let sourceVC = sourceVC else { return } guard let (title, message) = error.alertText() else { return } - let okAction = UIAlertAction(title: "OK", style: .default) { (_) in + let okAction = UIAlertAction(title: "OK", style: .default) { _ in onDismissal?() } let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) @@ -21,7 +21,7 @@ class ErrorPresenter { static func showErrorAlert(title: String, message: String, from sourceVC: UIViewController?, onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) { guard let sourceVC = sourceVC else { return } - let okAction = UIAlertAction(title: "OK", style: .default) { (_) in + let okAction = UIAlertAction(title: "OK", style: .default) { _ in onDismissal?() } let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) diff --git a/WireGuard/WireGuard/UI/iOS/MainViewController.swift b/WireGuard/WireGuard/UI/iOS/MainViewController.swift index 70d838e..6822263 100644 --- a/WireGuard/WireGuard/UI/iOS/MainViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/MainViewController.swift @@ -75,7 +75,7 @@ extension MainViewController { } func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool) { - let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] (tunnelsManager) in + let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in if let tunnel = tunnelsManager.tunnel(named: tunnelName) { let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel) let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC) diff --git a/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift b/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift index 3849f70..ad0fe79 100644 --- a/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift @@ -33,7 +33,7 @@ class QRScanViewController: UIViewController { tipLabel.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), tipLabel.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), tipLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32) - ]) + ]) guard let videoCaptureDevice = AVCaptureDevice.default(for: .video), let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), @@ -114,10 +114,10 @@ class QRScanViewController: UIViewController { let alert = UIAlertController(title: NSLocalizedString("Please name the scanned tunnel", comment: ""), message: nil, preferredStyle: .alert) alert.addTextField(configurationHandler: nil) - alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: { [weak self] _ in + alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { [weak self] _ in self?.dismiss(animated: true, completion: nil) - })) - alert.addAction(UIAlertAction(title: NSLocalizedString("Save", comment: ""), style: .default, handler: { [weak self] _ in + }) + alert.addAction(UIAlertAction(title: NSLocalizedString("Save", comment: ""), style: .default) { [weak self] _ in guard let title = alert.textFields?[0].text?.trimmingCharacters(in: .whitespacesAndNewlines), !title.isEmpty else { return } tunnelConfiguration.interface.name = title if let self = self { @@ -125,15 +125,15 @@ class QRScanViewController: UIViewController { self.dismiss(animated: true, completion: nil) } } - })) + }) present(alert, animated: true) } func scanDidEncounterError(title: String, message: String) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self] _ in + alertController.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in self?.dismiss(animated: true, completion: nil) - })) + }) present(alertController, animated: true) captureSession = nil } diff --git a/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift b/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift index c0cc86b..f2d0f58 100644 --- a/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift +++ b/WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift @@ -35,7 +35,7 @@ class ScrollableLabel: UIScrollView { label.bottomAnchor.constraint(equalTo: self.contentLayoutGuide.bottomAnchor), label.rightAnchor.constraint(equalTo: self.contentLayoutGuide.rightAnchor), label.heightAnchor.constraint(equalTo: self.heightAnchor) - ]) + ]) // If label has less content, it should expand to fit the scrollView, // so that right-alignment works in the label. let expandToFitValueLabelConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, diff --git a/WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift b/WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift index 3107579..8f248c9 100644 --- a/WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift @@ -40,8 +40,8 @@ class SettingsTableViewController: UITableViewController { self.tableView.rowHeight = UITableView.automaticDimension self.tableView.allowsSelection = false - self.tableView.register(TunnelSettingsTableViewKeyValueCell.self, forCellReuseIdentifier: TunnelSettingsTableViewKeyValueCell.reuseIdentifier) - self.tableView.register(TunnelSettingsTableViewButtonCell.self, forCellReuseIdentifier: TunnelSettingsTableViewButtonCell.reuseIdentifier) + self.tableView.register(KeyValueCell.self) + self.tableView.register(ButtonCell.self) let logo = UIImageView(image: UIImage(named: "wireguard.pdf", in: Bundle.main, compatibleWith: nil)!) logo.contentMode = .scaleAspectFit @@ -76,7 +76,7 @@ class SettingsTableViewController: UITableViewController { let count = tunnelsManager.numberOfTunnels() let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration() } - ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] (error) in + ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in if let error = error { ErrorPresenter.showErrorAlert(error: error, from: self) return @@ -127,7 +127,7 @@ class SettingsTableViewController: UITableViewController { // popoverPresentationController shall be non-nil on the iPad activityVC.popoverPresentationController?.sourceView = sourceView activityVC.popoverPresentationController?.sourceRect = sourceView.bounds - activityVC.completionWithItemsHandler = { (_, _, _, _) in + activityVC.completionWithItemsHandler = { _, _, _, _ in // Remove the exported log file after the activity has completed _ = FileManager.deleteFile(at: destinationURL) } @@ -164,7 +164,7 @@ extension SettingsTableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let field = settingsFieldsBySection[indexPath.section][indexPath.row] if field == .iosAppVersion || field == .goBackendVersion { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelSettingsTableViewKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelSettingsTableViewKeyValueCell + let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath) cell.key = field.rawValue if field == .iosAppVersion { var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version" @@ -177,7 +177,7 @@ extension SettingsTableViewController { } return cell } else if field == .exportZipArchive { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelSettingsTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelSettingsTableViewButtonCell + let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath) cell.buttonText = field.rawValue cell.onTapped = { [weak self] in self?.exportConfigurationsAsZipFile(sourceView: cell.button) @@ -185,7 +185,7 @@ extension SettingsTableViewController { return cell } else { assert(field == .exportLogFile) - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelSettingsTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelSettingsTableViewButtonCell + let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath) cell.buttonText = field.rawValue cell.onTapped = { [weak self] in self?.exportLogForLastActivatedTunnel(sourceView: cell.button) @@ -195,8 +195,7 @@ extension SettingsTableViewController { } } -class TunnelSettingsTableViewKeyValueCell: UITableViewCell { - static let reuseIdentifier = "TunnelSettingsTableViewKeyValueCell" +private class KeyValueCell: UITableViewCell { var key: String { get { return textLabel?.text ?? "" } set(value) { textLabel?.text = value } @@ -207,7 +206,7 @@ class TunnelSettingsTableViewKeyValueCell: UITableViewCell { } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: .value1, reuseIdentifier: TunnelSettingsTableViewKeyValueCell.reuseIdentifier) + super.init(style: .value1, reuseIdentifier: KeyValueCell.reuseIdentifier) } required init?(coder aDecoder: NSCoder) { @@ -221,8 +220,7 @@ class TunnelSettingsTableViewKeyValueCell: UITableViewCell { } } -class TunnelSettingsTableViewButtonCell: UITableViewCell { - static let reuseIdentifier = "TunnelSettingsTableViewButtonCell" +private class ButtonCell: UITableViewCell { var buttonText: String { get { return button.title(for: .normal) ?? "" } set(value) { button.setTitle(value, for: .normal) } @@ -242,7 +240,7 @@ class TunnelSettingsTableViewButtonCell: UITableViewCell { button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor), button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor) - ]) + ]) button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift index 2ab3c26..e6ad024 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift @@ -7,6 +7,14 @@ import UIKit class TunnelDetailTableViewController: UITableViewController { + private enum Section { + case status + case interface + case peer(_ peer: TunnelViewModel.PeerData) + case onDemand + case delete + } + let interfaceFields: [TunnelViewModel.InterfaceField] = [ .name, .publicKey, .addresses, .listenPort, .mtu, .dns @@ -20,12 +28,14 @@ class TunnelDetailTableViewController: UITableViewController { let tunnelsManager: TunnelsManager let tunnel: TunnelContainer var tunnelViewModel: TunnelViewModel + private var sections = [Section]() init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) { self.tunnelsManager = tunnelsManager self.tunnel = tunnel tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration()) super.init(style: .grouped) + loadSections() } required init?(coder aDecoder: NSCoder) { @@ -40,15 +50,24 @@ class TunnelDetailTableViewController: UITableViewController { self.tableView.estimatedRowHeight = 44 self.tableView.rowHeight = UITableView.automaticDimension self.tableView.allowsSelection = false - self.tableView.register(TunnelDetailTableViewStatusCell.self, forCellReuseIdentifier: TunnelDetailTableViewStatusCell.reuseIdentifier) - self.tableView.register(TunnelDetailTableViewKeyValueCell.self, forCellReuseIdentifier: TunnelDetailTableViewKeyValueCell.reuseIdentifier) - self.tableView.register(TunnelDetailTableViewButtonCell.self, forCellReuseIdentifier: TunnelDetailTableViewButtonCell.reuseIdentifier) - self.tableView.register(TunnelDetailTableViewActivateOnDemandCell.self, forCellReuseIdentifier: TunnelDetailTableViewActivateOnDemandCell.reuseIdentifier) + self.tableView.register(StatusCell.self) + self.tableView.register(KeyValueCell.self) + self.tableView.register(ButtonCell.self) + self.tableView.register(ActivateOnDemandCell.self) // State restoration self.restorationIdentifier = "TunnelDetailVC:\(tunnel.name)" } + private func loadSections() { + sections.removeAll() + sections.append(.status) + sections.append(.interface) + tunnelViewModel.peersData.forEach { sections.append(.peer($0)) } + sections.append(.onDemand) + sections.append(.delete) + } + @objc func editTapped() { let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel) editVC.delegate = self @@ -59,7 +78,7 @@ class TunnelDetailTableViewController: UITableViewController { func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) { - let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { (_) in + let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in onConfirmed() } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) @@ -80,6 +99,7 @@ class TunnelDetailTableViewController: UITableViewController { extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate { func tunnelSaved(tunnel: TunnelContainer) { tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration()) + loadSections() self.title = tunnel.name self.tableView.reloadData() } @@ -92,136 +112,125 @@ extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate extension TunnelDetailTableViewController { override func numberOfSections(in tableView: UITableView) -> Int { - return 4 + tunnelViewModel.peersData.count + return sections.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let interfaceData = tunnelViewModel.interfaceData - let numberOfPeerSections = tunnelViewModel.peersData.count - - if section == 0 { - // Status + switch sections[section] { + case .status: return 1 - } else if section == 1 { - // Interface - return interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields).count - } else if (numberOfPeerSections > 0) && (section < (2 + numberOfPeerSections)) { - // Peer - let peerData = tunnelViewModel.peersData[section - 2] + case .interface: + return tunnelViewModel.interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields).count + case .peer(let peerData): return peerData.filterFieldsWithValueOrControl(peerFields: peerFields).count - } else if section < (3 + numberOfPeerSections) { - // Activate on demand + case .onDemand: return 1 - } else { - assert(section == (3 + numberOfPeerSections)) - // Delete tunnel + case .delete: return 1 } } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - let numberOfPeerSections = tunnelViewModel.peersData.count - - if section == 0 { - // Status + switch sections[section] { + case .status: return "Status" - } else if section == 1 { - // Interface + case .interface: return "Interface" - } else if (numberOfPeerSections > 0) && (section < (2 + numberOfPeerSections)) { - // Peer + case .peer: return "Peer" - } else if section < (3 + numberOfPeerSections) { - // On-Demand Activation + case .onDemand: return "On-Demand Activation" - } else { - // Delete tunnel + case .delete: return nil } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let interfaceData = tunnelViewModel.interfaceData - let numberOfPeerSections = tunnelViewModel.peersData.count - - let section = indexPath.section - let row = indexPath.row - - if section == 0 { - // Status - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewStatusCell.reuseIdentifier, for: indexPath) as! TunnelDetailTableViewStatusCell - cell.tunnel = self.tunnel - cell.onSwitchToggled = { [weak self] isOn in - guard let self = self else { return } - if isOn { - self.tunnelsManager.startActivation(of: self.tunnel) { [weak self] error in - if let error = error { - ErrorPresenter.showErrorAlert(error: error, from: self, onPresented: { - DispatchQueue.main.async { - cell.statusSwitch.isOn = false - } - }) - } - } - } else { - self.tunnelsManager.startDeactivation(of: self.tunnel) - } - } - return cell - } else if section == 1 { - // Interface - let field = interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields)[row] - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelDetailTableViewKeyValueCell - // Set key and value - cell.key = field.rawValue - cell.value = interfaceData[field] - return cell - } else if (numberOfPeerSections > 0) && (section < (2 + numberOfPeerSections)) { - // Peer - let peerData = tunnelViewModel.peersData[section - 2] - let field = peerData.filterFieldsWithValueOrControl(peerFields: peerFields)[row] - - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelDetailTableViewKeyValueCell - // Set key and value - cell.key = field.rawValue - cell.value = peerData[field] - return cell - } else if section < (3 + numberOfPeerSections) { - // On-Demand Activation - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewActivateOnDemandCell.reuseIdentifier, for: indexPath) as! TunnelDetailTableViewActivateOnDemandCell - cell.tunnel = self.tunnel - return cell - } else { - assert(section == (3 + numberOfPeerSections)) - // Delete configuration - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelDetailTableViewButtonCell - cell.buttonText = "Delete tunnel" - cell.hasDestructiveAction = true - cell.onTapped = { [weak self] in - guard let self = self else { return } - self.showConfirmationAlert(message: "Delete this tunnel?", buttonTitle: "Delete", from: cell) { [weak self] in - guard let tunnelsManager = self?.tunnelsManager, let tunnel = self?.tunnel else { return } - tunnelsManager.remove(tunnel: tunnel) { (error) in - if error != nil { - print("Error removing tunnel: \(String(describing: error))") - return - } - } - self?.navigationController?.navigationController?.popToRootViewController(animated: true) - } - } - return cell + switch sections[indexPath.section] { + case .status: + return statusCell(for: tableView, at: indexPath) + case .interface: + return interfaceCell(for: tableView, at: indexPath) + case .peer(let peer): + return peerCell(for: tableView, at: indexPath, with: peer) + case .onDemand: + return onDemandCell(for: tableView, at: indexPath) + case .delete: + return deleteConfigurationCell(for: tableView, at: indexPath) } } + + private func statusCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { + let cell: StatusCell = tableView.dequeueReusableCell(for: indexPath) + cell.tunnel = self.tunnel + cell.onSwitchToggled = { [weak self] isOn in + guard let self = self else { return } + if isOn { + self.tunnelsManager.startActivation(of: self.tunnel) { [weak self] error in + if let error = error { + ErrorPresenter.showErrorAlert(error: error, from: self) { + DispatchQueue.main.async { + cell.statusSwitch.isOn = false + } + } + } + } + } else { + self.tunnelsManager.startDeactivation(of: self.tunnel) + } + } + return cell + } + + private func interfaceCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { + let field = tunnelViewModel.interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields)[indexPath.row] + let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath) + cell.key = field.rawValue + cell.value = tunnelViewModel.interfaceData[field] + return cell + } + + private func peerCell(for tableView: UITableView, at indexPath: IndexPath, with peerData: TunnelViewModel.PeerData) -> UITableViewCell { + let field = peerData.filterFieldsWithValueOrControl(peerFields: peerFields)[indexPath.row] + let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath) + cell.key = field.rawValue + cell.value = peerData[field] + return cell + } + + private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { + let cell: ActivateOnDemandCell = tableView.dequeueReusableCell(for: indexPath) + cell.tunnel = self.tunnel + return cell + } + + private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { + let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath) + cell.buttonText = "Delete tunnel" + cell.hasDestructiveAction = true + cell.onTapped = { [weak self] in + guard let self = self else { return } + self.showConfirmationAlert(message: "Delete this tunnel?", buttonTitle: "Delete", from: cell) { [weak self] in + guard let tunnelsManager = self?.tunnelsManager, let tunnel = self?.tunnel else { return } + tunnelsManager.remove(tunnel: tunnel) { error in + if error != nil { + print("Error removing tunnel: \(String(describing: error))") + return + } + } + self?.navigationController?.navigationController?.popToRootViewController(animated: true) + } + } + return cell + } + } -class TunnelDetailTableViewStatusCell: UITableViewCell { - static let reuseIdentifier = "TunnelDetailTableViewStatusCell" - +private class StatusCell: UITableViewCell { var tunnel: TunnelContainer? { didSet(value) { update(from: tunnel?.status) - statusObservervationToken = tunnel?.observe(\.status) { [weak self] (tunnel, _) in + statusObservervationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in self?.update(from: tunnel.status) } } @@ -238,7 +247,7 @@ class TunnelDetailTableViewStatusCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { statusSwitch = UISwitch() - super.init(style: .default, reuseIdentifier: TunnelDetailTableViewKeyValueCell.reuseIdentifier) + super.init(style: .default, reuseIdentifier: KeyValueCell.reuseIdentifier) accessoryView = statusSwitch statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged) @@ -296,8 +305,7 @@ class TunnelDetailTableViewStatusCell: UITableViewCell { } } -class TunnelDetailTableViewKeyValueCell: CopyableLabelTableViewCell { - static let reuseIdentifier = "TunnelDetailTableViewKeyValueCell" +private class KeyValueCell: CopyableLabelTableViewCell { var key: String { get { return keyLabel.text ?? "" } set(value) { keyLabel.text = value } @@ -337,14 +345,14 @@ class TunnelDetailTableViewKeyValueCell: CopyableLabelTableViewCell { NSLayoutConstraint.activate([ keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor), keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5) - ]) + ]) contentView.addSubview(valueLabel) valueLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor), contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueLabel.bottomAnchor, multiplier: 0.5) - ]) + ]) // Key label should never appear truncated keyLabel.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal) @@ -399,8 +407,7 @@ class TunnelDetailTableViewKeyValueCell: CopyableLabelTableViewCell { } } -class TunnelDetailTableViewButtonCell: UITableViewCell { - static let reuseIdentifier = "TunnelDetailTableViewButtonCell" +private class ButtonCell: UITableViewCell { var buttonText: String { get { return button.title(for: .normal) ?? "" } set(value) { button.setTitle(value, for: .normal) } @@ -426,7 +433,7 @@ class TunnelDetailTableViewButtonCell: UITableViewCell { button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor), button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor) - ]) + ]) button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } @@ -446,13 +453,11 @@ class TunnelDetailTableViewButtonCell: UITableViewCell { } } -class TunnelDetailTableViewActivateOnDemandCell: UITableViewCell { - static let reuseIdentifier = "TunnelDetailTableViewActivateOnDemandCell" - +private class ActivateOnDemandCell: UITableViewCell { var tunnel: TunnelContainer? { didSet(value) { update(from: tunnel?.activateOnDemandSetting()) - onDemandStatusObservervationToken = tunnel?.observe(\.isActivateOnDemandEnabled) { [weak self] (tunnel, _) in + onDemandStatusObservervationToken = tunnel?.observe(\.isActivateOnDemandEnabled) { [weak self] tunnel, _ in self?.update(from: tunnel.activateOnDemandSetting()) } } diff --git a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift index 330a3cc..b26992d 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift @@ -12,6 +12,13 @@ protocol TunnelEditTableViewControllerDelegate: class { class TunnelEditTableViewController: UITableViewController { + private enum Section { + case interface + case peer(_ peer: TunnelViewModel.PeerData) + case addPeer + case onDemand + } + weak var delegate: TunnelEditTableViewControllerDelegate? let interfaceFieldsBySection: [[TunnelViewModel.InterfaceField]] = [ @@ -36,9 +43,7 @@ class TunnelEditTableViewController: UITableViewController { let tunnel: TunnelContainer? let tunnelViewModel: TunnelViewModel var activateOnDemandSetting: ActivateOnDemandSetting - - private var interfaceSectionCount: Int { return interfaceFieldsBySection.count } - private var peerSectionCount: Int { return tunnelViewModel.peersData.count } + private var sections = [Section]() init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) { // Use this initializer to edit an existing tunnel. @@ -47,6 +52,7 @@ class TunnelEditTableViewController: UITableViewController { tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration()) activateOnDemandSetting = tunnel.activateOnDemandSetting() super.init(style: .grouped) + loadSections() } init(tunnelsManager: TunnelsManager, tunnelConfiguration: TunnelConfiguration?) { @@ -57,6 +63,7 @@ class TunnelEditTableViewController: UITableViewController { tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration) activateOnDemandSetting = ActivateOnDemandSetting.defaultSetting super.init(style: .grouped) + loadSections() } required init?(coder aDecoder: NSCoder) { @@ -72,11 +79,19 @@ class TunnelEditTableViewController: UITableViewController { self.tableView.estimatedRowHeight = 44 self.tableView.rowHeight = UITableView.automaticDimension - self.tableView.register(TunnelEditTableViewKeyValueCell.self, forCellReuseIdentifier: TunnelEditTableViewKeyValueCell.reuseIdentifier) - self.tableView.register(TunnelEditTableViewReadOnlyKeyValueCell.self, forCellReuseIdentifier: TunnelEditTableViewReadOnlyKeyValueCell.reuseIdentifier) - self.tableView.register(TunnelEditTableViewButtonCell.self, forCellReuseIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier) - self.tableView.register(TunnelEditTableViewSwitchCell.self, forCellReuseIdentifier: TunnelEditTableViewSwitchCell.reuseIdentifier) - self.tableView.register(TunnelEditTableViewSelectionListCell.self, forCellReuseIdentifier: TunnelEditTableViewSelectionListCell.reuseIdentifier) + self.tableView.register(KeyValueCell.self) + self.tableView.register(ReadOnlyKeyValueCell.self) + self.tableView.register(ButtonCell.self) + self.tableView.register(SwitchCell.self) + self.tableView.register(SelectionListCell.self) + } + + private func loadSections() { + sections.removeAll() + interfaceFieldsBySection.forEach { _ in sections.append(.interface) } + tunnelViewModel.peersData.forEach { sections.append(.peer($0)) } + sections.append(.addPeer) + sections.append(.onDemand) } @objc func saveTapped() { @@ -92,7 +107,7 @@ class TunnelEditTableViewController: UITableViewController { // We're modifying an existing tunnel tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, - activateOnDemandSetting: activateOnDemandSetting) { [weak self] (error) in + activateOnDemandSetting: activateOnDemandSetting) { [weak self] error in if let error = error { ErrorPresenter.showErrorAlert(error: error, from: self) } else { @@ -126,24 +141,19 @@ class TunnelEditTableViewController: UITableViewController { extension TunnelEditTableViewController { override func numberOfSections(in tableView: UITableView) -> Int { - return interfaceSectionCount + peerSectionCount + 1 /* Add Peer */ + 1 /* On-Demand */ + return sections.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section < interfaceSectionCount { - // Interface + switch sections[section] { + case .interface: return interfaceFieldsBySection[section].count - } else if (peerSectionCount > 0) && (section < (interfaceSectionCount + peerSectionCount)) { - // Peer - let peerIndex = (section - interfaceSectionCount) - let peerData = tunnelViewModel.peersData[peerIndex] + case .peer(let peerData): let peerFieldsToShow = peerData.shouldAllowExcludePrivateIPsControl ? peerFields : peerFields.filter { $0 != .excludePrivateIPs } return peerFieldsToShow.count - } else if section < (interfaceSectionCount + peerSectionCount + 1) { - // Add peer + case .addPeer: return 1 - } else { - // On-Demand Rules + case .onDemand: if activateOnDemandSetting.isActivateOnDemandEnabled { return 4 } else { @@ -153,222 +163,239 @@ extension TunnelEditTableViewController { } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - if section < interfaceSectionCount { - // Interface - return (section == 0) ? "Interface" : nil - } else if (peerSectionCount > 0) && (section < (interfaceSectionCount + peerSectionCount)) { - // Peer + switch sections[section] { + case .interface: + return section == 0 ? "Interface" : nil + case .peer: return "Peer" - } else if section == (interfaceSectionCount + peerSectionCount) { - // Add peer + case .addPeer: return nil - } else { - assert(section == (interfaceSectionCount + peerSectionCount + 1)) + case .onDemand: return "On-Demand Activation" } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section < interfaceSectionCount { + switch sections[indexPath.section] { + case .interface: return interfaceFieldCell(for: tableView, at: indexPath) - } else if (peerSectionCount > 0) && (indexPath.section < (interfaceSectionCount + peerSectionCount)) { - return peerCell(for: tableView, at: indexPath) - } else if indexPath.section == (interfaceSectionCount + peerSectionCount) { + case .peer(let peerData): + return peerCell(for: tableView, at: indexPath, with: peerData) + case .addPeer: return addPeerCell(for: tableView, at: indexPath) - } else { + case .onDemand: return onDemandCell(for: tableView, at: indexPath) } } private func interfaceFieldCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { - let interfaceData = tunnelViewModel.interfaceData let field = interfaceFieldsBySection[indexPath.section][indexPath.row] - if field == .generateKeyPair { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewButtonCell - cell.buttonText = field.rawValue - cell.onTapped = { [weak self, weak interfaceData] in - if let interfaceData = interfaceData, let self = self { - interfaceData[.privateKey] = Curve25519.generatePrivateKey().base64EncodedString() - if let privateKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .privateKey), - let publicKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .publicKey) { - let privateKeyIndex = IndexPath(row: privateKeyRow, section: indexPath.section) - let publicKeyIndex = IndexPath(row: publicKeyRow, section: indexPath.section) - self.tableView.reloadRows(at: [privateKeyIndex, publicKeyIndex], with: .automatic) - } - } - } - return cell - } else if field == .publicKey { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewReadOnlyKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewReadOnlyKeyValueCell - cell.key = field.rawValue - cell.value = interfaceData[field] - return cell - } else { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewKeyValueCell - // Set key - cell.key = field.rawValue - // Set placeholder text - switch field { - case .name: - cell.placeholderText = "Required" - case .privateKey: - cell.placeholderText = "Required" - case .addresses: - cell.placeholderText = "Optional" - case .listenPort: - cell.placeholderText = "Automatic" - case .mtu: - cell.placeholderText = "Automatic" - case .dns: - cell.placeholderText = "Optional" - case .publicKey: break - case .generateKeyPair: break - } - // Set keyboardType - if field == .mtu || field == .listenPort { - cell.keyboardType = .numberPad - } else if field == .addresses || field == .dns { - cell.keyboardType = .numbersAndPunctuation - } - // Show erroring fields - cell.isValueValid = (!interfaceData.fieldsWithError.contains(field)) - // Bind values to view model - cell.value = interfaceData[field] - if field == .dns { // While editing DNS, you might directly set exclude private IPs - cell.onValueBeingEdited = { [weak interfaceData] value in - interfaceData?[field] = value - } - } else { - cell.onValueChanged = { [weak interfaceData] value in - interfaceData?[field] = value - } - } - // Compute public key live - if field == .privateKey { - cell.onValueBeingEdited = { [weak self, weak interfaceData] value in - if let interfaceData = interfaceData, let self = self { - interfaceData[.privateKey] = value - if let row = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .publicKey) { - self.tableView.reloadRows(at: [IndexPath(row: row, section: indexPath.section)], with: .none) - } - } - } - } - return cell + switch field { + case .generateKeyPair: + return generateKeyPairCell(for: tableView, at: indexPath, with: field) + case .publicKey: + return publicKeyCell(for: tableView, at: indexPath, with: field) + default: + return interfaceFieldKeyValueCell(for: tableView, at: indexPath, with: field) } } - - private func peerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { - let peerIndex = indexPath.section - interfaceFieldsBySection.count - let peerData = tunnelViewModel.peersData[peerIndex] - let peerFieldsToShow = peerData.shouldAllowExcludePrivateIPsControl ? peerFields : peerFields.filter { $0 != .excludePrivateIPs } - let field = peerFieldsToShow[indexPath.row] - if field == .deletePeer { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewButtonCell - cell.buttonText = field.rawValue - cell.hasDestructiveAction = true - cell.onTapped = { [weak self, weak peerData] in - guard let peerData = peerData else { return } - guard let self = self else { return } - self.showConfirmationAlert(message: "Delete this peer?", buttonTitle: "Delete", from: cell) { [weak self] in - guard let self = self else { return } - let removedSectionIndices = self.deletePeer(peer: peerData) - let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl) - tableView.performBatchUpdates({ - self.tableView.deleteSections(removedSectionIndices, with: .automatic) - if shouldShowExcludePrivateIPs { - if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) { - let rowIndexPath = IndexPath(row: row, section: self.interfaceFieldsBySection.count /* First peer section */) - self.tableView.insertRows(at: [rowIndexPath], with: .automatic) - } - - } - }) - } + + private func generateKeyPairCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell { + let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath) + cell.buttonText = field.rawValue + cell.onTapped = { [weak self] in + guard let self = self else { return } + + self.tunnelViewModel.interfaceData[.privateKey] = Curve25519.generatePrivateKey().base64EncodedString() + if let privateKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .privateKey), + let publicKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .publicKey) { + let privateKeyIndex = IndexPath(row: privateKeyRow, section: indexPath.section) + let publicKeyIndex = IndexPath(row: publicKeyRow, section: indexPath.section) + self.tableView.reloadRows(at: [privateKeyIndex, publicKeyIndex], with: .fade) } - return cell - } else if field == .excludePrivateIPs { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSwitchCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewSwitchCell - cell.message = field.rawValue - cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl - cell.isOn = peerData.excludePrivateIPsValue - cell.onSwitchToggled = { [weak self] (isOn) in + } + return cell + } + + private func publicKeyCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell { + let cell: ReadOnlyKeyValueCell = tableView.dequeueReusableCell(for: indexPath) + cell.key = field.rawValue + cell.value = tunnelViewModel.interfaceData[field] + return cell + } + + private func interfaceFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell { + let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath) + cell.key = field.rawValue + + switch field { + case .name, .privateKey: + cell.placeholderText = "Required" + cell.keyboardType = .default + case .addresses, .dns: + cell.placeholderText = "Optional" + cell.keyboardType = .numbersAndPunctuation + case .listenPort, .mtu: + cell.placeholderText = "Automatic" + cell.keyboardType = .numberPad + case .publicKey, .generateKeyPair: + cell.keyboardType = .default + } + + cell.isValueValid = (!tunnelViewModel.interfaceData.fieldsWithError.contains(field)) + // Bind values to view model + cell.value = tunnelViewModel.interfaceData[field] + if field == .dns { // While editing DNS, you might directly set exclude private IPs + cell.onValueChanged = nil + cell.onValueBeingEdited = { [weak self] value in + self?.tunnelViewModel.interfaceData[field] = value + } + } else { + cell.onValueChanged = { [weak self] value in + self?.tunnelViewModel.interfaceData[field] = value + } + cell.onValueBeingEdited = nil + } + // Compute public key live + if field == .privateKey { + cell.onValueBeingEdited = { [weak self] value in guard let self = self else { return } - peerData.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: self.tunnelViewModel.interfaceData[.dns]) - if let row = self.peerFields.firstIndex(of: .allowedIPs) { + + self.tunnelViewModel.interfaceData[.privateKey] = value + if let row = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .publicKey) { self.tableView.reloadRows(at: [IndexPath(row: row, section: indexPath.section)], with: .none) } } - return cell } else { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewKeyValueCell - // Set key - cell.key = field.rawValue - // Set placeholder text - switch field { - case .publicKey: - cell.placeholderText = "Required" - case .preSharedKey: - cell.placeholderText = "Optional" - case .endpoint: - cell.placeholderText = "Optional" - case .allowedIPs: - cell.placeholderText = "Optional" - case .persistentKeepAlive: - cell.placeholderText = "Off" - case .excludePrivateIPs: break - case .deletePeer: break + cell.onValueBeingEdited = nil + } + return cell + } + + private func peerCell(for tableView: UITableView, at indexPath: IndexPath, with peerData: TunnelViewModel.PeerData) -> UITableViewCell { + let peerFieldsToShow = peerData.shouldAllowExcludePrivateIPsControl ? peerFields : peerFields.filter { $0 != .excludePrivateIPs } + let field = peerFieldsToShow[indexPath.row] + + switch field { + case .deletePeer: + return deletePeerCell(for: tableView, at: indexPath, peerData: peerData, field: field) + case .excludePrivateIPs: + return excludePrivateIPsCell(for: tableView, at: indexPath, peerData: peerData, field: field) + default: + return peerFieldKeyValueCell(for: tableView, at: indexPath, peerData: peerData, field: field) + } + } + + private func deletePeerCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell { + let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath) + cell.buttonText = field.rawValue + cell.hasDestructiveAction = true + cell.onTapped = { [weak self, weak peerData] in + guard let peerData = peerData else { return } + guard let self = self else { return } + self.showConfirmationAlert(message: "Delete this peer?", buttonTitle: "Delete", from: cell) { [weak self] in + guard let self = self else { return } + let removedSectionIndices = self.deletePeer(peer: peerData) + let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl) + tableView.performBatchUpdates({ + self.tableView.deleteSections(removedSectionIndices, with: .fade) + if shouldShowExcludePrivateIPs { + if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) { + let rowIndexPath = IndexPath(row: row, section: self.interfaceFieldsBySection.count /* First peer section */) + self.tableView.insertRows(at: [rowIndexPath], with: .fade) + } + + } + }) } - // Set keyboardType - if field == .persistentKeepAlive { - cell.keyboardType = .numberPad - } else if field == .allowedIPs { - cell.keyboardType = .numbersAndPunctuation + } + return cell + } + + private func excludePrivateIPsCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell { + let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath) + cell.message = field.rawValue + cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl + cell.isOn = peerData.excludePrivateIPsValue + cell.onSwitchToggled = { [weak self] isOn in + guard let self = self else { return } + peerData.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: self.tunnelViewModel.interfaceData[.dns]) + if let row = self.peerFields.firstIndex(of: .allowedIPs) { + self.tableView.reloadRows(at: [IndexPath(row: row, section: indexPath.section)], with: .none) } - // Show erroring fields - cell.isValueValid = (!peerData.fieldsWithError.contains(field)) - // Bind values to view model - cell.value = peerData[field] - if field != .allowedIPs { - cell.onValueChanged = { [weak peerData] value in - peerData?[field] = value - } + } + return cell + } + + private func peerFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell { + let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath) + cell.key = field.rawValue + + switch field { + case .publicKey: + cell.placeholderText = "Required" + case .preSharedKey, .endpoint, .allowedIPs: + cell.placeholderText = "Optional" + case .persistentKeepAlive: + cell.placeholderText = "Off" + case .excludePrivateIPs, .deletePeer: + break + } + + switch field { + case .persistentKeepAlive: + cell.keyboardType = .numberPad + case .allowedIPs: + cell.keyboardType = .numbersAndPunctuation + default: + cell.keyboardType = .default + } + + // Show erroring fields + cell.isValueValid = (!peerData.fieldsWithError.contains(field)) + // Bind values to view model + cell.value = peerData[field] + if field != .allowedIPs { + cell.onValueChanged = { [weak peerData] value in + peerData?[field] = value } - // Compute state of exclude private IPs live - if field == .allowedIPs { - cell.onValueBeingEdited = { [weak self, weak peerData] value in - if let peerData = peerData, let self = self { - let oldValue = peerData.shouldAllowExcludePrivateIPsControl - peerData[.allowedIPs] = value - if oldValue != peerData.shouldAllowExcludePrivateIPsControl { - if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) { - if peerData.shouldAllowExcludePrivateIPsControl { - self.tableView.insertRows(at: [IndexPath(row: row, section: indexPath.section)], with: .automatic) - } else { - self.tableView.deleteRows(at: [IndexPath(row: row, section: indexPath.section)], with: .automatic) - } + } + // Compute state of exclude private IPs live + if field == .allowedIPs { + cell.onValueBeingEdited = { [weak self, weak peerData] value in + if let peerData = peerData, let self = self { + let oldValue = peerData.shouldAllowExcludePrivateIPsControl + peerData[.allowedIPs] = value + if oldValue != peerData.shouldAllowExcludePrivateIPsControl { + if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) { + if peerData.shouldAllowExcludePrivateIPsControl { + self.tableView.insertRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade) + } else { + self.tableView.deleteRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade) } } } } } - return cell + } else { + cell.onValueBeingEdited = nil } + return cell } private func addPeerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewButtonCell + let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath) cell.buttonText = "Add peer" cell.onTapped = { [weak self] in guard let self = self else { return } let shouldHideExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl) let addedSectionIndices = self.appendEmptyPeer() tableView.performBatchUpdates({ - tableView.insertSections(addedSectionIndices, with: .automatic) + tableView.insertSections(addedSectionIndices, with: .fade) if shouldHideExcludePrivateIPs { if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) { let rowIndexPath = IndexPath(row: row, section: self.interfaceFieldsBySection.count /* First peer section */) - self.tableView.deleteRows(at: [rowIndexPath], with: .automatic) + self.tableView.deleteRows(at: [rowIndexPath], with: .fade) } } }, completion: nil) @@ -377,12 +404,11 @@ extension TunnelEditTableViewController { } private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { - assert(indexPath.section == interfaceSectionCount + peerSectionCount + 1) if indexPath.row == 0 { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSwitchCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewSwitchCell + let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath) cell.message = "Activate on demand" cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled - cell.onSwitchToggled = { [weak self] (isOn) in + cell.onSwitchToggled = { [weak self] isOn in guard let self = self else { return } let indexPaths: [IndexPath] = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) } if isOn { @@ -390,16 +416,17 @@ extension TunnelEditTableViewController { if self.activateOnDemandSetting.activateOnDemandOption == .none { self.activateOnDemandSetting.activateOnDemandOption = TunnelViewModel.defaultActivateOnDemandOption() } - self.tableView.insertRows(at: indexPaths, with: .automatic) + self.loadSections() + self.tableView.insertRows(at: indexPaths, with: .fade) } else { self.activateOnDemandSetting.isActivateOnDemandEnabled = false - self.tableView.deleteRows(at: indexPaths, with: .automatic) + self.loadSections() + self.tableView.deleteRows(at: indexPaths, with: .fade) } } return cell } else { - assert(indexPath.row < 4) - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSelectionListCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewSelectionListCell + let cell: SelectionListCell = tableView.dequeueReusableCell(for: indexPath) let rowOption = activateOnDemandOptions[indexPath.row - 1] let selectedOption = activateOnDemandSetting.activateOnDemandOption assert(selectedOption != .none) @@ -411,22 +438,19 @@ extension TunnelEditTableViewController { func appendEmptyPeer() -> IndexSet { tunnelViewModel.appendEmptyPeer() + loadSections() let addedPeerIndex = tunnelViewModel.peersData.count - 1 - - let addedSectionIndices = IndexSet(integer: interfaceSectionCount + addedPeerIndex) - return addedSectionIndices + return IndexSet(integer: interfaceFieldsBySection.count + addedPeerIndex) } func deletePeer(peer: TunnelViewModel.PeerData) -> IndexSet { - assert(peer.index < tunnelViewModel.peersData.count) tunnelViewModel.deletePeer(peer: peer) - - let removedSectionIndices = IndexSet(integer: (interfaceSectionCount + peer.index)) - return removedSectionIndices + loadSections() + return IndexSet(integer: interfaceFieldsBySection.count + peer.index) } func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) { - let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { (_) in + let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in onConfirmed() } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) @@ -446,31 +470,31 @@ extension TunnelEditTableViewController { extension TunnelEditTableViewController { override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { - if indexPath.section == (interfaceSectionCount + peerSectionCount + 1) { - return (indexPath.row > 0) ? indexPath : nil + if case .onDemand = sections[indexPath.section], indexPath.row > 0 { + return indexPath } else { return nil } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let section = indexPath.section - let row = indexPath.row - - assert(section == (interfaceSectionCount + peerSectionCount + 1)) - assert(row > 0) - - let option = activateOnDemandOptions[row - 1] - assert(option != .none) - activateOnDemandSetting.activateOnDemandOption = option - - let indexPaths: [IndexPath] = (1 ..< 4).map { IndexPath(row: $0, section: section) } - tableView.reloadRows(at: indexPaths, with: .automatic) + switch sections[indexPath.section] { + case .onDemand: + let option = activateOnDemandOptions[indexPath.row - 1] + assert(option != .none) + activateOnDemandSetting.activateOnDemandOption = option + + let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) } + UIView.performWithoutAnimation { + tableView.reloadRows(at: indexPaths, with: .none) + } + default: + assertionFailure() + } } } -class TunnelEditTableViewKeyValueCell: UITableViewCell { - static let reuseIdentifier = "TunnelEditTableViewKeyValueCell" +private class KeyValueCell: UITableViewCell { var key: String { get { return keyLabel.text ?? "" } set(value) {keyLabel.text = value } @@ -532,13 +556,13 @@ class TunnelEditTableViewKeyValueCell: UITableViewCell { keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor), keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5), widthRatioConstraint - ]) + ]) contentView.addSubview(valueTextField) valueTextField.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ valueTextField.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor), contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 0.5) - ]) + ]) valueTextField.delegate = self valueTextField.autocapitalizationType = .none @@ -597,7 +621,7 @@ class TunnelEditTableViewKeyValueCell: UITableViewCell { } } -extension TunnelEditTableViewKeyValueCell: UITextFieldDelegate { +extension KeyValueCell: UITextFieldDelegate { func textFieldDidBeginEditing(_ textField: UITextField) { textFieldValueOnBeginEditing = textField.text ?? "" isValueValid = true @@ -618,8 +642,7 @@ extension TunnelEditTableViewKeyValueCell: UITextFieldDelegate { } } -class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell { - static let reuseIdentifier = "TunnelEditTableViewReadOnlyKeyValueCell" +private class ReadOnlyKeyValueCell: CopyableLabelTableViewCell { var key: String { get { return keyLabel.text ?? "" } set(value) {keyLabel.text = value } @@ -660,7 +683,7 @@ class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell { keyLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor), widthRatioConstraint - ]) + ]) contentView.addSubview(valueLabel) valueLabel.translatesAutoresizingMaskIntoConstraints = false @@ -668,7 +691,7 @@ class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell { valueLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1), valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor) - ]) + ]) } override var textToCopy: String? { @@ -686,8 +709,7 @@ class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell { } } -class TunnelEditTableViewButtonCell: UITableViewCell { - static let reuseIdentifier = "TunnelEditTableViewButtonCell" +private class ButtonCell: UITableViewCell { var buttonText: String { get { return button.title(for: .normal) ?? "" } set(value) { button.setTitle(value, for: .normal) } @@ -713,7 +735,7 @@ class TunnelEditTableViewButtonCell: UITableViewCell { button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor), button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor) - ]) + ]) button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } @@ -733,8 +755,7 @@ class TunnelEditTableViewButtonCell: UITableViewCell { } } -class TunnelEditTableViewSwitchCell: UITableViewCell { - static let reuseIdentifier = "TunnelEditTableViewSwitchCell" +private class SwitchCell: UITableViewCell { var message: String { get { return textLabel?.text ?? "" } set(value) { textLabel!.text = value } @@ -759,7 +780,6 @@ class TunnelEditTableViewSwitchCell: UITableViewCell { switchView = UISwitch() super.init(style: .default, reuseIdentifier: reuseIdentifier) accessoryView = switchView - switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged) } @@ -778,8 +798,7 @@ class TunnelEditTableViewSwitchCell: UITableViewCell { } } -class TunnelEditTableViewSelectionListCell: UITableViewCell { - static let reuseIdentifier = "TunnelEditTableViewSelectionListCell" +private class SelectionListCell: UITableViewCell { var message: String { get { return textLabel?.text ?? "" } set(value) { textLabel!.text = value } diff --git a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift index bd5f972..e5d5af1 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift @@ -34,7 +34,7 @@ class TunnelsListTableViewController: UIViewController { NSLayoutConstraint.activate([ busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) - ]) + ]) busyIndicator.startAnimating() self.busyIndicator = busyIndicator @@ -54,7 +54,7 @@ class TunnelsListTableViewController: UIViewController { tableView.estimatedRowHeight = 60 tableView.rowHeight = UITableView.automaticDimension tableView.separatorStyle = .none - tableView.register(TunnelsListTableViewCell.self, forCellReuseIdentifier: TunnelsListTableViewCell.reuseIdentifier) + tableView.register(TunnelCell.self) self.view.addSubview(tableView) tableView.translatesAutoresizingMaskIntoConstraints = false @@ -63,7 +63,7 @@ class TunnelsListTableViewController: UIViewController { tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor), tableView.topAnchor.constraint(equalTo: self.view.topAnchor), tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) - ]) + ]) tableView.dataSource = self tableView.delegate = self self.tableView = tableView @@ -78,7 +78,7 @@ class TunnelsListTableViewController: UIViewController { NSLayoutConstraint.activate([ centeredAddButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), centeredAddButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor) - ]) + ]) centeredAddButton.onTapped = { [weak self] in self?.addButtonTapped(sender: centeredAddButton) } @@ -105,17 +105,17 @@ class TunnelsListTableViewController: UIViewController { @objc func addButtonTapped(sender: AnyObject) { if self.tunnelsManager == nil { return } // Do nothing until we've loaded the tunnels let alert = UIAlertController(title: "", message: "Add a new WireGuard tunnel", preferredStyle: .actionSheet) - let importFileAction = UIAlertAction(title: "Create from file or archive", style: .default) { [weak self] (_) in + let importFileAction = UIAlertAction(title: "Create from file or archive", style: .default) { [weak self] _ in self?.presentViewControllerForFileImport() } alert.addAction(importFileAction) - let scanQRCodeAction = UIAlertAction(title: "Create from QR code", style: .default) { [weak self] (_) in + let scanQRCodeAction = UIAlertAction(title: "Create from QR code", style: .default) { [weak self] _ in self?.presentViewControllerForScanningQRCode() } alert.addAction(scanQRCodeAction) - let createFromScratchAction = UIAlertAction(title: "Create from scratch", style: .default) { [weak self] (_) in + let createFromScratchAction = UIAlertAction(title: "Create from scratch", style: .default) { [weak self] _ in if let self = self, let tunnelsManager = self.tunnelsManager { self.presentViewControllerForTunnelCreation(tunnelsManager: tunnelsManager, tunnelConfiguration: nil) } @@ -174,7 +174,7 @@ class TunnelsListTableViewController: UIViewController { return } let configs: [TunnelConfiguration?] = result.value! - tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { [weak self] (numberSuccessful) in + tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { [weak self] numberSuccessful in if numberSuccessful == configs.count { completionHandler?() return @@ -241,7 +241,7 @@ extension TunnelsListTableViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: TunnelsListTableViewCell.reuseIdentifier, for: indexPath) as! TunnelsListTableViewCell + let cell: TunnelCell = tableView.dequeueReusableCell(for: indexPath) if let tunnelsManager = tunnelsManager { let tunnel = tunnelsManager.tunnel(at: indexPath.row) cell.tunnel = tunnel @@ -250,11 +250,11 @@ extension TunnelsListTableViewController: UITableViewDataSource { if isOn { tunnelsManager.startActivation(of: tunnel) { [weak self] error in if let error = error { - ErrorPresenter.showErrorAlert(error: error, from: self, onPresented: { + ErrorPresenter.showErrorAlert(error: error, from: self) { DispatchQueue.main.async { cell.statusSwitch.isOn = false } - }) + } } } } else { @@ -281,18 +281,18 @@ extension TunnelsListTableViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let deleteAction = UIContextualAction(style: .destructive, title: "Delete", handler: { [weak self] (_, _, completionHandler) in + let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { [weak self] _, _, completionHandler in guard let tunnelsManager = self?.tunnelsManager else { return } let tunnel = tunnelsManager.tunnel(at: indexPath.row) - tunnelsManager.remove(tunnel: tunnel, completionHandler: { (error) in + tunnelsManager.remove(tunnel: tunnel) { error in if error != nil { ErrorPresenter.showErrorAlert(error: error!, from: self) completionHandler(false) } else { completionHandler(true) } - }) - }) + } + } return UISwipeActionsConfiguration(actions: [deleteAction]) } } @@ -319,18 +319,17 @@ extension TunnelsListTableViewController: TunnelsManagerListDelegate { } } -class TunnelsListTableViewCell: UITableViewCell { - static let reuseIdentifier = "TunnelsListTableViewCell" +private class TunnelCell: UITableViewCell { var tunnel: TunnelContainer? { didSet(value) { // Bind to the tunnel's name nameLabel.text = tunnel?.name ?? "" - nameObservervationToken = tunnel?.observe(\.name) { [weak self] (tunnel, _) in + nameObservervationToken = tunnel?.observe(\.name) { [weak self] tunnel, _ in self?.nameLabel.text = tunnel.name } // Bind to the tunnel's status update(from: tunnel?.status) - statusObservervationToken = tunnel?.observe(\.status) { [weak self] (tunnel, _) in + statusObservervationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in self?.update(from: tunnel.status) } } @@ -357,13 +356,13 @@ class TunnelsListTableViewCell: UITableViewCell { NSLayoutConstraint.activate([ statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), contentView.rightAnchor.constraint(equalTo: statusSwitch.rightAnchor) - ]) + ]) contentView.addSubview(busyIndicator) busyIndicator.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), statusSwitch.leftAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.rightAnchor, multiplier: 1) - ]) + ]) contentView.addSubview(nameLabel) nameLabel.translatesAutoresizingMaskIntoConstraints = false nameLabel.numberOfLines = 0 @@ -376,7 +375,7 @@ class TunnelsListTableViewCell: UITableViewCell { nameLabel.leftAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leftAnchor, multiplier: 1), busyIndicator.leftAnchor.constraint(equalToSystemSpacingAfter: nameLabel.rightAnchor, multiplier: 1), bottomAnchorConstraint - ]) + ]) self.accessoryType = .disclosureIndicator @@ -445,7 +444,7 @@ class BorderedTextButton: UIView { NSLayoutConstraint.activate([ button.centerXAnchor.constraint(equalTo: self.centerXAnchor), button.centerYAnchor.constraint(equalTo: self.centerYAnchor) - ]) + ]) layer.borderWidth = 1 layer.cornerRadius = 5 layer.borderColor = button.tintColor.cgColor diff --git a/WireGuard/WireGuard/UI/iOS/UITableViewCell+Reuse.swift b/WireGuard/WireGuard/UI/iOS/UITableViewCell+Reuse.swift new file mode 100644 index 0000000..587f7b1 --- /dev/null +++ b/WireGuard/WireGuard/UI/iOS/UITableViewCell+Reuse.swift @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import UIKit + +extension UITableViewCell { + static var reuseIdentifier: String { + return NSStringFromClass(self) + } +} + +extension UITableView { + func register(_: T.Type) { + register(T.self, forCellReuseIdentifier: T.reuseIdentifier) + } + + func dequeueReusableCell(for indexPath: IndexPath) -> T { + //swiftlint:disable:next force_cast + return dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T + } +} diff --git a/WireGuard/WireGuard/VPN/InternetReachability.swift b/WireGuard/WireGuard/VPN/InternetReachability.swift index 831050e..2e50852 100644 --- a/WireGuard/WireGuard/VPN/InternetReachability.swift +++ b/WireGuard/WireGuard/VPN/InternetReachability.swift @@ -28,8 +28,8 @@ class InternetReachability { sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) - return withUnsafePointer(to: addrIn) { (addrInPtr) -> SCNetworkReachability? in - addrInPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { (addrPtr) -> SCNetworkReachability? in + return withUnsafePointer(to: addrIn) { addrInPtr in + addrInPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { addrPtr in return SCNetworkReachabilityCreateWithAddress(nil, addrPtr) } } diff --git a/WireGuard/WireGuard/VPN/TunnelsManager.swift b/WireGuard/WireGuard/VPN/TunnelsManager.swift index df50507..9716eaf 100644 --- a/WireGuard/WireGuard/VPN/TunnelsManager.swift +++ b/WireGuard/WireGuard/VPN/TunnelsManager.swift @@ -80,7 +80,7 @@ class TunnelsManager { // NETunnelProviderManager APIs don't work on the simulator completionHandler(.success(TunnelsManager(tunnelProviders: []))) #else - NETunnelProviderManager.loadAllFromPreferences { (managers, error) in + NETunnelProviderManager.loadAllFromPreferences { managers, error in if let error = error { os_log("Failed to load tunnel provider managers: %{public}@", log: OSLog.default, type: .debug, "\(error)") completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnListingTunnels)) @@ -112,7 +112,7 @@ class TunnelsManager { activateOnDemandSetting.apply(on: tunnelProviderManager) - tunnelProviderManager.saveToPreferences { [weak self] (error) in + tunnelProviderManager.saveToPreferences { [weak self] error in guard error == nil else { os_log("Add: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnAddTunnel)) @@ -138,7 +138,7 @@ class TunnelsManager { return } let tail = tunnelConfigurations.dropFirst() - self.add(tunnelConfiguration: head) { [weak self, tail] (result) in + self.add(tunnelConfiguration: head) { [weak self, tail] result in DispatchQueue.main.async { self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (result.isSuccess ? 1 : 0), completionHandler: completionHandler) } @@ -169,7 +169,7 @@ class TunnelsManager { let isActivatingOnDemand = (!tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled) activateOnDemandSetting.apply(on: tunnelProviderManager) - tunnelProviderManager.saveToPreferences { [weak self] (error) in + tunnelProviderManager.saveToPreferences { [weak self] error in guard error == nil else { os_log("Modify: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") completionHandler(TunnelsManagerError.vpnSystemErrorOnModifyTunnel) @@ -186,15 +186,14 @@ class TunnelsManager { if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting { // Turn off the tunnel, and then turn it back on, so the changes are made effective - let session = (tunnel.tunnelProvider.connection as! NETunnelProviderSession) tunnel.status = .restarting - session.stopTunnel() + (tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel() } if isActivatingOnDemand { // Reload tunnel after saving. // Without this, the tunnel stopes getting updates on the tunnel status from iOS. - tunnelProviderManager.loadFromPreferences { (error) in + tunnelProviderManager.loadFromPreferences { error in tunnel.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled guard error == nil else { os_log("Modify: Re-loading after saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") @@ -213,7 +212,7 @@ class TunnelsManager { func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) { let tunnelProviderManager = tunnel.tunnelProvider - tunnelProviderManager.removeFromPreferences { [weak self] (error) in + tunnelProviderManager.removeFromPreferences { [weak self] error in guard error == nil else { os_log("Remove: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") completionHandler(TunnelsManagerError.vpnSystemErrorOnRemoveTunnel) @@ -237,7 +236,7 @@ class TunnelsManager { } func tunnel(named tunnelName: String) -> TunnelContainer? { - return self.tunnels.first(where: { $0.name == tunnelName }) + return self.tunnels.first { $0.name == tunnelName } } func startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) { @@ -272,7 +271,7 @@ class TunnelsManager { statusObservationToken = NotificationCenter.default.addObserver( forName: .NEVPNStatusDidChange, object: nil, - queue: OperationQueue.main) { [weak self] (statusChangeNotification) in + queue: OperationQueue.main) { [weak self] statusChangeNotification in guard let self = self else { return } guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return } guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return } @@ -299,7 +298,7 @@ class TunnelsManager { // Don't change tunnel.status when disconnecting for a restart if session.status == .disconnected { self.tunnelBeingActivated = tunnel - tunnel.startActivation(completionHandler: { _ in }) + tunnel.startActivation { _ in } } return } @@ -339,7 +338,7 @@ class TunnelContainer: NSObject { } func tunnelConfiguration() -> TunnelConfiguration? { - return (tunnelProvider.protocolConfiguration as! NETunnelProviderProtocol).tunnelConfiguration() + return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.tunnelConfiguration() } func activateOnDemandSetting() -> ActivateOnDemandSetting { @@ -377,7 +376,7 @@ class TunnelContainer: NSObject { // then call this function again. os_log("startActivation: Tunnel is disabled. Re-enabling and saving", log: OSLog.default, type: .info) tunnelProvider.isEnabled = true - tunnelProvider.saveToPreferences { [weak self] (error) in + tunnelProvider.saveToPreferences { [weak self] error in if error != nil { os_log("Error saving tunnel after re-enabling: %{public}@", log: OSLog.default, type: .error, "\(error!)") completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) @@ -392,10 +391,9 @@ class TunnelContainer: NSObject { } // Start the tunnel - let session = (tunnelProvider.connection as! NETunnelProviderSession) do { os_log("startActivation: Starting tunnel", log: OSLog.default, type: .debug) - try session.startTunnel() + try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel() os_log("startActivation: Success", log: OSLog.default, type: .debug) completionHandler(nil) } catch let error { @@ -412,7 +410,7 @@ class TunnelContainer: NSObject { return } os_log("startActivation: Will reload tunnel and then try to start it. ", log: OSLog.default, type: .info) - tunnelProvider.loadFromPreferences { [weak self] (error) in + tunnelProvider.loadFromPreferences { [weak self] error in if error != nil { os_log("startActivation: Error reloading tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error!)") self?.status = .inactive @@ -427,8 +425,7 @@ class TunnelContainer: NSObject { } fileprivate func startDeactivation() { - let session = (tunnelProvider.connection as! NETunnelProviderSession) - session.stopTunnel() + (tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel() } } diff --git a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift index 4ce89b2..57093c8 100644 --- a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift +++ b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift @@ -66,25 +66,12 @@ extension DNSResolver { // Based on DNS resolution code by Jason Donenfeld // in parse_endpoint() in src/tools/config.c in the WireGuard codebase private static func resolveSync(endpoint: Endpoint) -> Endpoint? { - var hints = addrinfo( - ai_flags: 0, - ai_family: AF_UNSPEC, - ai_socktype: SOCK_DGRAM, // WireGuard is UDP-only - ai_protocol: IPPROTO_UDP, // WireGuard is UDP-only - ai_addrlen: 0, - ai_canonname: nil, - ai_addr: nil, - ai_next: nil) - var resultPointer = UnsafeMutablePointer(OpaquePointer(bitPattern: 0)) switch endpoint.host { case .name(let name, _): + var resultPointer = UnsafeMutablePointer(OpaquePointer(bitPattern: 0)) + // The endpoint is a hostname and needs DNS resolution - let returnValue = getaddrinfo( - name.cString(using: .utf8), // Hostname - "\(endpoint.port)".cString(using: .utf8), // Port - &hints, - &resultPointer) - if returnValue == 0 { + if addressInfo(for: name, port: endpoint.port, resultPointer: &resultPointer) == 0 { // getaddrinfo succeeded let ipv4Buffer = UnsafeMutablePointer.allocate(capacity: Int(INET_ADDRSTRLEN)) let ipv6Buffer = UnsafeMutablePointer.allocate(capacity: Int(INET6_ADDRSTRLEN)) @@ -115,9 +102,9 @@ extension DNSResolver { ipv6Buffer.deallocate() // We prefer an IPv4 address over an IPv6 address if let ipv4AddressString = ipv4AddressString, let ipv4Address = IPv4Address(ipv4AddressString) { - return Endpoint(host: NWEndpoint.Host.ipv4(ipv4Address), port: endpoint.port) + return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port) } else if let ipv6AddressString = ipv6AddressString, let ipv6Address = IPv6Address(ipv6AddressString) { - return Endpoint(host: NWEndpoint.Host.ipv6(ipv6Address), port: endpoint.port) + return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port) } else { return nil } @@ -130,4 +117,22 @@ extension DNSResolver { return endpoint } } + + private static func addressInfo(for name: String, port: NWEndpoint.Port, resultPointer: inout UnsafeMutablePointer?) -> Int32 { + var hints = addrinfo( + ai_flags: 0, + ai_family: AF_UNSPEC, + ai_socktype: SOCK_DGRAM, // WireGuard is UDP-only + ai_protocol: IPPROTO_UDP, // WireGuard is UDP-only + ai_addrlen: 0, + ai_canonname: nil, + ai_addr: nil, + ai_next: nil) + + return getaddrinfo( + name.cString(using: .utf8), // Hostname + "\(port)".cString(using: .utf8), // Port + &hints, + &resultPointer) + } } diff --git a/WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift b/WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift index f06860a..163535a 100644 --- a/WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift +++ b/WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift @@ -20,6 +20,6 @@ class ErrorNotifier { static func notify(_ error: PacketTunnelProviderError, from tunnelProvider: NEPacketTunnelProvider) { guard let (title, message) = ErrorNotifier.errorMessage(for: error) else { return } // displayMessage() is deprecated, but there's no better alternative to show the error to the user - tunnelProvider.displayMessage("\(title): \(message)", completionHandler: { (_) in }) + tunnelProvider.displayMessage("\(title): \(message)") { _ in } } } diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift index 029d74b..b1571d5 100644 --- a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift +++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift @@ -79,7 +79,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // Bring up wireguard-go backend - let fileDescriptor = packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32 + let fileDescriptor = packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32 //swiftlint:disable:this force_cast if fileDescriptor < 0 { wg_log(.error, staticMessage: "Starting tunnel failed: Could not determine file descriptor") ErrorNotifier.notify(PacketTunnelProviderError.couldNotStartWireGuard, from: self) @@ -124,7 +124,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // Apply network settings let networkSettings: NEPacketTunnelNetworkSettings = packetTunnelSettingsGenerator.generateNetworkSettings() - setTunnelNetworkSettings(networkSettings) { (error) in + setTunnelNetworkSettings(networkSettings) { error in if let error = error { wg_log(.error, staticMessage: "Starting tunnel failed: Error setting network settings.") wg_log(.error, message: "Error from setTunnelNetworkSettings: \(error.localizedDescription)") @@ -169,7 +169,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } // Setup WireGuard logger - wgSetLogger { (level, msgCStr) in + wgSetLogger { level, msgCStr in let logType: OSLogType switch level { case 0: @@ -187,7 +187,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } private func connect(interfaceName: String, settings: String, fileDescriptor: Int32) -> Int32 { - return withStringsAsGoStrings(interfaceName, settings) { (nameGoStr, settingsGoStr) -> Int32 in + return withStringsAsGoStrings(interfaceName, settings) { nameGoStr, settingsGoStr in return wgTurnOn(nameGoStr, settingsGoStr, fileDescriptor) } } @@ -206,9 +206,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } private func withStringsAsGoStrings(_ str1: String, _ str2: String, closure: (gostring_t, gostring_t) -> R) -> R { - return str1.withCString { (s1cStr) -> R in + return str1.withCString { s1cStr in let gstr1 = gostring_t(p: s1cStr, n: str1.utf8.count) - return str2.withCString { (s2cStr) -> R in + return str2.withCString { s2cStr in let gstr2 = gostring_t(p: s2cStr, n: str2.utf8.count) return closure(gstr1, gstr2) } diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift index b9562a0..1e5ae8e 100644 --- a/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift +++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift @@ -70,7 +70,7 @@ class PacketTunnelSettingsGenerator { * a valid IP address that will actually route over the Internet. */ var remoteAddress: String = "0.0.0.0" - let endpointsCompact = resolvedEndpoints.compactMap({ $0 }) + let endpointsCompact = resolvedEndpoints.compactMap { $0 } if endpointsCompact.count == 1 { switch endpointsCompact.first!.host { case .ipv4(let address):