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