// // KillSwitchClient.swift // PrivadoVPN // // Created by Juraldinio on 7/30/20. // Copyright © 2020 Privado LLC. All rights reserved. // import Foundation protocol KillSwitchClientDelegate: AnyObject { func clientChangeState(state: XPCClientState) } final class KillSwitchClient { enum KillSwitchError: Error { case unowned case writeError } weak var delegate: KillSwitchClientDelegate? private weak var client: (XPCFirewallClient & XPCSystemClient)? var state: XPCClientState { self.client?.state ?? .undefined } // MARK: - Init init(client: (XPCFirewallClient & XPCSystemClient)?) { self.client = client self.client?.stateEmitter.addReaction { [weak self] state -> ShouldContinueReceiveNotifications in self?.delegate?.clientChangeState(state: state) return self.isExist } } // MARK: - Client operations @discardableResult func flushDNS() -> Future { return Future(value: self.client) .flatMap { client in client.flushDNS() } } func resolveDNS(name: String) -> Future { return Future(value: self.client) .flatMap { client in client.resolveDNS(name: name) } } @discardableResult func changeDNS(dns: [String], port: String) -> Future { return Future(value: self.client) .flatMap { client in client.changeDNS(servers: dns, hardware: port) } } @discardableResult func changeDNSs(dns: [String], ports: [String]) -> Future { return ports.reduce(nil as Future?) { result, port in if let future = result { return future.flatMap { _ in self.changeDNS(dns: dns, port: port) } } else { return self.changeDNS(dns: dns, port: port) } } ?? Future(value: true) } @discardableResult func currentDNS(hardware: [String]) -> Future<[String: [String]]> { return Future(value: self.client) .flatMap { client in client.currentDNS(hardware: hardware) } } @discardableResult func hardwarePorts() -> Future<[HardwarePort]> { return Future(value: self.client) .flatMap { client in client.hardwarePorts() } } func firewall(check rules: [String]) -> Future { let content = rules.joined(separator: "\n") + "\n" return Future(value: URL.randomFile()) .flatMap { [weak self] url in do { try content.write(to: url, atomically: true, encoding: .utf8) } catch { return Future(error: KillSwitchError.writeError) } guard let client = self?.client else { return Future(error: KillSwitchError.unowned) } return client .firewallActivate(rulesPath: url.absoluteString, validate: true) .onResult { _ in url.remove() } } } @discardableResult func firewall(activate rules: [String]) -> Future { let content = rules.joined(separator: "\n") + "\n" return Future(value: URL.randomFile()) .flatMap { [weak self] url in do { try content.write(to: url, atomically: true, encoding: .utf8) } catch { return Future(error: KillSwitchError.writeError) } guard let client = self?.client else { return Future(error: KillSwitchError.unowned) } return client .firewallActivate(rulesPath: url.absoluteString, validate: false) .onResult { _ in url.remove() } } } @discardableResult func firewallDeactivate() -> Future { return Future(value: self.client) .flatMap { client in client.firewallDeactivate() } } func firewallEnable(_ enable: Bool) -> Future { return Future(value: self.client) .map({ success in Future(value: success) }, failure: { _ in let error = NSError(domain: PrivadoConstants.Hive.error, code: PrivadoConstants.Hive.ErrorCode.xpcEmptyRemoteObject, userInfo: nil) return Future(error: error) }).flatMap { client in client.firewallEnable(enable) } } @discardableResult func firewallState() -> Future { return Future(value: self.client) .map({ success in Future(value: success) }, failure: { _ in let error = NSError(domain: PrivadoConstants.Hive.error, code: PrivadoConstants.Hive.ErrorCode.xpcEmptyRemoteObject, userInfo: nil) return Future(error: error) }).flatMap { client in client.firewallState() } } }