]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
More linter warnings fixed, enabled more swiftlint rules, project cleanup
authorEric Kuck <eric@bluelinelabs.com>
Wed, 12 Dec 2018 21:33:14 +0000 (15:33 -0600)
committerEric Kuck <eric@bluelinelabs.com>
Wed, 12 Dec 2018 21:33:14 +0000 (15:33 -0600)
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
21 files changed:
WireGuard/.swiftlint.yml
WireGuard/Shared/NETunnelProviderProtocol+Extension.swift
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift
WireGuard/WireGuard/Crypto/Curve25519.swift
WireGuard/WireGuard/UI/TunnelViewModel.swift
WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift
WireGuard/WireGuard/UI/iOS/MainViewController.swift
WireGuard/WireGuard/UI/iOS/QRScanViewController.swift
WireGuard/WireGuard/UI/iOS/ScrollableLabel.swift
WireGuard/WireGuard/UI/iOS/SettingsTableViewController.swift
WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift
WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift
WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift
WireGuard/WireGuard/UI/iOS/UITableViewCell+Reuse.swift [new file with mode: 0644]
WireGuard/WireGuard/VPN/InternetReachability.swift
WireGuard/WireGuard/VPN/TunnelsManager.swift
WireGuard/WireGuardNetworkExtension/DNSResolver.swift
WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift

index b16b6b33e73ad8062f201395aeb4809680cd5271..df6266a36648083a10882b5daab954622791d7c4 100644 (file)
@@ -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:
index ec8b294a588485738b69e265cea5c490796a6cfa..960bf228cd4131b838eb34273d1564bcce11398f 100644 (file)
@@ -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 {
index 79d606d92a3efb93fd07377847a8bd98a47ea825..4177cbe70821796881d1fc739ed2b3d996d5c1b0 100644 (file)
@@ -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 = "<group>"; };
                6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyableLabelTableViewCell.swift; sourceTree = "<group>"; };
                6F0068562191AFD200419BE9 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = "<group>"; };
                6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = "<group>"; };
                                6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
                                6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
                                6F0068562191AFD200419BE9 /* ScrollableLabel.swift */,
+                               5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
                        );
                        path = iOS;
                        sourceTree = "<group>";
                                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 */,
index 515cd3ef49922474403cd47c1c01d6bdba1c66ef..138ca620820eeeedf1017a46a035d5d5766ee431 100644 (file)
@@ -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
+    }
+    
 }
index 43d9b00d899a3537bcd83330eb01c45438c8d185..53404cc16007a2750f59c4451d531057b75a6705 100644 (file)
@@ -9,7 +9,7 @@ struct Curve25519 {
 
     static func generatePrivateKey() -> Data {
         var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
-        privateKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) 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<UInt8>) in
-            publicKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) in
+        privateKey.withUnsafeBytes { privateKeyBytes in
+            publicKey.withUnsafeMutableBytes { bytes in
                 curve25519_derive_public_key(bytes, privateKeyBytes)
             }
         }
index 47a13a10b27e4289537035f6befe086362149092..af7ff4c1eca78c5a5743af342ff79e6af44e0bc9 100644 (file)
@@ -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
                 }
index 6cae1e6067dd4f74e242870b56be8932c4e30314..7c28495b15635a73009e2659c40344a2e509192b 100644 (file)
@@ -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)
index 70d838e2c5ee6467d59a6375f56bf66b81680c78..6822263d857656f19f6c257ad3eea5c874805f46 100644 (file)
@@ -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)
index 3849f70691cb7d922accc36184c69f52dd85eb5b..ad0fe79598190e987d99f78b7b075c01a913c7cf 100644 (file)
@@ -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
     }
index c0cc86bc18139d4375c82ca90c2e6fb3037e2222..f2d0f58504aa14cc71f8c1e2521f85c331bcc20d 100644 (file)
@@ -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,
index 31075795635ae64e4f03e1ea5322b452f4beaff9..8f248c90440789a7c1fa49ba6f68c470578bcce7 100644 (file)
@@ -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)
     }
 
index 2ab3c260b858ee3171b1c5db995d0da0bc53c6bc..e6ad02402733e4214541ebb47b8e676c00818475 100644 (file)
@@ -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
-                                }
-                            })
+        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)
                 }
+            } 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
-                        }
+        }
+        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)
                 }
+                self?.navigationController?.navigationController?.popToRootViewController(animated: true)
             }
-            return cell
         }
+        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())
             }
         }
index 330a3cc71c1a8d7a8dc085900ba0cd48cd01fc64..b26992d39f2938d6de18e42b3a851313ce120abe 100644 (file)
@@ -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
+        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 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)
             }
-            // Set keyboardType
-            if field == .mtu || field == .listenPort {
-                cell.keyboardType = .numberPad
-            } else if field == .addresses || field == .dns {
-                cell.keyboardType = .numbersAndPunctuation
+        }
+        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
             }
-            // 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
-                }
+        } else {
+            cell.onValueChanged = { [weak self] value in
+                self?.tunnelViewModel.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)
-                        }
-                    }
+            cell.onValueBeingEdited = nil
+        }
+        // Compute public key live
+        if field == .privateKey {
+            cell.onValueBeingEdited = { [weak self] value in
+                guard let self = self else { return }
+                
+                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 {
+            cell.onValueBeingEdited = nil
         }
+        return cell
     }
 
-    private func peerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
-        let peerIndex = indexPath.section - interfaceFieldsBySection.count
-        let peerData = tunnelViewModel.peersData[peerIndex]
+    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]
-        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 }
+        
+        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 }
-                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)
-                            }
-
+                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)
                         }
-                    })
-                }
-            }
-            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
-                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)
-                }
-            }
-            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
+                        
+                    }
+                })
             }
-            // 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 }
index bd5f97241b18159c2d62740c9e8437b90269d0fb..e5d5af13671cc4035953eaa82332a7a33a7a5bd6 100644 (file)
@@ -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 (file)
index 0000000..587f7b1
--- /dev/null
@@ -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: UITableViewCell>(_: T.Type) {
+        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
+    }
+    
+    func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T {
+        //swiftlint:disable:next force_cast
+        return dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T
+    }
+}
index 831050e62be10f3ca8da3d9e974bac6d2aa912b2..2e508520489ceba5228f1796991e0c6ecc03f1c2 100644 (file)
@@ -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)
             }
         }
index df5050709f44ae1483a3842914f80983ee409dd7..9716eafda1033b7d159e9686b4d268175508452d 100644 (file)
@@ -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()
     }
 }
 
index 4ce89b2def9faa975b5f8273d7f211b40b17eede..57093c820b10ac73079b2510f9382f7e7b258bcc 100644 (file)
@@ -66,25 +66,12 @@ extension DNSResolver {
     // Based on DNS resolution code by Jason Donenfeld <jason@zx2c4.com>
     // 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<addrinfo>(OpaquePointer(bitPattern: 0))
         switch endpoint.host {
         case .name(let name, _):
+            var resultPointer = UnsafeMutablePointer<addrinfo>(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<Int8>.allocate(capacity: Int(INET_ADDRSTRLEN))
                 let ipv6Buffer = UnsafeMutablePointer<Int8>.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<addrinfo>?) -> 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)
+    }
 }
index f06860a907ada5b53ad7c0d8eaabba2f0a87209a..163535a3bcb76cb3537032dd5245e47c3ce8e733 100644 (file)
@@ -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 }
     }
 }
index 029d74b78c309cb28946a693c197bbb459adbfb7..b1571d5278c3b2604d51e38aca284547aa9a7391 100644 (file)
@@ -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<R>(_ 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)
         }
index b9562a0bd6d17d697597fd840e1ef8053490dc47..1e5ae8e9008326943571bab9570fe0d1b7717e8b 100644 (file)
@@ -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):