Prepare for rewrite: Remove UI and model code
Signed-off-by: Roopesh Chander <roop@roopc.net>
|
@ -1,57 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
var appCoordinator: AppCoordinator!
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
self.window = UIWindow(frame: UIScreen.main.bounds)
|
||||
appCoordinator = AppCoordinator(window: self.window!)
|
||||
appCoordinator.start()
|
||||
|
||||
appCoordinator.checkAndCleanConfigs()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
defer {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url)
|
||||
} catch {
|
||||
os_log("Failed to remove item from Inbox: %{public}@", log: Log.general, type: .error, url.absoluteString)
|
||||
}
|
||||
}
|
||||
if url.pathExtension == "conf" {
|
||||
do {
|
||||
try appCoordinator.importConfig(config: url)
|
||||
} catch {
|
||||
os_log("Unable to import config: %{public}@", log: Log.general, type: .error, url.absoluteString)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else if url.pathExtension == "zip" {
|
||||
do {
|
||||
try appCoordinator.importConfigs(configZip: url)
|
||||
} catch {
|
||||
os_log("Unable to import config: %{public}@", log: Log.general, type: .error, url.absoluteString)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
appCoordinator.checkAndCleanConfigs()
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_20pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_20pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_29pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_29pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_40pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_40pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_60pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_60pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_20pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_20pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_29pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_29pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_76pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_76pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 740 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.9 KiB |
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Arrow.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Dots.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_action_delete.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_action_delete@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_action_delete@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 178 B |
Before Width: | Height: | Size: 289 B |
Before Width: | Height: | Size: 407 B |
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "wireguard.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="fHK-5V-Wmb">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="KXE-tg-GCt">
|
||||
<objects>
|
||||
<navigationController id="fHK-5V-Wmb" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="kSJ-B6-Sbs">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="barTintColor" red="0.53333333329999999" green="0.090196078430000007" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="ZbP-GT-mQn" kind="relationship" relationship="rootViewController" id="ejS-gV-hxU"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bEx-a1-E4B" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="150" y="313"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="QPe-xp-OsR">
|
||||
<objects>
|
||||
<viewController id="ZbP-GT-mQn" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="LhN-TB-3GI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<viewLayoutGuide key="safeArea" id="qsB-da-p6j"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="Qbp-DQ-urz"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Sdb-mk-Xj1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="910" y="313"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -1,17 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension AppCoordinator: QRScanViewControllerDelegate {
|
||||
func didSave(tunnel: Tunnel, qrScanViewController: QRScanViewController) {
|
||||
qrScanViewController.navigationController?.popViewController(animated: true)
|
||||
showTunnelInfoViewController(tunnel: tunnel, context: tunnel.managedObjectContext!)
|
||||
saveTunnel(tunnel)
|
||||
}
|
||||
|
||||
func didCancel(qrScanViewController: QRScanViewController) {
|
||||
qrScanViewController.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import PromiseKit
|
||||
import NetworkExtension
|
||||
|
||||
enum GoVersionCoordinatorError: Error {
|
||||
case noEnabledSession
|
||||
case noResponse
|
||||
}
|
||||
|
||||
extension AppCoordinator: SettingsTableViewControllerDelegate {
|
||||
func exportTunnels(settingsTableViewController: SettingsTableViewController, sourceView: UIView) {
|
||||
self.exportConfigs(sourceView: sourceView)
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension AppCoordinator: TunnelConfigurationTableViewControllerDelegate {
|
||||
func didSave(tunnel: Tunnel, tunnelConfigurationTableViewController: TunnelConfigurationTableViewController) {
|
||||
saveTunnel(tunnel)
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
extension AppCoordinator: TunnelInfoTableViewControllerDelegate {
|
||||
func connect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) {
|
||||
connect(tunnel: tunnel)
|
||||
}
|
||||
|
||||
func disconnect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) {
|
||||
disconnect(tunnel: tunnel)
|
||||
}
|
||||
|
||||
func status(for tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) -> NEVPNStatus {
|
||||
let session = self.providerManager(for: tunnel)?.connection as? NETunnelProviderSession
|
||||
return session?.status ?? .invalid
|
||||
}
|
||||
|
||||
func configure(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) {
|
||||
let editContext = persistentContainer.newBackgroundContext()
|
||||
var backgroundTunnel: Tunnel?
|
||||
editContext.performAndWait {
|
||||
backgroundTunnel = editContext.object(with: tunnel.objectID) as? Tunnel
|
||||
}
|
||||
|
||||
showTunnelConfigurationViewController(tunnel: backgroundTunnel, context: editContext)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import NetworkExtension
|
||||
import os.log
|
||||
|
||||
import MobileCoreServices
|
||||
|
||||
import ZIPFoundation
|
||||
import PromiseKit
|
||||
|
||||
extension AppCoordinator: TunnelsTableViewControllerDelegate {
|
||||
func status(for tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) -> NEVPNStatus {
|
||||
let session = self.providerManager(for: tunnel)?.connection as? NETunnelProviderSession
|
||||
return session?.status ?? .invalid
|
||||
}
|
||||
|
||||
func addProvider(tunnelsTableViewController: TunnelsTableViewController) {
|
||||
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("Create from file or archive", comment: ""), style: .default) { [unowned self] _ in
|
||||
self.addProviderFromFile()
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("Create from QR code", comment: ""), style: .default) { [unowned self] _ in
|
||||
self.addProviderWithQRScan()
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("Create from scratch", comment: ""), style: .default) { [unowned self] _ in
|
||||
self.addProviderManually()
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel))
|
||||
|
||||
tunnelsTableViewController.present(actionSheet, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func addProviderFromFile() {
|
||||
let documentPickerController = UIDocumentPickerViewController(documentTypes: [String(kUTTypeZipArchive), "com.wireguard.config.quick"], in: .import)
|
||||
documentPickerController.delegate = documentPickerDelegateObject
|
||||
tunnelsTableViewController.present(documentPickerController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func addProviderManually() {
|
||||
let addContext = persistentContainer.newBackgroundContext()
|
||||
showTunnelConfigurationViewController(tunnel: nil, context: addContext)
|
||||
}
|
||||
|
||||
func addProviderWithQRScan() {
|
||||
let addContext = persistentContainer.newBackgroundContext()
|
||||
|
||||
let qrScanViewController = storyboard.instantiateViewController(type: QRScanViewController.self)
|
||||
|
||||
qrScanViewController.configure(context: addContext, delegate: self)
|
||||
|
||||
self.navigationController.pushViewController(qrScanViewController, animated: true)
|
||||
}
|
||||
|
||||
func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) {
|
||||
connect(tunnel: tunnel)
|
||||
}
|
||||
|
||||
func disconnect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) {
|
||||
disconnect(tunnel: tunnel)
|
||||
}
|
||||
|
||||
func info(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) {
|
||||
showTunnelInfoViewController(tunnel: tunnel, context: self.persistentContainer.viewContext)
|
||||
}
|
||||
|
||||
func delete(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) {
|
||||
if let moc = tunnel.managedObjectContext {
|
||||
moc.perform {
|
||||
moc.delete(tunnel)
|
||||
moc.saveContextToStore()
|
||||
}
|
||||
let manager = providerManager(for: tunnel)
|
||||
manager?.removeFromPreferences { (error) in
|
||||
if let error = error {
|
||||
os_log("error removing preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription)
|
||||
return
|
||||
}
|
||||
self.providerManagers?.removeAll { $0 == manager }
|
||||
os_log("removed preferences", log: Log.general, type: .info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveTunnel(_ tunnel: Tunnel) {
|
||||
let manager = providerManager(for: tunnel) ?? NETunnelProviderManager()
|
||||
manager.localizedDescription = tunnel.title
|
||||
|
||||
let protocolConfiguration = NETunnelProviderProtocol()
|
||||
protocolConfiguration.providerBundleIdentifier = VPNBUNDLE
|
||||
protocolConfiguration.serverAddress = (tunnel.peers?.array as? [Peer])?.compactMap { $0.endpoint}.joined(separator: ", ")
|
||||
protocolConfiguration.providerConfiguration = tunnel.generateProviderConfiguration()
|
||||
|
||||
manager.protocolConfiguration = protocolConfiguration
|
||||
let connectRule = NEOnDemandRuleConnect()
|
||||
connectRule.interfaceTypeMatch = .any
|
||||
manager.onDemandRules = [connectRule]
|
||||
|
||||
manager.saveToPreferences { (error) in
|
||||
if let error = error {
|
||||
os_log("error saving preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription)
|
||||
return
|
||||
}
|
||||
os_log("saved preferences", log: Log.general, type: .info)
|
||||
}
|
||||
|
||||
_ = refreshProviderManagers().then { () -> Promise<Void> in
|
||||
self.navigationController.popViewController(animated: true)
|
||||
return Promise.value(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,468 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import os.log
|
||||
import ZIPFoundation
|
||||
import PromiseKit
|
||||
|
||||
import CoreData
|
||||
import BNRCoreDataStack
|
||||
|
||||
import MobileCoreServices
|
||||
|
||||
enum AppCoordinatorError: Error {
|
||||
case configImportError(msg: String)
|
||||
}
|
||||
|
||||
extension UINavigationController: Identifyable {}
|
||||
|
||||
let APPGROUP = "group.com.wireguard.ios"
|
||||
let VPNBUNDLE = "com.wireguard.ios.network-extension"
|
||||
|
||||
class AppCoordinator: RootViewCoordinator { // swiftlint:disable:this type_body_length
|
||||
|
||||
let persistentContainer = NSPersistentContainer(name: "WireGuard")
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
var providerManagers: [NETunnelProviderManager]?
|
||||
let documentPickerDelegateObject: AppDocumentPickerDelegate
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
var rootViewController: UIViewController {
|
||||
return self.tunnelsTableViewController
|
||||
}
|
||||
|
||||
var tunnelsTableViewController: TunnelsTableViewController!
|
||||
|
||||
/// Window to manage
|
||||
let window: UIWindow
|
||||
|
||||
let navigationController: UINavigationController = {
|
||||
let navController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(type: UINavigationController.self)
|
||||
return navController
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
public init(window: UIWindow) {
|
||||
self.window = window
|
||||
|
||||
self.window.rootViewController = self.navigationController
|
||||
self.window.makeKeyAndVisible()
|
||||
|
||||
documentPickerDelegateObject = AppDocumentPickerDelegate()
|
||||
documentPickerDelegateObject.appCoordinator = self
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(VPNStatusDidChange(notification:)),
|
||||
name: .NEVPNStatusDidChange,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
/// Starts the coordinator
|
||||
public func start() {
|
||||
_ = refreshProviderManagers().then { () -> Promise<Void> in
|
||||
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
|
||||
self.persistentContainer.loadPersistentStores { [weak self] (_, error) in
|
||||
if let error = error {
|
||||
os_log("Unable to load Persistent Store: %{public}@", log: Log.general, type: .error, error.localizedDescription)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
//start
|
||||
if let tunnelsTableViewController = self?.storyboard.instantiateViewController(type: TunnelsTableViewController.self) {
|
||||
self?.tunnelsTableViewController = tunnelsTableViewController
|
||||
self?.tunnelsTableViewController.viewContext = self?.persistentContainer.viewContext
|
||||
self?.tunnelsTableViewController.delegate = self
|
||||
self?.navigationController.viewControllers = [tunnelsTableViewController]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.value(())
|
||||
}
|
||||
}
|
||||
|
||||
func refreshProviderManagers() -> Promise<Void> {
|
||||
return Promise { (resolver) in
|
||||
NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in
|
||||
if let error = error {
|
||||
os_log("Unable to load provider managers: %{public}@", log: Log.general, type: .error, error.localizedDescription)
|
||||
}
|
||||
self?.providerManagers = managers
|
||||
resolver.fulfill(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importConfig(config: URL) throws {
|
||||
do {
|
||||
try importConfig(configString: try String(contentsOf: config), title: config.deletingPathExtension().lastPathComponent)
|
||||
} catch {
|
||||
throw AppCoordinatorError.configImportError(msg: "Failed")
|
||||
}
|
||||
}
|
||||
|
||||
func importConfig(configString: String, title: String) throws {
|
||||
do {
|
||||
let addContext = persistentContainer.newBackgroundContext()
|
||||
let tunnel = try Tunnel.fromConfig(configString, context: addContext)
|
||||
tunnel.title = title
|
||||
addContext.saveContext()
|
||||
self.saveTunnel(tunnel)
|
||||
} catch {
|
||||
throw AppCoordinatorError.configImportError(msg: "Failed")
|
||||
}
|
||||
}
|
||||
|
||||
func importConfigs(configZip: URL) throws {
|
||||
if let archive = Archive(url: configZip, accessMode: .read) {
|
||||
for entry in archive {
|
||||
var entryData = Data(capacity: 0)
|
||||
_ = try archive.extract(entry) { (data) in
|
||||
entryData.append(data)
|
||||
}
|
||||
if let config = String(data: entryData, encoding: .utf8) {
|
||||
try importConfig(configString: config, title: entry.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkAndCleanConfigs() {
|
||||
_ = refreshProviderManagers().then { () -> Promise<Void> in
|
||||
guard let providerManagers = self.providerManagers else {
|
||||
return Promise.value(())
|
||||
}
|
||||
let tunnels = try Tunnel.allInContext(self.persistentContainer.viewContext)
|
||||
let tunnelIdentifiers = tunnels.compactMap {$0.tunnelIdentifier}
|
||||
|
||||
let unknownManagers = providerManagers.filter {
|
||||
guard let prot = $0.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
return false
|
||||
}
|
||||
guard let candidateTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
|
||||
return false
|
||||
}
|
||||
|
||||
return !tunnelIdentifiers.contains(candidateTunnelIdentifier)
|
||||
}
|
||||
|
||||
let deletionPromises = unknownManagers.map({ (manager) -> Promise<NETunnelProviderManager> in
|
||||
return Promise(resolver: { resolver in
|
||||
return manager.removeFromPreferences(completionHandler: { (error) in
|
||||
if let error = error {
|
||||
resolver.reject(error)
|
||||
} else {
|
||||
resolver.fulfill(manager)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return when(resolved: deletionPromises).asVoid()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable next function_body_length
|
||||
func exportConfigs(sourceView: UIView) {
|
||||
guard let path = FileManager.default
|
||||
.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
return
|
||||
}
|
||||
let saveFileURL = path.appendingPathComponent("wireguard-export.zip")
|
||||
do {
|
||||
try FileManager.default.removeItem(at: saveFileURL)
|
||||
} catch {
|
||||
os_log("Failed to delete file: %{public}@ : %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString, error.localizedDescription)
|
||||
}
|
||||
|
||||
guard let archive = Archive(url: saveFileURL, accessMode: .create) else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
var tunnelsByTitle = [String: [Tunnel]]()
|
||||
let tunnels = try Tunnel.allInContext(persistentContainer.viewContext)
|
||||
tunnels.forEach {
|
||||
guard let title = $0.title ?? $0.tunnelIdentifier else {
|
||||
// there is always a tunnelidentifier.
|
||||
return
|
||||
}
|
||||
if let tunnels = tunnelsByTitle[title] {
|
||||
tunnelsByTitle[title] = tunnels + [$0]
|
||||
} else {
|
||||
tunnelsByTitle[title] = [$0]
|
||||
}
|
||||
}
|
||||
|
||||
func addEntry(title: String, tunnel: Tunnel) throws {
|
||||
let data = tunnel.export().data(using: .utf8)!
|
||||
let byteCount: UInt32 = UInt32(data.count)
|
||||
try archive.addEntry(with: "\(title).conf", type: .file, uncompressedSize: byteCount, provider: { (position, size) -> Data in
|
||||
return data.subdata(in: position ..< size)
|
||||
})
|
||||
}
|
||||
|
||||
try tunnelsByTitle.keys.forEach {
|
||||
if let tunnels = tunnelsByTitle[$0] {
|
||||
if tunnels.count == 1 {
|
||||
try addEntry(title: $0, tunnel: tunnels[0])
|
||||
} else {
|
||||
for (index, tunnel) in tunnels.enumerated() {
|
||||
try addEntry(title: $0 + "-\(index + 1)", tunnel: tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
os_log("Failed to create archive file: %{public}@ : %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString, error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
let activityViewController = UIActivityViewController(
|
||||
activityItems: [saveFileURL],
|
||||
applicationActivities: nil)
|
||||
if let popoverPresentationController = activityViewController.popoverPresentationController {
|
||||
popoverPresentationController.sourceView = sourceView
|
||||
}
|
||||
navigationController.present(activityViewController, animated: true) {
|
||||
}
|
||||
}
|
||||
|
||||
func exportConfig(tunnel: Tunnel, barButtonItem: UIBarButtonItem) {
|
||||
let exportString = tunnel.export()
|
||||
|
||||
guard let path = FileManager.default
|
||||
.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
return
|
||||
}
|
||||
let saveFileURL = path.appendingPathComponent("/\(tunnel.title ?? "wireguard").conf")
|
||||
do {
|
||||
try exportString.write(to: saveFileURL, atomically: true, encoding: .utf8)
|
||||
} catch {
|
||||
os_log("Failed to export tunnel to: %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString)
|
||||
return
|
||||
}
|
||||
|
||||
let activityViewController = UIActivityViewController(
|
||||
activityItems: [saveFileURL],
|
||||
applicationActivities: nil)
|
||||
if let popoverPresentationController = activityViewController.popoverPresentationController {
|
||||
popoverPresentationController.barButtonItem = barButtonItem
|
||||
}
|
||||
self.navigationController.present(activityViewController, animated: true) {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NEVPNManager handling
|
||||
|
||||
@objc private func VPNStatusDidChange(notification: NSNotification) {
|
||||
guard let session = notification.object as? NETunnelProviderSession else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let prot = session.manager.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let changedTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
providerManagers?.first(where: { (manager) -> Bool in
|
||||
guard let prot = manager.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
return false
|
||||
}
|
||||
guard let candidateTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
|
||||
return false
|
||||
}
|
||||
|
||||
return changedTunnelIdentifier == candidateTunnelIdentifier
|
||||
|
||||
})?.loadFromPreferences(completionHandler: { [weak self] (_) in
|
||||
self?.tunnelsTableViewController.updateStatus(for: changedTunnelIdentifier)
|
||||
})
|
||||
}
|
||||
|
||||
func showTunnelInfoViewController(tunnel: Tunnel, context: NSManagedObjectContext) {
|
||||
let tunnelInfoViewController = storyboard.instantiateViewController(type: TunnelInfoTableViewController.self)
|
||||
|
||||
tunnelInfoViewController.configure(context: context, delegate: self, tunnel: tunnel)
|
||||
|
||||
self.navigationController.pushViewController(tunnelInfoViewController, animated: true)
|
||||
}
|
||||
|
||||
func showTunnelConfigurationViewController(tunnel: Tunnel?, context: NSManagedObjectContext) {
|
||||
let tunnelConfigurationViewController = storyboard.instantiateViewController(type: TunnelConfigurationTableViewController.self)
|
||||
|
||||
tunnelConfigurationViewController.configure(context: context, delegate: self, tunnel: tunnel)
|
||||
|
||||
self.navigationController.pushViewController(tunnelConfigurationViewController, animated: true)
|
||||
}
|
||||
|
||||
func showSettings() {
|
||||
let settingsTableViewController = storyboard.instantiateViewController(type: SettingsTableViewController.self)
|
||||
|
||||
settingsTableViewController.delegate = self
|
||||
|
||||
self.navigationController.pushViewController(settingsTableViewController, animated: true)
|
||||
}
|
||||
|
||||
public func showError(_ error: Error) {
|
||||
showAlert(title: NSLocalizedString("Error", comment: "Error alert title"), message: error.localizedDescription)
|
||||
}
|
||||
|
||||
func connect(tunnel: Tunnel) {
|
||||
_ = refreshProviderManagers().then { () -> Promise<Void> in
|
||||
guard let manager = self.providerManager(for: tunnel) else {
|
||||
return Promise.value(())
|
||||
}
|
||||
let block = {
|
||||
switch manager.connection.status {
|
||||
case .invalid, .disconnected:
|
||||
os_log("connect tunnel: %{public}@", log: Log.general, type: .info, tunnel.description)
|
||||
// Should the manager be enabled?
|
||||
|
||||
let manager = self.providerManager(for: tunnel)
|
||||
manager?.isEnabled = true
|
||||
manager?.saveToPreferences { (error) in
|
||||
if let error = error {
|
||||
os_log("error saving preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription)
|
||||
return
|
||||
}
|
||||
os_log("saved preferences", log: Log.general, type: .info)
|
||||
|
||||
let session = manager?.connection as! NETunnelProviderSession //swiftlint:disable:this force_cast
|
||||
do {
|
||||
try session.startTunnel()
|
||||
} catch let error {
|
||||
os_log("error starting tunnel: %{public}@", log: Log.general, type: .error, error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if manager.connection.status == .invalid {
|
||||
manager.loadFromPreferences { (_) in
|
||||
block()
|
||||
}
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
|
||||
return Promise.value(())
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect(tunnel: Tunnel) {
|
||||
_ = refreshProviderManagers().then { () -> Promise<Void> in
|
||||
let manager = self.providerManager(for: tunnel)!
|
||||
let block = {
|
||||
switch manager.connection.status {
|
||||
case .connected, .connecting:
|
||||
let manager = self.providerManager(for: tunnel)
|
||||
manager?.connection.stopVPNTunnel()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if manager.connection.status == .invalid {
|
||||
manager.loadFromPreferences { (_) in
|
||||
block()
|
||||
}
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
return Promise.value(())
|
||||
}
|
||||
}
|
||||
|
||||
private func showAlert(title: String, message: String) {
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "OK button"), style: .default))
|
||||
self.navigationController.present(alert, animated: true)
|
||||
}
|
||||
|
||||
func providerManager(for tunnel: Tunnel) -> NETunnelProviderManager? {
|
||||
return self.providerManagers?.first {
|
||||
guard let prot = $0.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
return false
|
||||
}
|
||||
guard let tunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
|
||||
return false
|
||||
}
|
||||
return tunnelIdentifier == tunnel.tunnelIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
func extensionGoVersionInformation() -> Promise<String> {
|
||||
return Promise(resolver: { (resolver) in
|
||||
guard let session = self.providerManagers?.first(where: { $0.isEnabled })?.connection as? NETunnelProviderSession else {
|
||||
resolver.reject(GoVersionCoordinatorError.noEnabledSession)
|
||||
return
|
||||
}
|
||||
do {
|
||||
try session.sendProviderMessage(ExtensionMessage.requestVersion.data, responseHandler: { (data) in
|
||||
guard let data = data, let responseString = String(data: data, encoding: .utf8) else {
|
||||
resolver.reject(GoVersionCoordinatorError.noResponse)
|
||||
return
|
||||
}
|
||||
resolver.fulfill(responseString)
|
||||
})
|
||||
} catch {
|
||||
resolver.reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class AppDocumentPickerDelegate: NSObject, UIDocumentPickerDelegate {
|
||||
weak var appCoordinator: AppCoordinator?
|
||||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
|
||||
if url.pathExtension == "conf" {
|
||||
do {
|
||||
try appCoordinator?.importConfig(config: url)
|
||||
} catch {
|
||||
os_log("Unable to import config: %{public}@", log: Log.general, type: .error, url.absoluteString)
|
||||
}
|
||||
} else if url.pathExtension == "zip" {
|
||||
do {
|
||||
try appCoordinator?.importConfigs(configZip: url)
|
||||
} catch {
|
||||
os_log("Unable to import config: %{public}@", log: Log.general, type: .error, url.absoluteString)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension NEVPNStatus {
|
||||
var statusDescription: String {
|
||||
switch self {
|
||||
case .connected:
|
||||
return "Connected"
|
||||
case .connecting:
|
||||
return "Connecting"
|
||||
case .disconnected:
|
||||
return "Disconnected"
|
||||
case .disconnecting:
|
||||
return "Disconnecting"
|
||||
case .invalid:
|
||||
return "Invalid"
|
||||
case .reasserting:
|
||||
return "Reasserting"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The Coordinator protocol
|
||||
public protocol Coordinator: class {
|
||||
|
||||
/// Starts the coordinator
|
||||
func start()
|
||||
|
||||
/// The array containing any child Coordinators
|
||||
var childCoordinators: [Coordinator] { get set }
|
||||
|
||||
}
|
||||
|
||||
public extension Coordinator {
|
||||
|
||||
/// Add a child coordinator to the parent
|
||||
public func addChildCoordinator(_ childCoordinator: Coordinator) {
|
||||
self.childCoordinators.append(childCoordinator)
|
||||
}
|
||||
|
||||
/// Remove a child coordinator from the parent
|
||||
public func removeChildCoordinator(_ childCoordinator: Coordinator) {
|
||||
self.childCoordinators = self.childCoordinators.filter { $0 !== childCoordinator }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public protocol RootViewControllerProvider: class {
|
||||
// The coordinators 'rootViewController'. It helps to think of this as the view
|
||||
// controller that can be used to dismiss the coordinator from the view hierarchy.
|
||||
var rootViewController: UIViewController { get }
|
||||
}
|
||||
|
||||
/// A Coordinator type that provides a root UIViewController
|
||||
public typealias RootViewCoordinator = Coordinator & RootViewControllerProvider
|
|
@ -1,177 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* Copyright (C) 2015-2018 WireGuard LLC. All Rights Reserved.
|
||||
*
|
||||
* Curve25519 ECDH functions, based on TweetNaCl but cleaned up.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <CommonCrypto/CommonRandom.h>
|
||||
|
||||
#include "x25519.h"
|
||||
|
||||
typedef int64_t fe[16];
|
||||
|
||||
static inline void carry(fe o)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);
|
||||
o[i] &= 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void cswap(fe p, fe q, int b)
|
||||
{
|
||||
int i;
|
||||
int64_t t, c = ~(b - 1);
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
t = c & (p[i] ^ q[i]);
|
||||
p[i] ^= t;
|
||||
q[i] ^= t;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void pack(uint8_t *o, const fe n)
|
||||
{
|
||||
int i, j, b;
|
||||
fe m, t;
|
||||
|
||||
memcpy(t, n, sizeof(t));
|
||||
carry(t);
|
||||
carry(t);
|
||||
carry(t);
|
||||
for (j = 0; j < 2; ++j) {
|
||||
m[0] = t[0] - 0xffed;
|
||||
for (i = 1; i < 15; ++i) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
|
||||
m[i - 1] &= 0xffff;
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
|
||||
b = (m[15] >> 16) & 1;
|
||||
m[14] &= 0xffff;
|
||||
cswap(t, m, 1 - b);
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
o[2 * i] = t[i] & 0xff;
|
||||
o[2 * i + 1] = t[i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void unpack(fe o, const uint8_t *n)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
o[i] = n[2 * i] + ((int64_t)n[2 * i + 1] << 8);
|
||||
o[15] &= 0x7fff;
|
||||
}
|
||||
|
||||
static inline void add(fe o, const fe a, const fe b)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
o[i] = a[i] + b[i];
|
||||
}
|
||||
|
||||
static inline void subtract(fe o, const fe a, const fe b)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
o[i] = a[i] - b[i];
|
||||
}
|
||||
|
||||
static inline void multmod(fe o, const fe a, const fe b)
|
||||
{
|
||||
int i, j;
|
||||
int64_t t[31] = { 0 };
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
for (j = 0; j < 16; ++j)
|
||||
t[i + j] += a[i] * b[j];
|
||||
}
|
||||
for (i = 0; i < 15; ++i)
|
||||
t[i] += 38 * t[i + 16];
|
||||
memcpy(o, t, sizeof(fe));
|
||||
carry(o);
|
||||
carry(o);
|
||||
}
|
||||
|
||||
static inline void invert(fe o, const fe i)
|
||||
{
|
||||
fe c;
|
||||
int a;
|
||||
|
||||
memcpy(c, i, sizeof(c));
|
||||
for (a = 253; a >= 0; --a) {
|
||||
multmod(c, c, c);
|
||||
if (a != 2 && a != 4)
|
||||
multmod(c, c, i);
|
||||
}
|
||||
memcpy(o, c, sizeof(fe));
|
||||
}
|
||||
|
||||
static void curve25519_shared_secret(uint8_t shared_secret[32], const uint8_t private_key[32], const uint8_t public_key[32])
|
||||
{
|
||||
static const fe a24 = { 0xdb41, 1 };
|
||||
uint8_t z[32];
|
||||
int64_t r;
|
||||
int i;
|
||||
fe a = { 1 }, b, c = { 0 }, d = { 1 }, e, f, x;
|
||||
|
||||
memcpy(z, private_key, sizeof(z));
|
||||
|
||||
z[31] = (z[31] & 127) | 64;
|
||||
z[0] &= 248;
|
||||
|
||||
unpack(x, public_key);
|
||||
memcpy(b, x, sizeof(b));
|
||||
|
||||
for (i = 254; i >= 0; --i) {
|
||||
r = (z[i >> 3] >> (i & 7)) & 1;
|
||||
cswap(a, b, (int)r);
|
||||
cswap(c, d, (int)r);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
add(c, b, d);
|
||||
subtract(b, b, d);
|
||||
multmod(d, e, e);
|
||||
multmod(f, a, a);
|
||||
multmod(a, c, a);
|
||||
multmod(c, b, e);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
multmod(b, a, a);
|
||||
subtract(c, d, f);
|
||||
multmod(a, c, a24);
|
||||
add(a, a, d);
|
||||
multmod(c, c, a);
|
||||
multmod(a, d, f);
|
||||
multmod(d, b, x);
|
||||
multmod(b, e, e);
|
||||
cswap(a, b, (int)r);
|
||||
cswap(c, d, (int)r);
|
||||
}
|
||||
invert(c, c);
|
||||
multmod(a, a, c);
|
||||
pack(shared_secret, a);
|
||||
}
|
||||
|
||||
void curve25519_derive_public_key(uint8_t public_key[32], const uint8_t private_key[32])
|
||||
{
|
||||
static const uint8_t basepoint[32] = { 9 };
|
||||
|
||||
curve25519_shared_secret(public_key, private_key, basepoint);
|
||||
}
|
||||
|
||||
void curve25519_generate_private_key(uint8_t private_key[32])
|
||||
{
|
||||
CCRandomGenerateBytes(private_key, 32);
|
||||
private_key[31] = (private_key[31] & 127) | 64;
|
||||
private_key[0] &= 248;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#ifndef X25519_H
|
||||
#define X25519_H
|
||||
|
||||
void curve25519_derive_public_key(unsigned char public_key[32], const unsigned char private_key[32]);
|
||||
void curve25519_generate_private_key(unsigned char private_key[32]);
|
||||
|
||||
#endif
|
|
@ -1,40 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class CopyableLabel: UILabel {
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
|
||||
self.addGestureRecognizer(gestureRecognizer)
|
||||
self.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizer
|
||||
@objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
|
||||
guard recognizer.state == .recognized else { return }
|
||||
|
||||
if let recognizerView = recognizer.view,
|
||||
let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
|
||||
let menuController = UIMenuController.shared
|
||||
menuController.setTargetRect(recognizerView.frame, in: recognizerSuperView)
|
||||
menuController.setMenuVisible(true, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
return (action == #selector(UIResponderStandardEditActions.copy(_:)))
|
||||
|
||||
}
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
UIPasteboard.general.string = text
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
static func commaSeparatedStringFrom(elements: [String]) -> String {
|
||||
return elements.joined(separator: ",")
|
||||
}
|
||||
|
||||
func commaSeparatedToArray() -> [String] {
|
||||
return components(separatedBy: .whitespaces)
|
||||
.joined()
|
||||
.split(separator: ",")
|
||||
.map(String.init)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
func isBase64() -> Bool {
|
||||
let base64Predicate = NSPredicate(format: "SELF MATCHES %@", "^[a-zA-Z0-9+/]{43}=$")
|
||||
return base64Predicate.evaluate(with: self)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>conf</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array>
|
||||
<string>icon_20pt</string>
|
||||
<string>icon_20pt@3x</string>
|
||||
<string>icon_60pt@3x</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>WireGuard configuration</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.wireguard.config.quick</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>zip</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>zip</string>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeMIMETypes</key>
|
||||
<array>
|
||||
<string>application/zip</string>
|
||||
<string>application/x-zip</string>
|
||||
<string>application/x-zip-compressed</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>WireGuard Config Zip</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>ZIP </string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.pkware.zip-archive</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>WireGuardGoVersion</key>
|
||||
<string>unknown</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>auto-generated</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<false/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Camera is used to scan QR codes</string>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>WireGuard configuration</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array>
|
||||
<string>icon_20pt@3x</string>
|
||||
<string>icon_60pt@3x</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.wireguard.config.quick</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<string>conf</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>com.pkware.zip-archive</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>WireGuard configuration</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array>
|
||||
<string>icon_20pt@3x</string>
|
||||
<string>icon_60pt@3x</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.wireguard.config.quick</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<string>conf</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>WireGuard configurations</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array>
|
||||
<string>icon_20pt.png</string>
|
||||
<string>icon_60pt@3x.png</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.wireguard.config.quick.confs</string>
|
||||
<key>public.filename-extension</key>
|
||||
<string>zip</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import os.log
|
||||
|
||||
struct Log {
|
||||
static var general = OSLog(subsystem: "com.wireguard.ios", category: "general")
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Attribute {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case address = "Address"
|
||||
case allowedIPs = "AllowedIPs"
|
||||
case dns = "DNS"
|
||||
case endpoint = "Endpoint"
|
||||
case listenPort = "ListenPort"
|
||||
case mtu = "MTU"
|
||||
case persistentKeepalive = "PersistentKeepalive"
|
||||
case presharedKey = "PresharedKey"
|
||||
case privateKey = "PrivateKey"
|
||||
case publicKey = "PublicKey"
|
||||
}
|
||||
|
||||
private static let separatorPattern = (try? NSRegularExpression(pattern: "\\s|=", options: []))!
|
||||
|
||||
let line: String
|
||||
let key: Key
|
||||
let stringValue: String
|
||||
var arrayValue: [String] {
|
||||
return stringValue.commaSeparatedToArray()
|
||||
}
|
||||
|
||||
static func match(line: String) -> Attribute? {
|
||||
guard let equalsIndex = line.firstIndex(of: "=") else { return nil }
|
||||
let keyString = line[..<equalsIndex].trimmingCharacters(in: .whitespaces)
|
||||
let value = line[line.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespaces)
|
||||
guard let key = Key.allCases.first(where: { $0.rawValue.lowercased() == keyString.lowercased() }) else { return nil }
|
||||
|
||||
return Attribute(line: line, key: key, stringValue: value)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@objc(Interface)
|
||||
public class Interface: NSManagedObject {
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension Interface {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<Interface> {
|
||||
return NSFetchRequest<Interface>(entityName: "Interface")
|
||||
}
|
||||
|
||||
@NSManaged public var addresses: String?
|
||||
@NSManaged public var listenPort: Int16
|
||||
@NSManaged public var privateKey: String?
|
||||
@NSManaged public var mtu: Int32
|
||||
@NSManaged public var dns: String?
|
||||
@NSManaged public var tunnel: Tunnel?
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for adresses
|
||||
extension Interface {
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Interface {
|
||||
|
||||
var publicKey: String? {
|
||||
if let privateKeyString = privateKey, let privateKey = Data(base64Encoded: privateKeyString) {
|
||||
var publicKey = Data(count: 32)
|
||||
privateKey.withUnsafeBytes({ (privateKeyBytes) -> Void in
|
||||
publicKey.withUnsafeMutableBytes({ (mutableBytes) -> Void in
|
||||
curve25519_derive_public_key(mutableBytes, privateKeyBytes)
|
||||
})
|
||||
})
|
||||
return publicKey.base64EncodedString()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func validate() throws {
|
||||
guard let privateKey = privateKey, !privateKey.isEmpty else {
|
||||
throw InterfaceValidationError.emptyPrivateKey
|
||||
}
|
||||
|
||||
guard privateKey.isBase64() else {
|
||||
throw InterfaceValidationError.invalidPrivateKey
|
||||
}
|
||||
|
||||
try addresses?.commaSeparatedToArray().forEach { address in
|
||||
do {
|
||||
try _ = CIDRAddress(stringRepresentation: address)
|
||||
} catch {
|
||||
throw InterfaceValidationError.invalidAddress(cause: error)
|
||||
}
|
||||
}
|
||||
|
||||
try dns?.commaSeparatedToArray().forEach { address in
|
||||
do {
|
||||
try _ = Endpoint(endpointString: address, needsPort: false)
|
||||
} catch {
|
||||
throw InterfaceValidationError.invalidDNSServer(cause: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parse(attribute: Attribute) throws {
|
||||
switch attribute.key {
|
||||
case .address:
|
||||
addresses = attribute.stringValue
|
||||
case .dns:
|
||||
dns = attribute.stringValue
|
||||
case .listenPort:
|
||||
if let port = Int16(attribute.stringValue) {
|
||||
listenPort = port
|
||||
}
|
||||
case .mtu:
|
||||
if let mtu = Int32(attribute.stringValue) {
|
||||
self.mtu = mtu
|
||||
}
|
||||
case .privateKey:
|
||||
privateKey = attribute.stringValue
|
||||
default:
|
||||
throw TunnelParseError.invalidLine(attribute.line)
|
||||
}
|
||||
}
|
||||
|
||||
func export() -> String {
|
||||
var exportString = "[Interface]\n"
|
||||
if let privateKey = privateKey {
|
||||
exportString.append("PrivateKey=\(privateKey)\n")
|
||||
}
|
||||
if let addresses = addresses {
|
||||
exportString.append("Address=\(addresses)\n")
|
||||
}
|
||||
if let dns = dns {
|
||||
exportString.append("DNS=\(dns)\n")
|
||||
}
|
||||
if mtu > 0 {
|
||||
exportString.append("MTU=\(mtu)\n")
|
||||
}
|
||||
if listenPort > 0 {
|
||||
exportString.append("ListenPort=\(listenPort)\n")
|
||||
}
|
||||
|
||||
exportString.append("\n")
|
||||
|
||||
return exportString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum InterfaceValidationError: Error {
|
||||
case emptyPrivateKey
|
||||
case invalidPrivateKey
|
||||
case invalidAddress(cause: Error)
|
||||
case invalidDNSServer(cause: Error)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@objc(Peer)
|
||||
public class Peer: NSManagedObject {
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension Peer {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<Peer> {
|
||||
return NSFetchRequest<Peer>(entityName: "Peer")
|
||||
}
|
||||
|
||||
@NSManaged public var publicKey: String?
|
||||
@NSManaged public var presharedKey: String?
|
||||
@NSManaged public var allowedIPs: String?
|
||||
@NSManaged public var endpoint: String?
|
||||
@NSManaged public var persistentKeepalive: Int32
|
||||
@NSManaged public var tunnel: Tunnel?
|
||||
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Peer {
|
||||
|
||||
func validate() throws {
|
||||
guard let publicKey = publicKey, !publicKey.isEmpty else {
|
||||
throw PeerValidationError.emptyPublicKey
|
||||
}
|
||||
|
||||
guard publicKey.isBase64() else {
|
||||
throw PeerValidationError.invalidPublicKey
|
||||
}
|
||||
|
||||
guard let allowedIPs = allowedIPs, !allowedIPs.isEmpty else {
|
||||
throw PeerValidationError.nilAllowedIps
|
||||
}
|
||||
|
||||
try allowedIPs.commaSeparatedToArray().forEach { address in
|
||||
do {
|
||||
try _ = CIDRAddress(stringRepresentation: address)
|
||||
} catch {
|
||||
throw PeerValidationError.invalidAllowedIPs(cause: error)
|
||||
}
|
||||
}
|
||||
|
||||
if let endpoint = endpoint {
|
||||
do {
|
||||
try _ = Endpoint(endpointString: endpoint)
|
||||
} catch {
|
||||
throw PeerValidationError.invalidEndpoint(cause: error)
|
||||
}
|
||||
}
|
||||
|
||||
guard persistentKeepalive >= 0, persistentKeepalive <= 65535 else {
|
||||
throw PeerValidationError.invalidPersistedKeepAlive
|
||||
}
|
||||
}
|
||||
|
||||
func parse(attribute: Attribute) throws {
|
||||
switch attribute.key {
|
||||
case .allowedIPs:
|
||||
allowedIPs = attribute.stringValue
|
||||
case .endpoint:
|
||||
endpoint = attribute.stringValue
|
||||
case .persistentKeepalive:
|
||||
if let keepAlive = Int32(attribute.stringValue) {
|
||||
persistentKeepalive = keepAlive
|
||||
}
|
||||
case .presharedKey:
|
||||
presharedKey = attribute.stringValue
|
||||
case .publicKey:
|
||||
publicKey = attribute.stringValue
|
||||
default:
|
||||
throw TunnelParseError.invalidLine(attribute.line)
|
||||
}
|
||||
}
|
||||
|
||||
func export() -> String {
|
||||
var exportString = "[Peer]\n"
|
||||
if let publicKey = publicKey {
|
||||
exportString.append("PublicKey=\(publicKey)\n")
|
||||
}
|
||||
if let presharedKey = presharedKey {
|
||||
exportString.append("PresharedKey=\(presharedKey)\n")
|
||||
}
|
||||
if let allowedIPs = allowedIPs {
|
||||
exportString.append("AllowedIPs=\(allowedIPs)\n")
|
||||
}
|
||||
if let endpoint = endpoint {
|
||||
exportString.append("Endpoint=\(endpoint)\n")
|
||||
}
|
||||
if persistentKeepalive > 0 {
|
||||
exportString.append("PersistentKeepalive=\(persistentKeepalive)\n")
|
||||
}
|
||||
|
||||
exportString.append("\n")
|
||||
|
||||
return exportString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum PeerValidationError: Error {
|
||||
case emptyPublicKey
|
||||
case invalidPublicKey
|
||||
case nilAllowedIps
|
||||
case invalidAllowedIPs(cause: Error)
|
||||
case invalidEndpoint(cause: Error)
|
||||
case invalidPersistedKeepAlive
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@objc(Tunnel)
|
||||
public class Tunnel: NSManagedObject {
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension Tunnel {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<Tunnel> {
|
||||
return NSFetchRequest<Tunnel>(entityName: "Tunnel")
|
||||
}
|
||||
|
||||
@NSManaged public var tunnelIdentifier: String?
|
||||
@NSManaged public var title: String?
|
||||
@NSManaged public var interface: Interface?
|
||||
@NSManaged public var peers: NSOrderedSet?
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for peers
|
||||
extension Tunnel {
|
||||
|
||||
@objc(insertObject:inPeersAtIndex:)
|
||||
@NSManaged public func insertIntoPeers(_ value: Peer, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromPeersAtIndex:)
|
||||
@NSManaged public func removeFromPeers(at idx: Int)
|
||||
|
||||
@objc(insertPeers:atIndexes:)
|
||||
@NSManaged public func insertIntoPeers(_ values: [Peer], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removePeersAtIndexes:)
|
||||
@NSManaged public func removeFromPeers(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInPeersAtIndex:withObject:)
|
||||
@NSManaged public func replacePeers(at idx: Int, with value: Peer)
|
||||
|
||||
@objc(replacePeersAtIndexes:withPeers:)
|
||||
@NSManaged public func replacePeers(at indexes: NSIndexSet, with values: [Peer])
|
||||
|
||||
@objc(addPeersObject:)
|
||||
@NSManaged public func addToPeers(_ value: Peer)
|
||||
|
||||
@objc(removePeersObject:)
|
||||
@NSManaged public func removeFromPeers(_ value: Peer)
|
||||
|
||||
@objc(addPeers:)
|
||||
@NSManaged public func addToPeers(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removePeers:)
|
||||
@NSManaged public func removeFromPeers(_ values: NSOrderedSet)
|
||||
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension Tunnel {
|
||||
public func generateProviderConfiguration() -> [String: Any] {
|
||||
var providerConfiguration = [String: Any]()
|
||||
|
||||
providerConfiguration[PCKeys.title.rawValue] = self.title
|
||||
providerConfiguration[PCKeys.tunnelIdentifier.rawValue] = self.tunnelIdentifier
|
||||
providerConfiguration[PCKeys.endpoints.rawValue] = peers?.array.compactMap {($0 as? Peer)?.endpoint}.joined(separator: ", ")
|
||||
providerConfiguration[PCKeys.dns.rawValue] = interface?.dns
|
||||
providerConfiguration[PCKeys.addresses.rawValue] = interface?.addresses
|
||||
if let mtu = interface?.mtu, mtu > 0 {
|
||||
providerConfiguration[PCKeys.mtu.rawValue] = NSNumber(value: mtu)
|
||||
}
|
||||
|
||||
var settingsString = "replace_peers=true\n"
|
||||
if let interface = interface {
|
||||
settingsString += generateInterfaceProviderConfiguration(interface)
|
||||
}
|
||||
|
||||
if let peers = peers?.array as? [Peer] {
|
||||
peers.forEach {
|
||||
settingsString += generatePeerProviderConfiguration($0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
providerConfiguration["settings"] = settingsString
|
||||
|
||||
return providerConfiguration
|
||||
}
|
||||
|
||||
private func generateInterfaceProviderConfiguration(_ interface: Interface) -> String {
|
||||
var settingsString = ""
|
||||
|
||||
if let hexPrivateKey = base64KeyToHex(interface.privateKey) {
|
||||
settingsString += "private_key=\(hexPrivateKey)\n"
|
||||
}
|
||||
if interface.listenPort > 0 {
|
||||
settingsString += "listen_port=\(interface.listenPort)\n"
|
||||
}
|
||||
if interface.mtu > 0 {
|
||||
settingsString += "mtu=\(interface.mtu)\n"
|
||||
}
|
||||
|
||||
return settingsString
|
||||
}
|
||||
|
||||
private func generatePeerProviderConfiguration(_ peer: Peer) -> String {
|
||||
var settingsString = ""
|
||||
|
||||
if let hexPublicKey = base64KeyToHex(peer.publicKey) {
|
||||
settingsString += "public_key=\(hexPublicKey)\n"
|
||||
}
|
||||
if let presharedKey = peer.presharedKey {
|
||||
settingsString += "preshared_key=\(presharedKey)\n"
|
||||
}
|
||||
if let endpoint = peer.endpoint {
|
||||
settingsString += "endpoint=\(endpoint)\n"
|
||||
}
|
||||
if peer.persistentKeepalive > 0 {
|
||||
settingsString += "persistent_keepalive_interval=\(peer.persistentKeepalive)\n"
|
||||
}
|
||||
if let allowedIPs = peer.allowedIPs?.commaSeparatedToArray() {
|
||||
allowedIPs.forEach {
|
||||
settingsString += "allowed_ip=\($0.trimmingCharacters(in: .whitespaces))\n"
|
||||
}
|
||||
}
|
||||
|
||||
return settingsString
|
||||
}
|
||||
|
||||
func validate() throws {
|
||||
let nameRegex = "[a-zA-Z0-9_=+.-]{1,15}"
|
||||
let nameTest = NSPredicate(format: "SELF MATCHES %@", nameRegex)
|
||||
guard let title = title, nameTest.evaluate(with: title) else {
|
||||
throw TunnelValidationError.invalidTitle
|
||||
}
|
||||
|
||||
let fetchRequest: NSFetchRequest<Tunnel> = Tunnel.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "title == %@", title)
|
||||
guard (try? managedObjectContext?.count(for: fetchRequest)) == 1 else {
|
||||
throw TunnelValidationError.titleExists
|
||||
}
|
||||
|
||||
guard let interface = interface else {
|
||||
throw TunnelValidationError.nilInterface
|
||||
}
|
||||
|
||||
try interface.validate()
|
||||
|
||||
guard let peers = peers else {
|
||||
throw TunnelValidationError.nilPeers
|
||||
}
|
||||
|
||||
try peers.forEach {
|
||||
guard let peer = $0 as? Peer else {
|
||||
throw TunnelValidationError.invalidPeer
|
||||
}
|
||||
|
||||
try peer.validate()
|
||||
}
|
||||
}
|
||||
|
||||
static func fromConfig(_ text: String, context: NSManagedObjectContext) throws -> Tunnel {
|
||||
let lines = text.split(separator: "\n")
|
||||
|
||||
var currentPeer: Peer?
|
||||
var isInInterfaceSection = false
|
||||
|
||||
var tunnel: Tunnel!
|
||||
context.performAndWait {
|
||||
tunnel = Tunnel(context: context)
|
||||
tunnel.interface = Interface(context: context)
|
||||
}
|
||||
tunnel.tunnelIdentifier = UUID().uuidString
|
||||
|
||||
for line in lines {
|
||||
var trimmedLine: String
|
||||
if let commentRange = line.range(of: "#") {
|
||||
trimmedLine = String(line[..<commentRange.lowerBound])
|
||||
} else {
|
||||
trimmedLine = String(line)
|
||||
}
|
||||
|
||||
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
|
||||
|
||||
guard trimmedLine.count > 0 else { continue }
|
||||
|
||||
if "[interface]" == line.lowercased() {
|
||||
currentPeer = nil
|
||||
isInInterfaceSection = true
|
||||
} else if "[peer]" == line.lowercased() {
|
||||
context.performAndWait { currentPeer = Peer(context: context) }
|
||||
tunnel.insertIntoPeers(currentPeer!, at: tunnel.peers?.count ?? 0)
|
||||
isInInterfaceSection = false
|
||||
} else if isInInterfaceSection, let attribute = Attribute.match(line: String(line)) {
|
||||
try tunnel.interface!.parse(attribute: attribute)
|
||||
} else if let currentPeer = currentPeer, let attribute = Attribute.match(line: String(line)) {
|
||||
try currentPeer.parse(attribute: attribute)
|
||||
} else {
|
||||
throw TunnelParseError.invalidLine(String(line))
|
||||
}
|
||||
}
|
||||
|
||||
if !isInInterfaceSection && currentPeer == nil {
|
||||
throw TunnelParseError.noConfigInfo
|
||||
}
|
||||
|
||||
return tunnel
|
||||
}
|
||||
|
||||
func export() -> String {
|
||||
var exportString = ""
|
||||
if let interfaceExport = self.interface?.export() {
|
||||
exportString.append(interfaceExport)
|
||||
}
|
||||
|
||||
if let peers = peers?.array as? [Peer] {
|
||||
peers.forEach {
|
||||
exportString.append($0.export())
|
||||
}
|
||||
}
|
||||
|
||||
return exportString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func base64KeyToHex(_ base64: String?) -> String? {
|
||||
guard let base64 = base64 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard base64.count == 44 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard base64.last == "=" else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let keyData = Data(base64Encoded: base64) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard keyData.count == 32 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hexKey = keyData.reduce("") {$0 + String(format: "%02x", $1)}
|
||||
|
||||
return hexKey
|
||||
}
|
||||
|
||||
enum TunnelValidationError: Error {
|
||||
case invalidTitle
|
||||
case titleExists
|
||||
case nilInterface
|
||||
case nilPeers
|
||||
case invalidPeer
|
||||
}
|
||||
|
||||
enum TunnelParseError: Error {
|
||||
case invalidLine(_ line: String)
|
||||
case noConfigInfo
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14315.18" systemVersion="18A391" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Interface" representedClassName="Interface" syncable="YES">
|
||||
<attribute name="addresses" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="dns" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="listenPort" optional="YES" attributeType="Integer 16" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="mtu" optional="YES" attributeType="Integer 32" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="privateKey" attributeType="String" syncable="YES"/>
|
||||
<relationship name="tunnel" maxCount="1" deletionRule="Nullify" destinationEntity="Tunnel" inverseName="interface" inverseEntity="Tunnel" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Peer" representedClassName="Peer" syncable="YES">
|
||||
<attribute name="allowedIPs" attributeType="String" syncable="YES"/>
|
||||
<attribute name="endpoint" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="persistentKeepalive" attributeType="Integer 32" minValueString="0" maxValueString="65535" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="presharedKey" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="publicKey" attributeType="String" syncable="YES"/>
|
||||
<relationship name="tunnel" maxCount="1" deletionRule="Nullify" destinationEntity="Tunnel" inverseName="peers" inverseEntity="Tunnel" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Tunnel" representedClassName="Tunnel" syncable="YES">
|
||||
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="tunnelIdentifier" attributeType="String" syncable="YES"/>
|
||||
<relationship name="interface" maxCount="1" deletionRule="Cascade" destinationEntity="Interface" inverseName="tunnel" inverseEntity="Interface" syncable="YES"/>
|
||||
<relationship name="peers" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="Peer" inverseName="tunnel" inverseEntity="Peer" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Interface" positionX="-54" positionY="-9" width="128" height="135"/>
|
||||
<element name="Peer" positionX="-36" positionY="9" width="128" height="135"/>
|
||||
<element name="Tunnel" positionX="160" positionY="192" width="128" height="105"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public protocol Identifyable: class {
|
||||
static var identifier: String { get }
|
||||
}
|
||||
|
||||
public extension Identifyable {
|
||||
static var identifier: String {
|
||||
return String(describing: Self.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIStoryboard {
|
||||
|
||||
public func instantiateViewController<T: Identifyable>(type: T.Type) -> T where T: UIViewController {
|
||||
return self.instantiateViewController(withIdentifier: type.identifier) as! T // swiftlint:disable:this force_cast
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import CoreData
|
||||
import UIKit
|
||||
|
||||
protocol QRScanViewControllerDelegate: class {
|
||||
func didSave(tunnel: Tunnel, qrScanViewController: QRScanViewController)
|
||||
func didCancel(qrScanViewController: QRScanViewController)
|
||||
}
|
||||
|
||||
class QRScanViewController: UIViewController {
|
||||
|
||||
private var viewContext: NSManagedObjectContext!
|
||||
private weak var delegate: QRScanViewControllerDelegate?
|
||||
var captureSession: AVCaptureSession? = AVCaptureSession()
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
var previewLayer: AVCaptureVideoPreviewLayer!
|
||||
|
||||
func configure(context: NSManagedObjectContext, delegate: QRScanViewControllerDelegate? = nil) {
|
||||
viewContext = context
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
|
||||
let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
|
||||
let captureSession = captureSession,
|
||||
captureSession.canAddInput(videoInput),
|
||||
captureSession.canAddOutput(metadataOutput) else {
|
||||
scanDidEncounterError(title: "Scanning Not Supported", message: "This device does not have the ability to scan QR codes.")
|
||||
return
|
||||
}
|
||||
|
||||
captureSession.addInput(videoInput)
|
||||
captureSession.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
|
||||
metadataOutput.metadataObjectTypes = [.qr]
|
||||
|
||||
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||
previewLayer.frame = view.layer.bounds
|
||||
previewLayer.videoGravity = .resizeAspectFill
|
||||
view.layer.insertSublayer(previewLayer, at: 0)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if captureSession?.isRunning == false {
|
||||
captureSession?.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
if captureSession?.isRunning == true {
|
||||
captureSession?.stopRunning()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
if let connection = previewLayer.connection {
|
||||
|
||||
let currentDevice: UIDevice = UIDevice.current
|
||||
|
||||
let orientation: UIDeviceOrientation = currentDevice.orientation
|
||||
|
||||
let previewLayerConnection: AVCaptureConnection = connection
|
||||
|
||||
if previewLayerConnection.isVideoOrientationSupported {
|
||||
|
||||
switch orientation {
|
||||
case .portrait:
|
||||
previewLayerConnection.videoOrientation = .portrait
|
||||
case .landscapeRight:
|
||||
previewLayerConnection.videoOrientation = .landscapeLeft
|
||||
case .landscapeLeft:
|
||||
previewLayerConnection.videoOrientation = .landscapeRight
|
||||
case .portraitUpsideDown:
|
||||
previewLayerConnection.videoOrientation = .portraitUpsideDown
|
||||
default:
|
||||
previewLayerConnection.videoOrientation = .portrait
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previewLayer.frame = self.view.bounds
|
||||
}
|
||||
|
||||
func scanDidComplete(withCode code: String) {
|
||||
do {
|
||||
let tunnel = try Tunnel.fromConfig(code, context: viewContext)
|
||||
let alert = UIAlertController(title: NSLocalizedString("Enter a title for new tunnel", comment: ""), message: nil, preferredStyle: .alert)
|
||||
alert.addTextField(configurationHandler: nil)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: { [weak self] _ in
|
||||
self?.delegate?.didCancel(qrScanViewController: self!)
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Save", comment: ""), style: .default, handler: { [weak self] _ in
|
||||
do {
|
||||
tunnel.title = alert.textFields?[0].text
|
||||
try self?.viewContext.save()
|
||||
self?.delegate?.didSave(tunnel: tunnel, qrScanViewController: self!)
|
||||
} catch {
|
||||
self?.scanDidEncounterError(title: "Invalid Code", message: "The scanned code is not a valid WireGuard config file.")
|
||||
}
|
||||
}))
|
||||
|
||||
self.present(alert, animated: true)
|
||||
|
||||
} catch {
|
||||
scanDidEncounterError(title: "Invalid Code", message: "The scanned code is not a valid WireGuard config file.")
|
||||
}
|
||||
}
|
||||
|
||||
func scanDidEncounterError(title: String, message: String) {
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self] _ in
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
}))
|
||||
present(alertController, animated: true)
|
||||
captureSession = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension QRScanViewController: AVCaptureMetadataOutputObjectsDelegate {
|
||||
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
||||
captureSession?.stopRunning()
|
||||
|
||||
guard let metadataObject = metadataObjects.first,
|
||||
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
|
||||
let stringValue = readableObject.stringValue else {
|
||||
scanDidEncounterError(title: "Invalid Code", message: "The scanned code could not be read.")
|
||||
return
|
||||
}
|
||||
|
||||
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
|
||||
scanDidComplete(withCode: stringValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension QRScanViewController: Identifyable {}
|
|
@ -1,77 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import PromiseKit
|
||||
|
||||
enum GoVersionError: Error {
|
||||
case noDelegate
|
||||
}
|
||||
|
||||
protocol SettingsTableViewControllerDelegate: class {
|
||||
func exportTunnels(settingsTableViewController: SettingsTableViewController, sourceView: UIView)
|
||||
}
|
||||
|
||||
class SettingsTableViewController: UITableViewController {
|
||||
|
||||
weak var delegate: SettingsTableViewControllerDelegate?
|
||||
@IBOutlet weak var versionInfoCell: UITableViewCell!
|
||||
@IBOutlet weak var goVersionInfoCell: UITableViewCell!
|
||||
@IBOutlet weak var exportCell: UITableViewCell!
|
||||
|
||||
@IBOutlet weak var versionInfoLabel: UILabel!
|
||||
@IBOutlet weak var goVersionInfoLabel: UILabel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
versionInfoLabel.text = versionInformation
|
||||
goVersionInfoLabel.text = goVersionInformation
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if let cell = tableView.cellForRow(at: indexPath) {
|
||||
switch cell {
|
||||
case versionInfoCell, goVersionInfoCell:
|
||||
UIPasteboard.general.string = ["WireGuard for iOS:", versionInformation, "Go userspace backend:", goVersionInfoLabel.text ?? ""].joined(separator: "\n")
|
||||
showCopyConfirmation()
|
||||
case exportCell:
|
||||
delegate?.exportTunnels(settingsTableViewController: self, sourceView: exportCell)
|
||||
default:
|
||||
()
|
||||
}
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
var versionInformation: String {
|
||||
var versionElements: [String] = []
|
||||
if let appBuildNumber = Bundle.main.infoDictionary!["CFBundleVersion"] as? String, let appVersion = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String {
|
||||
versionElements.append(appVersion)
|
||||
versionElements.append("(\(appBuildNumber))")
|
||||
}
|
||||
|
||||
return versionElements.joined(separator: " ")
|
||||
}
|
||||
|
||||
var goVersionInformation: String {
|
||||
return Bundle.main.infoDictionary!["WireGuardGoVersion"] as? String ?? "Unknown!!!"
|
||||
}
|
||||
|
||||
private func showNotEnabledAlert() {
|
||||
let notEnabledAlertController = UIAlertController(title: NSLocalizedString("Go version", comment: ""), message: NSLocalizedString("Enable a WireGuard config by connecting or by selecting one in the VPN section of the device Settings app.", comment: ""), preferredStyle: .alert)
|
||||
notEnabledAlertController.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: "Generic OK button"), style: .default, handler: nil))
|
||||
|
||||
present(notEnabledAlertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func showCopyConfirmation() {
|
||||
let confirmationAlertController = UIAlertController(title: NSLocalizedString("Copied version information", comment: ""), message: UIPasteboard.general.string, preferredStyle: .alert)
|
||||
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: "Generic OK button"), style: .default, handler: nil))
|
||||
|
||||
present(confirmationAlertController, animated: true, completion: nil)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsTableViewController: Identifyable {}
|
|
@ -1,300 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import BNRCoreDataStack
|
||||
import PromiseKit
|
||||
|
||||
protocol TunnelConfigurationTableViewControllerDelegate: class {
|
||||
func didSave(tunnel: Tunnel, tunnelConfigurationTableViewController: TunnelConfigurationTableViewController)
|
||||
func showSettings()
|
||||
}
|
||||
|
||||
class TunnelConfigurationTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var saveButton: UIBarButtonItem!
|
||||
|
||||
private var viewContext: NSManagedObjectContext!
|
||||
private weak var delegate: TunnelConfigurationTableViewControllerDelegate?
|
||||
private var tunnel: Tunnel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Get rid of seperator lines in table.
|
||||
tableView.tableFooterView = UIView(frame: CGRect.zero)
|
||||
}
|
||||
|
||||
func configure(context: NSManagedObjectContext, delegate: TunnelConfigurationTableViewControllerDelegate? = nil, tunnel: Tunnel? = nil) {
|
||||
viewContext = context
|
||||
self.delegate = delegate
|
||||
self.tunnel = tunnel ?? generateNewTunnelConfig()
|
||||
}
|
||||
|
||||
private func generateNewTunnelConfig() -> Tunnel {
|
||||
var tunnel: Tunnel! = nil
|
||||
|
||||
viewContext.performAndWait {
|
||||
tunnel = Tunnel(context: viewContext)
|
||||
tunnel.tunnelIdentifier = UUID().uuidString
|
||||
|
||||
let interface = Interface(context: viewContext)
|
||||
|
||||
tunnel.interface = interface
|
||||
}
|
||||
return tunnel
|
||||
}
|
||||
|
||||
@IBAction func showSettings(_ sender: Any) {
|
||||
delegate?.showSettings()
|
||||
}
|
||||
|
||||
@IBAction func addPeer(_ sender: Any) {
|
||||
if let moc = tunnel.managedObjectContext {
|
||||
tableView.beginUpdates()
|
||||
let insertedAt = IndexPath(row: tunnel.peers?.count ?? 0, section: 1)
|
||||
tableView.insertRows(at: [insertedAt], with: .automatic)
|
||||
|
||||
let peer = Peer(context: moc)
|
||||
tunnel.addToPeers(peer)
|
||||
|
||||
tableView.endUpdates()
|
||||
tableView.scrollToRow(at: insertedAt, at: .middle, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 3
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 1:
|
||||
return tunnel?.peers?.count ?? 1
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
let cell = tableView.dequeueReusableCell(type: InterfaceTableViewCell.self, for: indexPath)
|
||||
cell.model = tunnel.interface
|
||||
cell.delegate = self
|
||||
return cell
|
||||
case 1:
|
||||
let cell = tableView.dequeueReusableCell(type: PeerTableViewCell.self, for: indexPath)
|
||||
if let peer = tunnel.peers?.object(at: indexPath.row) as? Peer {
|
||||
cell.peer = peer
|
||||
} else {
|
||||
let peer = Peer(context: tunnel.managedObjectContext!)
|
||||
tunnel.addToPeers(peer)
|
||||
cell.peer = peer
|
||||
}
|
||||
cell.delegate = self
|
||||
return cell
|
||||
default:
|
||||
let cell = tableView.dequeueReusableCell(type: AddPeerTableViewCell.self, for: indexPath)
|
||||
cell.tunnel = tunnel
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func saveTunnelConfiguration(_ sender: Any) {
|
||||
Promise<Void>(resolver: { (seal) in
|
||||
do {
|
||||
try tunnel.validate()
|
||||
} catch {
|
||||
seal.reject(error)
|
||||
return
|
||||
}
|
||||
|
||||
viewContext.perform({
|
||||
self.viewContext.saveContext({ (result) in
|
||||
switch result {
|
||||
case .success:
|
||||
seal.fulfill(())
|
||||
case .failure(let error):
|
||||
seal.reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}).then { () -> Promise<Void> in
|
||||
self.delegate?.didSave(tunnel: self.tunnel, tunnelConfigurationTableViewController: self)
|
||||
return Promise.value(())
|
||||
}.catch { error in
|
||||
let alert = UIAlertController(title: "Error", message: "\(error)", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelConfigurationTableViewController: PeerTableViewCellDelegate {
|
||||
func delete(peer: Peer) {
|
||||
if let moc = tunnel.managedObjectContext {
|
||||
tableView.beginUpdates()
|
||||
let deletedAt = IndexPath(row: tunnel.peers?.index(of: peer) ?? 0, section: 1)
|
||||
tableView.deleteRows(at: [deletedAt], with: .automatic)
|
||||
tunnel.removeFromPeers(peer)
|
||||
moc.delete(peer)
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelConfigurationTableViewController: InterfaceTableViewCellDelegate {
|
||||
func generateKeys() {
|
||||
if let moc = tunnel.managedObjectContext {
|
||||
moc.perform {
|
||||
var privateKey = Data(count: 32)
|
||||
privateKey.withUnsafeMutableBytes { (mutableBytes) -> Void in
|
||||
curve25519_generate_private_key(mutableBytes)
|
||||
}
|
||||
|
||||
self.tunnel.interface?.privateKey = privateKey.base64EncodedString()
|
||||
}
|
||||
}
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
protocol InterfaceTableViewCellDelegate: class {
|
||||
func generateKeys()
|
||||
}
|
||||
|
||||
class InterfaceTableViewCell: UITableViewCell {
|
||||
var model: Interface! {
|
||||
didSet {
|
||||
nameField.text = model.tunnel?.title
|
||||
addressesField.text = model.addresses
|
||||
privateKeyField.text = model.privateKey
|
||||
publicKeyField.text = model.publicKey
|
||||
|
||||
listenPortField.text = model.listenPort > 0 ? String(model.listenPort) : nil
|
||||
dnsField.text = model.dns
|
||||
mtuField.text = model.mtu > 0 ? String(model.mtu) : nil
|
||||
}
|
||||
}
|
||||
|
||||
weak var delegate: InterfaceTableViewCellDelegate?
|
||||
|
||||
@IBOutlet weak var nameField: UITextField!
|
||||
@IBOutlet weak var addressesField: UITextField!
|
||||
@IBOutlet weak var privateKeyField: UITextField!
|
||||
@IBOutlet weak var publicKeyField: CopyableLabel!
|
||||
@IBOutlet weak var listenPortField: UITextField!
|
||||
@IBOutlet weak var dnsField: UITextField!
|
||||
@IBOutlet weak var mtuField: UITextField!
|
||||
|
||||
@IBAction func generateTapped(_ sender: Any) {
|
||||
delegate?.generateKeys()
|
||||
}
|
||||
}
|
||||
|
||||
extension InterfaceTableViewCell: UITextFieldDelegate {
|
||||
|
||||
@IBAction
|
||||
func textfieldDidChange(_ sender: UITextField) {
|
||||
let string = sender.text
|
||||
|
||||
if sender == nameField {
|
||||
model.tunnel?.title = string
|
||||
} else if sender == privateKeyField {
|
||||
model.privateKey = string
|
||||
publicKeyField.text = model.publicKey
|
||||
} else if sender == addressesField {
|
||||
model.addresses = string
|
||||
} else if sender == listenPortField {
|
||||
if let string = string, let port = Int16(string) {
|
||||
model.listenPort = port
|
||||
} else {
|
||||
model.listenPort = 0
|
||||
}
|
||||
} else if sender == dnsField {
|
||||
model.dns = string
|
||||
} else if sender == mtuField {
|
||||
if let string = string, let mtu = Int32(string) {
|
||||
model.mtu = mtu
|
||||
} else {
|
||||
model.mtu = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
if textField == addressesField {
|
||||
if let addresses = model.addresses?.commaSeparatedToArray() {
|
||||
textField.text = addresses.compactMap { try? CIDRAddress(stringRepresentation: $0 ) }.compactMap { $0?.stringRepresentation }.joined(separator: ", ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol PeerTableViewCellDelegate: class {
|
||||
func delete(peer: Peer)
|
||||
}
|
||||
|
||||
class PeerTableViewCell: UITableViewCell {
|
||||
var peer: Peer! {
|
||||
didSet {
|
||||
publicKeyField.text = peer.publicKey
|
||||
preSharedKeyField.text = peer.presharedKey
|
||||
allowedIpsField.text = peer.allowedIPs
|
||||
endpointField.text = peer.endpoint
|
||||
persistentKeepaliveField.text = peer.persistentKeepalive > 0 ? String(peer.persistentKeepalive) : nil
|
||||
}
|
||||
}
|
||||
weak var delegate: PeerTableViewCellDelegate?
|
||||
|
||||
@IBOutlet weak var publicKeyField: UITextField!
|
||||
@IBOutlet weak var preSharedKeyField: UITextField!
|
||||
@IBOutlet weak var allowedIpsField: UITextField!
|
||||
@IBOutlet weak var endpointField: UITextField!
|
||||
@IBOutlet weak var persistentKeepaliveField: UITextField!
|
||||
|
||||
@IBAction func deletePeer(_ sender: Any) {
|
||||
delegate?.delete(peer: peer)
|
||||
}
|
||||
}
|
||||
|
||||
extension PeerTableViewCell: UITextFieldDelegate {
|
||||
@IBAction
|
||||
func textfieldDidChange(_ sender: UITextField) {
|
||||
let string = sender.text
|
||||
|
||||
if sender == publicKeyField {
|
||||
peer.publicKey = string
|
||||
} else if sender == preSharedKeyField {
|
||||
peer.presharedKey = string
|
||||
} else if sender == allowedIpsField {
|
||||
peer.allowedIPs = string
|
||||
} else if sender == endpointField {
|
||||
peer.endpoint = string
|
||||
} else if sender == persistentKeepaliveField {
|
||||
if let string = string, let persistentKeepalive = Int32(string) {
|
||||
peer.persistentKeepalive = persistentKeepalive
|
||||
} else {
|
||||
peer.persistentKeepalive = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AddPeerTableViewCell: UITableViewCell {
|
||||
var tunnel: Tunnel!
|
||||
|
||||
@IBAction func addPeer(_ sender: Any) {
|
||||
if let moc = tunnel.managedObjectContext {
|
||||
tunnel.addToPeers(Peer(context: moc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelConfigurationTableViewController: Identifyable {}
|
||||
extension InterfaceTableViewCell: Identifyable {}
|
||||
extension PeerTableViewCell: Identifyable {}
|
||||
extension AddPeerTableViewCell: Identifyable {}
|
|
@ -1,194 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import NetworkExtension
|
||||
|
||||
import BNRCoreDataStack
|
||||
import PromiseKit
|
||||
|
||||
protocol TunnelInfoTableViewControllerDelegate: class {
|
||||
func connect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController)
|
||||
func disconnect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController)
|
||||
func configure(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController)
|
||||
func showSettings()
|
||||
func status(for tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) -> NEVPNStatus
|
||||
}
|
||||
|
||||
class TunnelInfoTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var editButton: UIBarButtonItem!
|
||||
|
||||
private var viewContext: NSManagedObjectContext!
|
||||
private weak var delegate: TunnelInfoTableViewControllerDelegate?
|
||||
private var tunnel: Tunnel!
|
||||
|
||||
func configure(context: NSManagedObjectContext, delegate: TunnelInfoTableViewControllerDelegate? = nil, tunnel: Tunnel) {
|
||||
viewContext = context
|
||||
self.delegate = delegate
|
||||
self.tunnel = tunnel
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Get rid of seperator lines in table.
|
||||
tableView.tableFooterView = UIView(frame: CGRect.zero)
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(VPNStatusDidChange(notification:)),
|
||||
name: .NEVPNStatusDidChange,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super .viewWillAppear(animated)
|
||||
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 1:
|
||||
return tunnel?.peers?.count ?? 0
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
let cell = tableView.dequeueReusableCell(type: InterfaceInfoTableViewCell.self, for: indexPath)
|
||||
cell.delegate = self
|
||||
cell.configure(model: tunnel.interface, status: delegate?.status(for: tunnel, tunnelInfoTableViewController: self) ?? .invalid)
|
||||
return cell
|
||||
default:
|
||||
let cell = tableView.dequeueReusableCell(type: PeerInfoTableViewCell.self, for: indexPath)
|
||||
if let peer = tunnel.peers?.object(at: indexPath.row) as? Peer {
|
||||
cell.peer = peer
|
||||
} else {
|
||||
let peer = Peer(context: tunnel.managedObjectContext!)
|
||||
tunnel.addToPeers(peer)
|
||||
cell.peer = peer
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func showSettings(_ sender: Any) {
|
||||
delegate?.showSettings()
|
||||
}
|
||||
|
||||
@IBAction func editTunnelConfiguration(_ sender: Any) {
|
||||
delegate?.configure(tunnel: self.tunnel, tunnelInfoTableViewController: self)
|
||||
}
|
||||
|
||||
@objc private func VPNStatusDidChange(notification: NSNotification) {
|
||||
guard let session = notification.object as? NETunnelProviderSession else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let prot = session.manager.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let changedTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
guard tunnel.tunnelIdentifier == changedTunnelIdentifier else {
|
||||
return
|
||||
}
|
||||
|
||||
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .none)
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelInfoTableViewController: InterfaceInfoTableViewCellDelegate {
|
||||
func connect(tunnelIdentifier: String) {
|
||||
delegate?.connect(tunnel: tunnel, tunnelInfoTableViewController: self)
|
||||
}
|
||||
|
||||
func disconnect(tunnelIdentifier: String) {
|
||||
delegate?.disconnect(tunnel: tunnel, tunnelInfoTableViewController: self)
|
||||
}
|
||||
}
|
||||
|
||||
protocol InterfaceInfoTableViewCellDelegate: class {
|
||||
func connect(tunnelIdentifier: String)
|
||||
func disconnect(tunnelIdentifier: String)
|
||||
}
|
||||
|
||||
class InterfaceInfoTableViewCell: UITableViewCell {
|
||||
weak var delegate: InterfaceInfoTableViewCellDelegate?
|
||||
private var model: Interface! {
|
||||
didSet {
|
||||
nameField.text = model.tunnel?.title
|
||||
addressesField.text = model.addresses
|
||||
publicKeyField.text = model.publicKey
|
||||
}
|
||||
}
|
||||
|
||||
func configure(model: Interface!, status: NEVPNStatus) {
|
||||
self.model = model
|
||||
|
||||
if status == .connecting || status == .disconnecting || status == .reasserting {
|
||||
activityIndicator.startAnimating()
|
||||
tunnelSwitch.isHidden = true
|
||||
} else {
|
||||
activityIndicator.stopAnimating()
|
||||
tunnelSwitch.isHidden = false
|
||||
}
|
||||
|
||||
tunnelSwitch.isOn = status == .connected
|
||||
tunnelSwitch.onTintColor = status == .invalid || status == .reasserting ? .gray : .green
|
||||
tunnelSwitch.isEnabled = true
|
||||
}
|
||||
|
||||
@IBAction func tunnelSwitchChanged(_ sender: Any) {
|
||||
tunnelSwitch.isEnabled = false
|
||||
|
||||
guard let tunnelIdentifier = model.tunnel?.tunnelIdentifier else {
|
||||
return
|
||||
}
|
||||
|
||||
if tunnelSwitch.isOn {
|
||||
delegate?.connect(tunnelIdentifier: tunnelIdentifier)
|
||||
} else {
|
||||
delegate?.disconnect(tunnelIdentifier: tunnelIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var nameField: UILabel!
|
||||
@IBOutlet weak var addressesField: UILabel!
|
||||
@IBOutlet weak var publicKeyField: CopyableLabel!
|
||||
@IBOutlet weak var tunnelSwitch: UISwitch!
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
}
|
||||
|
||||
class PeerInfoTableViewCell: UITableViewCell {
|
||||
var peer: Peer! {
|
||||
didSet {
|
||||
publicKeyField.text = peer.publicKey
|
||||
allowedIpsField.text = peer.allowedIPs
|
||||
endpointField.text = peer.endpoint
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var publicKeyField: CopyableLabel!
|
||||
@IBOutlet weak var allowedIpsField: UILabel!
|
||||
@IBOutlet weak var endpointField: UILabel!
|
||||
@IBOutlet weak var copiedStatusLabel: UILabel!
|
||||
|
||||
}
|
||||
|
||||
extension TunnelInfoTableViewController: Identifyable {}
|
||||
extension InterfaceInfoTableViewCell: Identifyable {}
|
||||
extension PeerInfoTableViewCell: Identifyable {}
|
|
@ -1,308 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
import CoreData
|
||||
|
||||
import BNRCoreDataStack
|
||||
import NetworkExtension
|
||||
|
||||
protocol TunnelsTableViewControllerDelegate: class {
|
||||
func addProvider(tunnelsTableViewController: TunnelsTableViewController)
|
||||
func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
||||
func disconnect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
||||
func info(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
||||
func delete(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
||||
func status(for tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) -> NEVPNStatus
|
||||
func showSettings()
|
||||
}
|
||||
|
||||
class TunnelsTableViewController: UITableViewController {
|
||||
weak var delegate: TunnelsTableViewControllerDelegate?
|
||||
|
||||
var viewContext: NSManagedObjectContext!
|
||||
|
||||
@IBOutlet var settingsButton: UIBarButtonItem!
|
||||
@IBOutlet var editButton: UIBarButtonItem!
|
||||
@IBOutlet var doneButton: UIBarButtonItem!
|
||||
|
||||
private lazy var fetchedResultsController: FetchedResultsController<Tunnel> = {
|
||||
let fetchRequest = NSFetchRequest<Tunnel>()
|
||||
fetchRequest.entity = Tunnel.entity()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
|
||||
let frc = FetchedResultsController<Tunnel>(fetchRequest: fetchRequest,
|
||||
managedObjectContext: viewContext)
|
||||
frc.setDelegate(self.frcDelegate)
|
||||
return frc
|
||||
}()
|
||||
|
||||
public func updateStatus(for tunnelIdentifier: String) {
|
||||
viewContext.perform {
|
||||
do {
|
||||
let tunnel = try Tunnel.findFirstInContext(self.viewContext, predicate: NSPredicate(format: "tunnelIdentifier == %@", tunnelIdentifier))
|
||||
if let tunnel = tunnel {
|
||||
if let indexPath = self.fetchedResultsController.indexPathForObject(tunnel) {
|
||||
self.tableView.reloadRows(at: [indexPath], with: .none)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
os_log("Unable to load tunnel for tunnel identifier: %{public}@", log: Log.general, type: .error, error.localizedDescription)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var frcDelegate: TunnelFetchedResultsControllerDelegate = { // swiftlint:disable:this weak_delegate
|
||||
return TunnelFetchedResultsControllerDelegate(tableView: self.tableView)
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
do {
|
||||
try fetchedResultsController.performFetch()
|
||||
} catch {
|
||||
print("Failed to fetch objects: \(error)")
|
||||
}
|
||||
|
||||
// Get rid of seperator lines in table.
|
||||
tableView.tableFooterView = UIView(frame: CGRect.zero)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
updateBarButtons()
|
||||
}
|
||||
|
||||
@IBAction func editTunnels(_ sender: Any) {
|
||||
tableView.setEditing(!tableView.isEditing, animated: true)
|
||||
updateBarButtons()
|
||||
|
||||
}
|
||||
|
||||
private func updateBarButtons() {
|
||||
navigationController?.setToolbarHidden(tableView.isEditing, animated: true)
|
||||
if tableView.isEditing {
|
||||
self.navigationItem.setRightBarButtonItems([doneButton], animated: true)
|
||||
} else {
|
||||
self.navigationItem.setRightBarButtonItems([settingsButton, editButton], animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func showSettings(_ sender: Any) {
|
||||
delegate?.showSettings()
|
||||
}
|
||||
|
||||
@IBAction func addProvider(_ sender: UIBarButtonItem) {
|
||||
delegate?.addProvider(tunnelsTableViewController: self)
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return fetchedResultsController.sections?[0].objects.count ?? 0
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(type: TunnelTableViewCell.self, for: indexPath)
|
||||
cell.delegate = self
|
||||
|
||||
guard let sections = fetchedResultsController.sections else {
|
||||
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
||||
}
|
||||
|
||||
let section = sections[indexPath.section]
|
||||
let tunnel = section.objects[indexPath.row]
|
||||
|
||||
cell.configure(tunnel: tunnel, status: delegate?.status(for: tunnel, tunnelsTableViewController: self) ?? .invalid)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let sections = fetchedResultsController.sections else {
|
||||
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
||||
}
|
||||
|
||||
let section = sections[indexPath.section]
|
||||
let tunnel = section.objects[indexPath.row]
|
||||
|
||||
delegate?.info(tunnel: tunnel, tunnelsTableViewController: self)
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||
guard let sections = fetchedResultsController.sections else {
|
||||
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
||||
}
|
||||
|
||||
let section = sections[indexPath.section]
|
||||
let tunnel = section.objects[indexPath.row]
|
||||
|
||||
delegate?.info(tunnel: tunnel, tunnelsTableViewController: self)
|
||||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
if editingStyle == .delete {
|
||||
|
||||
guard let sections = fetchedResultsController.sections else {
|
||||
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
||||
}
|
||||
|
||||
let section = sections[indexPath.section]
|
||||
let tunnel = section.objects[indexPath.row]
|
||||
|
||||
delegate?.delete(tunnel: tunnel, tunnelsTableViewController: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelsTableViewController: TunnelTableViewCellDelegate {
|
||||
func connect(tunnelIdentifier: String) {
|
||||
let tunnel = try? Tunnel.findFirstInContext(self.viewContext, predicate: NSPredicate(format: "tunnelIdentifier == %@", tunnelIdentifier))
|
||||
if let tunnel = tunnel {
|
||||
self.delegate?.connect(tunnel: tunnel!, tunnelsTableViewController: self)
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect(tunnelIdentifier: String) {
|
||||
let tunnel = try? Tunnel.findFirstInContext(self.viewContext, predicate: NSPredicate(format: "tunnelIdentifier == %@", tunnelIdentifier))
|
||||
if let tunnel = tunnel {
|
||||
self.delegate?.disconnect(tunnel: tunnel!, tunnelsTableViewController: self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension TunnelsTableViewController: Identifyable {}
|
||||
|
||||
class TunnelFetchedResultsControllerDelegate: NSObject, FetchedResultsControllerDelegate {
|
||||
|
||||
private weak var tableView: UITableView?
|
||||
private var arrowImage: UIImageView?
|
||||
|
||||
// MARK: - Lifecycle
|
||||
init(tableView: UITableView) {
|
||||
self.tableView = tableView
|
||||
}
|
||||
|
||||
func fetchedResultsControllerDidPerformFetch(_ controller: FetchedResultsController<Tunnel>) {
|
||||
tableView?.reloadData()
|
||||
updateEmptyIndicator(controller)
|
||||
}
|
||||
|
||||
func fetchedResultsControllerWillChangeContent(_ controller: FetchedResultsController<Tunnel>) {
|
||||
tableView?.beginUpdates()
|
||||
}
|
||||
|
||||
func fetchedResultsControllerDidChangeContent(_ controller: FetchedResultsController<Tunnel>) {
|
||||
tableView?.endUpdates()
|
||||
updateEmptyIndicator(controller)
|
||||
}
|
||||
|
||||
func fetchedResultsController(_ controller: FetchedResultsController<Tunnel>, didChangeObject change: FetchedResultsObjectChange<Tunnel>) {
|
||||
guard let tableView = tableView else { return }
|
||||
switch change {
|
||||
case let .insert(_, indexPath):
|
||||
tableView.insertRows(at: [indexPath], with: .automatic)
|
||||
|
||||
case let .delete(_, indexPath):
|
||||
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
|
||||
case let .move(_, fromIndexPath, toIndexPath):
|
||||
tableView.moveRow(at: fromIndexPath, to: toIndexPath)
|
||||
|
||||
case let .update(_, indexPath):
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchedResultsController(_ controller: FetchedResultsController<Tunnel>, didChangeSection change: FetchedResultsSectionChange<Tunnel>) {
|
||||
guard let tableView = tableView else { return }
|
||||
switch change {
|
||||
case let .insert(_, index):
|
||||
tableView.insertSections(IndexSet(integer: index), with: .automatic)
|
||||
|
||||
case let .delete(_, index):
|
||||
tableView.deleteSections(IndexSet(integer: index), with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateEmptyIndicator(_ controller: FetchedResultsController<Tunnel>) {
|
||||
guard let tableView = tableView else { return }
|
||||
if controller.count > 0 {
|
||||
tableView.backgroundView = nil
|
||||
arrowImage = nil
|
||||
} else {
|
||||
if arrowImage == nil {
|
||||
let imageView = UIImageView(image: UIImage(named: "Arrow"))
|
||||
imageView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
|
||||
imageView.frame = tableView.bounds
|
||||
imageView.contentMode = .bottomRight
|
||||
tableView.backgroundView = imageView
|
||||
arrowImage = imageView
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol TunnelTableViewCellDelegate: class {
|
||||
func connect(tunnelIdentifier: String)
|
||||
func disconnect(tunnelIdentifier: String)
|
||||
}
|
||||
|
||||
class TunnelTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var tunnelTitleLabel: UILabel!
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
@IBOutlet weak var tunnelSwitch: UISwitch!
|
||||
|
||||
weak var delegate: TunnelTableViewCellDelegate?
|
||||
private var tunnelIdentifier: String?
|
||||
|
||||
@IBAction func tunnelSwitchChanged(_ sender: Any) {
|
||||
tunnelSwitch.isEnabled = false
|
||||
guard let tunnelIdentifier = tunnelIdentifier else {
|
||||
return
|
||||
}
|
||||
|
||||
if tunnelSwitch.isOn {
|
||||
delegate?.connect(tunnelIdentifier: tunnelIdentifier)
|
||||
} else {
|
||||
delegate?.disconnect(tunnelIdentifier: tunnelIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
func configure(tunnel: Tunnel, status: NEVPNStatus) {
|
||||
self.tunnelTitleLabel?.text = tunnel.title
|
||||
tunnelIdentifier = tunnel.tunnelIdentifier
|
||||
|
||||
if status == .connecting || status == .disconnecting || status == .reasserting {
|
||||
activityIndicator.startAnimating()
|
||||
tunnelSwitch.isHidden = true
|
||||
} else {
|
||||
activityIndicator.stopAnimating()
|
||||
tunnelSwitch.isHidden = false
|
||||
}
|
||||
|
||||
tunnelSwitch.isOn = status == .connected
|
||||
tunnelSwitch.onTintColor = status == .invalid || status == .reasserting ? .gray : .green
|
||||
tunnelSwitch.isEnabled = true
|
||||
}
|
||||
|
||||
override func setEditing(_ editing: Bool, animated: Bool) {
|
||||
super.setEditing(editing, animated: animated)
|
||||
tunnelSwitch.isHidden = editing
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelTableViewCell: Identifyable {}
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITableView {
|
||||
|
||||
func register<T: Identifyable>(type: T.Type, prefix: String = "") where T: UITableViewCell {
|
||||
register(type, forCellReuseIdentifier: prefix + type.identifier)
|
||||
}
|
||||
|
||||
func dequeueReusableCell<T: Identifyable>(type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UITableViewCell {
|
||||
return dequeueReusableCell(withIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast
|
||||
}
|
||||
|
||||
func registerNib<T: Identifyable>(type: T.Type, prefix: String = "") where T: UITableViewCell {
|
||||
let nib = UINib(nibName: prefix + type.identifier, bundle: nil)
|
||||
register(nib, forCellReuseIdentifier: prefix + type.identifier)
|
||||
}
|
||||
|
||||
func registerNib<T: Identifyable>(type: T.Type, prefix: String = "") where T: UITableViewHeaderFooterView {
|
||||
let nib = UINib(nibName: prefix + type.identifier, bundle: nil)
|
||||
register(nib, forHeaderFooterViewReuseIdentifier: prefix + type.identifier)
|
||||
}
|
||||
|
||||
func dequeueReusableHeaderFooterView<T: Identifyable>(type: T.Type, prefix: String = "") -> T where T: UITableViewHeaderFooterView {
|
||||
return dequeueReusableHeaderFooterView(withIdentifier: prefix + type.identifier) as! T // swiftlint:disable:this force_cast
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "Crypto/x25519.h"
|
||||
#import "../wireguard-go-bridge/wireguard.h"
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.wireguard.ios</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)com.wireguard.ios</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,189 +0,0 @@
|
|||
//
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import WireGuard
|
||||
|
||||
class ValidatorsTests: XCTestCase {
|
||||
func testEndpoint() throws {
|
||||
_ = try Endpoint(endpointString: "[2607:f938:3001:4000::aac]:12345")
|
||||
_ = try Endpoint(endpointString: "192.168.0.1:12345")
|
||||
}
|
||||
|
||||
func testEndpoint_invalidIP() throws {
|
||||
func executeTest(endpointString: String, ipString: String, file: StaticString = #file, line: UInt = #line) {
|
||||
XCTAssertThrowsError(try Endpoint(endpointString: endpointString)) { (error) in
|
||||
guard case EndpointValidationError.invalidIP(let value) = error else {
|
||||
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
XCTAssertEqual(value, ipString, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
executeTest(endpointString: "12345:12345", ipString: "12345")
|
||||
executeTest(endpointString: ":12345", ipString: "")
|
||||
}
|
||||
|
||||
func testEndpoint_invalidPort() throws {
|
||||
func executeTest(endpointString: String, portString: String, file: StaticString = #file, line: UInt = #line) {
|
||||
XCTAssertThrowsError(try Endpoint(endpointString: endpointString)) { (error) in
|
||||
guard case EndpointValidationError.invalidPort(let value) = error else {
|
||||
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
XCTAssertEqual(value, portString, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
executeTest(endpointString: ":", portString: "")
|
||||
executeTest(endpointString: "[2607:f938:3001:4000::aac]:-12345", portString: "-12345")
|
||||
executeTest(endpointString: "[2607:f938:3001:4000::aac]", portString: "aac]")
|
||||
executeTest(endpointString: "[2607:f938:3001:4000::aac]:", portString: "")
|
||||
executeTest(endpointString: "192.168.0.1:-12345", portString: "-12345")
|
||||
executeTest(endpointString: "192.168.0.1:", portString: "")
|
||||
|
||||
}
|
||||
|
||||
func testEndpoint_noIpAndPort() throws {
|
||||
|
||||
func executeTest(endpointString: String, file: StaticString = #file, line: UInt = #line) {
|
||||
XCTAssertThrowsError(try Endpoint(endpointString: endpointString)) { (error) in
|
||||
guard case EndpointValidationError.noIpAndPort(let value) = error else {
|
||||
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
XCTAssertEqual(value, endpointString, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
executeTest(endpointString: "192.168.0.1")
|
||||
executeTest(endpointString: "12345")
|
||||
}
|
||||
|
||||
func testCIDRAddress() throws {
|
||||
_ = try CIDRAddress(stringRepresentation: "2607:f938:3001:4000::aac/24")
|
||||
_ = try CIDRAddress(stringRepresentation: "192.168.0.1/24")
|
||||
}
|
||||
|
||||
func testIPv4CIDRAddress() throws {
|
||||
_ = try CIDRAddress(stringRepresentation: "192.168.0.1/24")
|
||||
}
|
||||
|
||||
func testCIDRAddress_invalidIP() throws {
|
||||
func executeTest(stringRepresentation: String, ipString: String, file: StaticString = #file, line: UInt = #line) {
|
||||
XCTAssertThrowsError(try CIDRAddress(stringRepresentation: stringRepresentation)) { (error) in
|
||||
guard case CIDRAddressValidationError.invalidIP(let value) = error else {
|
||||
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
XCTAssertEqual(value, ipString, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
executeTest(stringRepresentation: "12345/12345", ipString: "12345")
|
||||
executeTest(stringRepresentation: "/12345", ipString: "")
|
||||
}
|
||||
|
||||
func testCIDRAddress_invalidSubnet() throws {
|
||||
func executeTest(stringRepresentation: String, subnetString: String, file: StaticString = #file, line: UInt = #line) {
|
||||
XCTAssertThrowsError(try CIDRAddress(stringRepresentation: stringRepresentation)) { (error) in
|
||||
guard case CIDRAddressValidationError.invalidSubnet(let value) = error else {
|
||||
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
XCTAssertEqual(value, subnetString, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
executeTest(stringRepresentation: "/", subnetString: "")
|
||||
executeTest(stringRepresentation: "2607:f938:3001:4000::aac/a", subnetString: "a")
|
||||
executeTest(stringRepresentation: "2607:f938:3001:4000:/aac", subnetString: "aac")
|
||||
executeTest(stringRepresentation: "2607:f938:3001:4000::aac/", subnetString: "")
|
||||
executeTest(stringRepresentation: "192.168.0.1/a", subnetString: "a")
|
||||
executeTest(stringRepresentation: "192.168.0.1/", subnetString: "")
|
||||
|
||||
}
|
||||
|
||||
func testCIDRAddress_noIpAndSubnet() throws {
|
||||
|
||||
func executeTest(stringRepresentation: String, file: StaticString = #file, line: UInt = #line) {
|
||||
XCTAssertThrowsError(try CIDRAddress(stringRepresentation: stringRepresentation)) { (error) in
|
||||
guard case CIDRAddressValidationError.noIpAndSubnet(let value) = error else {
|
||||
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
XCTAssertEqual(value, stringRepresentation, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
executeTest(stringRepresentation: "192.168.0.1")
|
||||
executeTest(stringRepresentation: "12345")
|
||||
}
|
||||
|
||||
// swiftlint:disable next function_body_length
|
||||
func testIPv4CIDRAddressSubnetConversion() throws {
|
||||
// swiftlint:disable force_try
|
||||
let cidrAddress1 = try! CIDRAddress(stringRepresentation: "128.0.0.0/1")!
|
||||
XCTAssertEqual(cidrAddress1.ipAddress, cidrAddress1.subnetString)
|
||||
let cidrAddress2 = try! CIDRAddress(stringRepresentation: "192.0.0.0/2")!
|
||||
XCTAssertEqual(cidrAddress2.ipAddress, cidrAddress2.subnetString)
|
||||
let cidrAddress3 = try! CIDRAddress(stringRepresentation: "224.0.0.0/3")!
|
||||
XCTAssertEqual(cidrAddress3.ipAddress, cidrAddress3.subnetString)
|
||||
let cidrAddress4 = try! CIDRAddress(stringRepresentation: "240.0.0.0/4")!
|
||||
XCTAssertEqual(cidrAddress4.ipAddress, cidrAddress4.subnetString)
|
||||
let cidrAddress5 = try! CIDRAddress(stringRepresentation: "248.0.0.0/5")!
|
||||
XCTAssertEqual(cidrAddress5.ipAddress, cidrAddress5.subnetString)
|
||||
let cidrAddress6 = try! CIDRAddress(stringRepresentation: "252.0.0.0/6")!
|
||||
XCTAssertEqual(cidrAddress6.ipAddress, cidrAddress6.subnetString)
|
||||
let cidrAddress7 = try! CIDRAddress(stringRepresentation: "254.0.0.0/7")!
|
||||
XCTAssertEqual(cidrAddress7.ipAddress, cidrAddress7.subnetString)
|
||||
let cidrAddress8 = try! CIDRAddress(stringRepresentation: "255.0.0.0/8")!
|
||||
XCTAssertEqual(cidrAddress8.ipAddress, cidrAddress8.subnetString)
|
||||
let cidrAddress9 = try! CIDRAddress(stringRepresentation: "255.128.0.0/9")!
|
||||
XCTAssertEqual(cidrAddress9.ipAddress, cidrAddress9.subnetString)
|
||||
let cidrAddress10 = try! CIDRAddress(stringRepresentation: "255.192.0.0/10")!
|
||||
XCTAssertEqual(cidrAddress10.ipAddress, cidrAddress10.subnetString)
|
||||
let cidrAddress11 = try! CIDRAddress(stringRepresentation: "255.224.0.0/11")!
|
||||
XCTAssertEqual(cidrAddress11.ipAddress, cidrAddress11.subnetString)
|
||||
let cidrAddress12 = try! CIDRAddress(stringRepresentation: "255.240.0.0/12")!
|
||||
XCTAssertEqual(cidrAddress12.ipAddress, cidrAddress12.subnetString)
|
||||
let cidrAddress13 = try! CIDRAddress(stringRepresentation: "255.248.0.0/13")!
|
||||
XCTAssertEqual(cidrAddress13.ipAddress, cidrAddress13.subnetString)
|
||||
let cidrAddress14 = try! CIDRAddress(stringRepresentation: "255.252.0.0/14")!
|
||||
XCTAssertEqual(cidrAddress14.ipAddress, cidrAddress14.subnetString)
|
||||
let cidrAddress15 = try! CIDRAddress(stringRepresentation: "255.254.0.0/15")!
|
||||
XCTAssertEqual(cidrAddress15.ipAddress, cidrAddress15.subnetString)
|
||||
let cidrAddress16 = try! CIDRAddress(stringRepresentation: "255.255.0.0/16")!
|
||||
XCTAssertEqual(cidrAddress16.ipAddress, cidrAddress16.subnetString)
|
||||
let cidrAddress17 = try! CIDRAddress(stringRepresentation: "255.255.128.0/17")!
|
||||
XCTAssertEqual(cidrAddress17.ipAddress, cidrAddress17.subnetString)
|
||||
let cidrAddress18 = try! CIDRAddress(stringRepresentation: "255.255.192.0/18")!
|
||||
XCTAssertEqual(cidrAddress18.ipAddress, cidrAddress18.subnetString)
|
||||
let cidrAddress19 = try! CIDRAddress(stringRepresentation: "255.255.224.0/19")!
|
||||
XCTAssertEqual(cidrAddress19.ipAddress, cidrAddress19.subnetString)
|
||||
let cidrAddress20 = try! CIDRAddress(stringRepresentation: "255.255.240.0/20")!
|
||||
XCTAssertEqual(cidrAddress20.ipAddress, cidrAddress20.subnetString)
|
||||
let cidrAddress21 = try! CIDRAddress(stringRepresentation: "255.255.248.0/21")!
|
||||
XCTAssertEqual(cidrAddress21.ipAddress, cidrAddress21.subnetString)
|
||||
let cidrAddress22 = try! CIDRAddress(stringRepresentation: "255.255.252.0/22")!
|
||||
XCTAssertEqual(cidrAddress22.ipAddress, cidrAddress22.subnetString)
|
||||
let cidrAddress23 = try! CIDRAddress(stringRepresentation: "255.255.254.0/23")!
|
||||
XCTAssertEqual(cidrAddress23.ipAddress, cidrAddress23.subnetString)
|
||||
let cidrAddress24 = try! CIDRAddress(stringRepresentation: "255.255.255.0/24")!
|
||||
XCTAssertEqual(cidrAddress24.ipAddress, cidrAddress24.subnetString)
|
||||
let cidrAddress25 = try! CIDRAddress(stringRepresentation: "255.255.255.128/25")!
|
||||
XCTAssertEqual(cidrAddress25.ipAddress, cidrAddress25.subnetString)
|
||||
let cidrAddress26 = try! CIDRAddress(stringRepresentation: "255.255.255.192/26")!
|
||||
XCTAssertEqual(cidrAddress26.ipAddress, cidrAddress26.subnetString)
|
||||
let cidrAddress27 = try! CIDRAddress(stringRepresentation: "255.255.255.224/27")!
|
||||
XCTAssertEqual(cidrAddress27.ipAddress, cidrAddress27.subnetString)
|
||||
let cidrAddress28 = try! CIDRAddress(stringRepresentation: "255.255.255.240/28")!
|
||||
XCTAssertEqual(cidrAddress28.ipAddress, cidrAddress28.subnetString)
|
||||
let cidrAddress29 = try! CIDRAddress(stringRepresentation: "255.255.255.248/29")!
|
||||
XCTAssertEqual(cidrAddress29.ipAddress, cidrAddress29.subnetString)
|
||||
let cidrAddress30 = try! CIDRAddress(stringRepresentation: "255.255.255.252/30")!
|
||||
XCTAssertEqual(cidrAddress30.ipAddress, cidrAddress30.subnetString)
|
||||
let cidrAddress31 = try! CIDRAddress(stringRepresentation: "255.255.255.254/31")!
|
||||
XCTAssertEqual(cidrAddress31.ipAddress, cidrAddress31.subnetString)
|
||||
let cidrAddress32 = try! CIDRAddress(stringRepresentation: "255.255.255.255/32")!
|
||||
XCTAssertEqual(cidrAddress32.ipAddress, cidrAddress32.subnetString)
|
||||
// swiftlint:enable force_try
|
||||
}
|
||||
|
||||
}
|