2018-10-24 01:37:28 +00:00
// S P D X - L i c e n s e - I d e n t i f i e r : M I T
// C o p y r i g h t © 2 0 1 8 W i r e G u a r d L L C . A l l r i g h t s r e s e r v e d .
2018-10-13 01:38:30 +00:00
import UIKit
2018-10-28 20:22:43 +00:00
import MobileCoreServices
2018-10-13 01:38:30 +00:00
class TunnelsListTableViewController : UITableViewController {
2018-10-17 08:17:57 +00:00
var tunnelsManager : TunnelsManager ? = nil
2018-10-25 05:40:18 +00:00
var onTunnelsManagerReady : ( ( TunnelsManager ) -> Void ) ? = nil
2018-10-17 08:17:57 +00:00
2018-10-17 07:50:20 +00:00
init ( ) {
super . init ( style : . plain )
}
required init ? ( coder aDecoder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2018-10-13 01:38:30 +00:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
self . title = " WireGuard "
let addButtonItem = UIBarButtonItem ( barButtonSystemItem : . add , target : self , action : #selector ( addButtonTapped ( sender : ) ) )
self . navigationItem . rightBarButtonItem = addButtonItem
2018-10-29 12:04:02 +00:00
let settingsButtonItem = UIBarButtonItem ( title : " Settings " , style : . plain , target : self , action : #selector ( settingsButtonTapped ( sender : ) ) )
self . navigationItem . leftBarButtonItem = settingsButtonItem
2018-10-17 08:17:57 +00:00
2018-10-28 09:26:10 +00:00
self . tableView . rowHeight = 60
2018-10-17 08:17:57 +00:00
self . tableView . register ( TunnelsListTableViewCell . self , forCellReuseIdentifier : TunnelsListTableViewCell . id )
TunnelsManager . create { [ weak self ] tunnelsManager in
guard let tunnelsManager = tunnelsManager else { return }
2018-10-23 12:11:37 +00:00
if let s = self {
tunnelsManager . delegate = s
s . tunnelsManager = tunnelsManager
2018-10-25 05:40:18 +00:00
s . onTunnelsManagerReady ? ( tunnelsManager )
s . onTunnelsManagerReady = nil
2018-10-23 12:11:37 +00:00
s . tableView . reloadData ( )
}
2018-10-17 08:17:57 +00:00
}
2018-10-13 01:38:30 +00:00
}
@objc func addButtonTapped ( sender : UIBarButtonItem ! ) {
2018-10-17 10:03:06 +00:00
let alert = UIAlertController ( title : " " ,
message : " Add a tunnel " ,
preferredStyle : . actionSheet )
2018-10-28 20:22:43 +00:00
let importFileAction = UIAlertAction ( title : " Import file or archive " , style : . default ) { [ weak self ] ( action ) in
2018-10-25 09:05:23 +00:00
self ? . presentViewControllerForFileImport ( )
}
alert . addAction ( importFileAction )
2018-10-28 16:52:27 +00:00
let scanQRCodeAction = UIAlertAction ( title : " Scan QR code " , style : . default ) { [ weak self ] ( action ) in
self ? . presentViewControllerForScanningQRCode ( )
}
alert . addAction ( scanQRCodeAction )
2018-10-25 09:05:23 +00:00
let createFromScratchAction = UIAlertAction ( title : " Create from scratch " , style : . default ) { [ weak self ] ( action ) in
if let s = self , let tunnelsManager = s . tunnelsManager {
s . presentViewControllerForTunnelCreation ( tunnelsManager : tunnelsManager , tunnelConfiguration : nil )
2018-10-17 10:03:06 +00:00
}
2018-10-25 09:05:23 +00:00
}
alert . addAction ( createFromScratchAction )
let cancelAction = UIAlertAction ( title : " Cancel " , style : . cancel )
alert . addAction ( cancelAction )
2018-10-17 10:03:06 +00:00
// p o p o v e r P r e s e n t a t i o n C o n t r o l l e r w i l l b e n i l o n i P h o n e a n d n o n - n i l o n i P a d
alert . popoverPresentationController ? . barButtonItem = self . navigationItem . rightBarButtonItem
self . present ( alert , animated : true , completion : nil )
2018-10-13 01:38:30 +00:00
}
2018-10-25 05:40:18 +00:00
2018-10-29 12:04:02 +00:00
@objc func settingsButtonTapped ( sender : UIBarButtonItem ! ) {
2018-10-29 17:26:25 +00:00
let settingsVC = SettingsTableViewController ( tunnelsManager : tunnelsManager )
2018-10-29 12:04:02 +00:00
let settingsNC = UINavigationController ( rootViewController : settingsVC )
settingsNC . modalPresentationStyle = . formSheet
self . present ( settingsNC , animated : true )
}
2018-10-25 05:40:18 +00:00
func openForEditing ( configFileURL : URL ) {
let tunnelConfiguration : TunnelConfiguration ?
let name = configFileURL . deletingPathExtension ( ) . lastPathComponent
do {
let fileContents = try String ( contentsOf : configFileURL )
2018-10-28 16:29:52 +00:00
try tunnelConfiguration = WgQuickConfigFileParser . parse ( fileContents , name : name )
2018-10-25 05:40:18 +00:00
} catch {
showErrorAlert ( title : " Could not import config " , message : " There was an error importing the config file " )
return
}
tunnelConfiguration ? . interface . name = name
if let tunnelsManager = tunnelsManager {
presentViewControllerForTunnelCreation ( tunnelsManager : tunnelsManager , tunnelConfiguration : tunnelConfiguration )
} else {
onTunnelsManagerReady = { [ weak self ] tunnelsManager in
self ? . presentViewControllerForTunnelCreation ( tunnelsManager : tunnelsManager , tunnelConfiguration : tunnelConfiguration )
}
}
}
func presentViewControllerForTunnelCreation ( tunnelsManager : TunnelsManager , tunnelConfiguration : TunnelConfiguration ? ) {
let editVC = TunnelEditTableViewController ( tunnelsManager : tunnelsManager , tunnelConfiguration : tunnelConfiguration )
editVC . delegate = self
let editNC = UINavigationController ( rootViewController : editVC )
2018-10-25 07:53:09 +00:00
editNC . modalPresentationStyle = . formSheet
2018-10-25 05:40:18 +00:00
self . present ( editNC , animated : true )
}
2018-10-25 09:05:23 +00:00
func presentViewControllerForFileImport ( ) {
2018-10-28 20:22:43 +00:00
let documentTypes = [ " com.wireguard.config.quick " , String ( kUTTypeZipArchive ) ]
let filePicker = UIDocumentPickerViewController ( documentTypes : documentTypes , in : . import )
2018-10-25 09:05:23 +00:00
filePicker . delegate = self
self . present ( filePicker , animated : true )
}
2018-10-28 16:52:27 +00:00
func presentViewControllerForScanningQRCode ( ) {
let scanQRCodeVC = QRScanViewController ( )
scanQRCodeVC . delegate = self
let scanQRCodeNC = UINavigationController ( rootViewController : scanQRCodeVC )
scanQRCodeNC . modalPresentationStyle = . fullScreen
self . present ( scanQRCodeNC , animated : true )
}
2018-10-25 05:40:18 +00:00
func showErrorAlert ( title : String , message : String ) {
let okAction = UIAlertAction ( title : " Ok " , style : . default )
let alert = UIAlertController ( title : title , message : message , preferredStyle : . alert )
alert . addAction ( okAction )
self . present ( alert , animated : true , completion : nil )
}
2018-10-28 20:38:40 +00:00
func importFromFile ( url : URL ) {
// I m p o r t c o n f i g u r a t i o n s f r o m a . c o n f o r a . z i p f i l e
if ( url . pathExtension = = " conf " ) {
let fileBaseName = url . deletingPathExtension ( ) . lastPathComponent
if let fileContents = try ? String ( contentsOf : url ) ,
let tunnelConfiguration = try ? WgQuickConfigFileParser . parse ( fileContents , name : fileBaseName ) {
tunnelsManager ? . add ( tunnelConfiguration : tunnelConfiguration ) { ( tunnel , error ) in
if ( error != nil ) {
print ( " Error adding configuration: \( tunnelConfiguration . interface . name ) " )
}
}
} else {
showErrorAlert ( title : " Could not import " , message : " The config file contained errors " )
}
} else if ( url . pathExtension = = " zip " ) {
var unarchivedFiles : [ ( fileName : String , contents : Data ) ] = [ ]
do {
unarchivedFiles = try ZipArchive . unarchive ( url : url , requiredFileExtensions : [ " conf " ] )
} catch ZipArchiveError . cantOpenInputZipFile {
showErrorAlert ( title : " Cannot read zip archive " , message : " The zip file couldn't be read " )
} catch ZipArchiveError . badArchive {
showErrorAlert ( title : " Cannot read zip archive " , message : " Bad archive " )
} catch ( let error ) {
print ( " Error opening zip archive: \( error ) " )
}
var numberOfConfigFilesWithErrors = 0
for unarchivedFile in unarchivedFiles {
if let fileBaseName = URL ( string : unarchivedFile . fileName ) ? . deletingPathExtension ( ) . lastPathComponent ,
let fileContents = String ( data : unarchivedFile . contents , encoding : . utf8 ) ,
let tunnelConfiguration = try ? WgQuickConfigFileParser . parse ( fileContents , name : fileBaseName ) {
tunnelsManager ? . add ( tunnelConfiguration : tunnelConfiguration ) { ( tunnel , error ) in
if ( error != nil ) {
print ( " Error adding configuration: \( tunnelConfiguration . interface . name ) " )
}
}
} else {
numberOfConfigFilesWithErrors = numberOfConfigFilesWithErrors + 1
}
}
if ( numberOfConfigFilesWithErrors > 0 ) {
showErrorAlert ( title : " Could not import \( numberOfConfigFilesWithErrors ) files " , message : " \( numberOfConfigFilesWithErrors ) of \( unarchivedFiles . count ) files contained errors and were not imported " )
}
}
}
2018-10-13 01:38:30 +00:00
}
2018-10-24 11:39:34 +00:00
// MARK: T u n n e l E d i t T a b l e V i e w C o n t r o l l e r D e l e g a t e
extension TunnelsListTableViewController : TunnelEditTableViewControllerDelegate {
2018-10-25 05:44:38 +00:00
func tunnelSaved ( tunnel : TunnelContainer ) {
2018-10-24 11:39:34 +00:00
guard let tunnelsManager = tunnelsManager else { return }
let tunnelDetailVC = TunnelDetailTableViewController ( tunnelsManager : tunnelsManager ,
tunnel : tunnel )
2018-10-25 06:41:05 +00:00
let tunnelDetailNC = UINavigationController ( rootViewController : tunnelDetailVC )
showDetailViewController ( tunnelDetailNC , sender : self ) // S h a l l g e t p r o p a g a t e d u p t o t h e s p l i t - v c
2018-10-24 11:39:34 +00:00
}
2018-10-25 05:44:38 +00:00
func tunnelEditingCancelled ( ) {
// N o t h i n g t o d o h e r e
}
2018-10-24 11:39:34 +00:00
}
2018-10-25 09:05:23 +00:00
// MARK: U I D o c u m e n t P i c k e r D e l e g a t e
extension TunnelsListTableViewController : UIDocumentPickerDelegate {
func documentPicker ( _ controller : UIDocumentPickerViewController , didPickDocumentsAt urls : [ URL ] ) {
if let url = urls . first {
2018-10-28 20:38:40 +00:00
importFromFile ( url : url )
2018-10-25 09:05:23 +00:00
}
}
}
2018-10-28 16:52:27 +00:00
// MARK: Q R S c a n V i e w C o n t r o l l e r D e l e g a t e
extension TunnelsListTableViewController : QRScanViewControllerDelegate {
func scannedQRCode ( tunnelConfiguration : TunnelConfiguration , qrScanViewController : QRScanViewController ) {
2018-10-28 18:02:15 +00:00
tunnelsManager ? . add ( tunnelConfiguration : tunnelConfiguration ) { [ weak self ] ( tunnel , error ) in
if let error = error {
print ( " Could not add tunnel: \( error ) " )
self ? . showErrorAlert ( title : " Could not save scanned config " , message : " Internal error " )
}
}
2018-10-28 16:52:27 +00:00
}
}
2018-10-13 01:38:30 +00:00
// MARK: U I T a b l e V i e w D a t a S o u r c e
extension TunnelsListTableViewController {
override func numberOfSections ( in tableView : UITableView ) -> Int {
2018-10-17 08:17:57 +00:00
return 1
2018-10-13 01:38:30 +00:00
}
override func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
2018-10-17 08:17:57 +00:00
return ( tunnelsManager ? . numberOfTunnels ( ) ? ? 0 )
}
override func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
let cell = tableView . dequeueReusableCell ( withIdentifier : TunnelsListTableViewCell . id , for : indexPath ) as ! TunnelsListTableViewCell
if let tunnelsManager = tunnelsManager {
let tunnel = tunnelsManager . tunnel ( at : indexPath . row )
2018-10-28 09:26:10 +00:00
cell . tunnel = tunnel
cell . onSwitchToggled = { [ weak self ] isOn in
guard let s = self , let tunnelsManager = s . tunnelsManager else { return }
if ( isOn ) {
tunnelsManager . startActivation ( of : tunnel ) { error in
2018-10-29 18:54:50 +00:00
if let error = error {
switch ( error ) {
case TunnelsManagerError . noEndpoint :
self ? . showErrorAlert ( title : " Endpoint missing " , message : " There must be atleast one peer with an endpoint " )
case TunnelsManagerError . dnsResolutionFailed :
self ? . showErrorAlert ( title : " DNS Failure " , message : " One or more endpoint domains could not be resolved " )
default :
self ? . showErrorAlert ( title : " Internal error " , message : " The tunnel could not be activated " )
}
}
2018-10-28 09:26:10 +00:00
}
} else {
tunnelsManager . startDeactivation ( of : tunnel ) { error in
print ( " Error while deactivating: \( String ( describing : error ) ) " )
}
}
}
2018-10-17 08:17:57 +00:00
}
return cell
}
}
2018-10-23 16:48:45 +00:00
// MARK: U I T a b l e V i e w D e l e g a t e
extension TunnelsListTableViewController {
override func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
guard let tunnelsManager = tunnelsManager else { return }
2018-10-24 11:39:34 +00:00
let tunnel = tunnelsManager . tunnel ( at : indexPath . row )
2018-10-23 16:48:45 +00:00
let tunnelDetailVC = TunnelDetailTableViewController ( tunnelsManager : tunnelsManager ,
2018-10-24 11:39:34 +00:00
tunnel : tunnel )
2018-10-25 06:41:05 +00:00
let tunnelDetailNC = UINavigationController ( rootViewController : tunnelDetailVC )
showDetailViewController ( tunnelDetailNC , sender : self ) // S h a l l g e t p r o p a g a t e d u p t o t h e s p l i t - v c
2018-10-23 16:48:45 +00:00
}
}
2018-10-23 12:11:37 +00:00
// MARK: T u n n e l s M a n a g e r D e l e g a t e
extension TunnelsListTableViewController : TunnelsManagerDelegate {
2018-10-25 10:20:27 +00:00
func tunnelAdded ( at index : Int ) {
tableView . insertRows ( at : [ IndexPath ( row : index , section : 0 ) ] , with : . automatic )
}
func tunnelModified ( at index : Int ) {
tableView . reloadRows ( at : [ IndexPath ( row : index , section : 0 ) ] , with : . automatic )
}
func tunnelsChanged ( ) {
tableView . reloadData ( )
2018-10-23 12:11:37 +00:00
}
2018-10-28 23:25:50 +00:00
func tunnelRemoved ( at index : Int ) {
tableView . deleteRows ( at : [ IndexPath ( row : index , section : 0 ) ] , with : . automatic )
}
2018-10-23 12:11:37 +00:00
}
2018-10-17 08:17:57 +00:00
class TunnelsListTableViewCell : UITableViewCell {
static let id : String = " TunnelsListTableViewCell "
2018-10-28 09:26:10 +00:00
var tunnel : TunnelContainer ? {
didSet ( value ) {
// B i n d t o t h e t u n n e l ' s n a m e
nameLabel . text = tunnel ? . name ? ? " "
nameObservervationToken = tunnel ? . observe ( \ . name ) { [ weak self ] ( tunnel , _ ) in
self ? . nameLabel . text = tunnel . name
}
// B i n d t o t h e t u n n e l ' s s t a t u s
update ( from : tunnel ? . status )
statusObservervationToken = tunnel ? . observe ( \ . status ) { [ weak self ] ( tunnel , _ ) in
self ? . update ( from : tunnel . status )
}
}
2018-10-17 08:17:57 +00:00
}
2018-10-28 09:26:10 +00:00
var onSwitchToggled : ( ( Bool ) -> Void ) ? = nil
let nameLabel : UILabel
let busyIndicator : UIActivityIndicatorView
let statusSwitch : UISwitch
private var statusObservervationToken : AnyObject ? = nil
private var nameObservervationToken : AnyObject ? = nil
2018-10-17 08:17:57 +00:00
override init ( style : UITableViewCellStyle , reuseIdentifier : String ? ) {
2018-10-28 09:26:10 +00:00
nameLabel = UILabel ( )
busyIndicator = UIActivityIndicatorView ( activityIndicatorStyle : . gray )
busyIndicator . hidesWhenStopped = true
statusSwitch = UISwitch ( )
2018-10-17 08:17:57 +00:00
super . init ( style : style , reuseIdentifier : reuseIdentifier )
2018-10-28 09:26:10 +00:00
contentView . addSubview ( statusSwitch )
statusSwitch . translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint . activate ( [
statusSwitch . centerYAnchor . constraint ( equalTo : contentView . centerYAnchor ) ,
statusSwitch . rightAnchor . constraint ( equalTo : contentView . rightAnchor , constant : - 8 )
] )
contentView . addSubview ( busyIndicator )
busyIndicator . translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint . activate ( [
busyIndicator . centerYAnchor . constraint ( equalTo : contentView . centerYAnchor ) ,
busyIndicator . rightAnchor . constraint ( equalTo : statusSwitch . leftAnchor , constant : - 8 )
] )
contentView . addSubview ( nameLabel )
nameLabel . translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint . activate ( [
nameLabel . centerYAnchor . constraint ( equalTo : contentView . centerYAnchor ) ,
nameLabel . leftAnchor . constraint ( equalTo : contentView . leftAnchor , constant : 16 ) ,
nameLabel . rightAnchor . constraint ( equalTo : busyIndicator . leftAnchor )
] )
2018-10-17 08:17:57 +00:00
self . accessoryType = . disclosureIndicator
2018-10-28 09:26:10 +00:00
statusSwitch . addTarget ( self , action : #selector ( switchToggled ) , for : . valueChanged )
}
@objc func switchToggled ( ) {
onSwitchToggled ? ( statusSwitch . isOn )
}
private func update ( from status : TunnelStatus ? ) {
guard let status = status else {
reset ( )
return
}
DispatchQueue . main . async { [ weak statusSwitch , weak busyIndicator ] in
guard let statusSwitch = statusSwitch , let busyIndicator = busyIndicator else { return }
statusSwitch . isOn = ! ( status = = . deactivating || status = = . inactive )
statusSwitch . isUserInteractionEnabled = ( status = = . inactive || status = = . active )
if ( status = = . inactive || status = = . active ) {
busyIndicator . stopAnimating ( )
} else {
busyIndicator . startAnimating ( )
}
}
2018-10-17 08:17:57 +00:00
}
required init ? ( coder aDecoder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2018-10-28 09:26:10 +00:00
private func reset ( ) {
statusSwitch . isOn = false
statusSwitch . isUserInteractionEnabled = false
busyIndicator . stopAnimating ( )
}
2018-10-17 08:17:57 +00:00
override func prepareForReuse ( ) {
super . prepareForReuse ( )
2018-10-28 09:26:10 +00:00
reset ( )
2018-10-13 01:38:30 +00:00
}
}