import Foundation final class PrivadoHiveClient: NSObject { enum FirewallError: Error { case enable(Error) case activate(Error) case deactivate(Error) case state(Error) } // MARK: - XPCСommonClient private(set) var state: XPCClientState = .undefined { didSet { print("self.state: \(self.state)") guard self.state != oldValue else { return } let state = self.state if self.state == .requireUpdate { self.reset() } self.stateEmitter.invoke(state) } } let stateEmitter = Emitter() // MARK: - Properties private var connection: NSXPCConnection? private var isValidConnection: Bool { self.connection.isExist } private var installed = false weak var consoleClient: XPCСonsoleClient? // MARK: - Static private static var instance: PrivadoHiveClient? static func shared() -> PrivadoHiveClient { if let instance = self.instance { return instance } let instance = PrivadoHiveClient() self.instance = instance return instance } // MARK: - Init private override init() { super.init() self.createConnection() } // MARK: - XPCСommonClient func install(installer: @escaping (String) -> Bool) -> Future { self.state = .installing return Future { completion in let success = installer(PrivadoConstants.Hive.bundle) self.installed = success let result: Result = success ? .success(self) : .failure(FutureError.none) completion(result) } } func resume() { guard let connection = self.connection else { self.createConnection() return } connection.resume() } // MARK: - Private private func reset() { guard self.state == .requireUpdate else { return } self.connection?.invalidate() self.connection = nil } private func createConnection() { guard !self.connection.isExist else { return } let connection = NSXPCConnection(machServiceName: PrivadoConstants.Hive.bundle, options: .privileged) connection.exportedInterface = NSXPCInterface(with: IntercommConsoleClientType.self) connection.exportedObject = self connection.remoteObjectInterface = NSXPCInterface(with: IntercommHiveType.self) connection.invalidationHandler = self.connectionInvalidationHandler connection.interruptionHandler = self.connetionInterruptionHandler self.connection = connection connection.resume() } /* func ping(completion: @escaping () -> Void) func connect(type: Int, configuration: HiveVPNConfiguration, completion: @escaping (Error?) -> Void) func disconnect(completion: @escaping (HiveVPNStatus) -> Void) // sudo killall openvpn */ private func intercomm() -> Future { return Future { [weak self] completion in guard let self = self else { completion(.failure(XPCСommonClientError.unowned)) return } guard let connection = self.connection , let intercomm = connection.remoteObjectProxyWithErrorHandler({ error in completion(.failure(XPCСommonClientError.proxy(error))) }) as? IntercommHiveType else { completion(.failure(XPCСommonClientError.incorrectType)) return } completion(.success(intercomm)) }.onResult { result in switch result { case let .failure(hiveError): switch hiveError { case XPCСommonClientError.unowned: break case let XPCСommonClientError.proxy(error): CometLogger.shared.record(error: error) if self.state == .initializing { self.state = .requireUpdate } case XPCСommonClientError.incorrectType: if self.state == .initializing { self.state = .requireUpdate } default: break } default: break } } } private func checkCallAllowed() -> Future { return Future { [weak self] completion in guard let self = self else { completion(.failure(XPCСommonClientError.unowned)) return } guard self.state == .connected else { completion(.failure(XPCСommonClientError.notConnected)) return /*throw NSError(domain: PrivadoConstants.Hive.error, code: PrivadoConstants.Hive.ErrorCode.xpcNotConnected, userInfo: nil)*/ } completion(.success( () )) }.flatMap { [weak self] _ -> Future in guard let self = self else { return Future(error: XPCСommonClientError.unowned) } return self.intercomm() }.onFailure { [weak self] _ in self?.state = .undefined /*throw NSError(domain: PrivadoConstants.Hive.error, code: PrivadoConstants.Hive.ErrorCode.xpcEmptyRemoteObject, userInfo: nil)*/ } } private func connetionInterruptionHandler() { guard self.state != .requireUpdate else { return } if self.state == .initializing , !self.installed { self.state = .requireUpdate return } self.state = .interrupted // NSLog("[SMJBS]: \(#function)") } private func connectionInvalidationHandler() { if self.installed { // Think what I must todo here return } if self.state == .undefined , !self.installed { self.state = .requireUpdate return } guard self.state != .requireUpdate else { return } if self.state == .initializing { self.state = .requireUpdate return } self.state = .invalid // NSLog("[SMJBS]: \(#function)") } } // MARK: - XPCSystemClient extension PrivadoHiveClient: XPCSystemClient { func checkVersion() -> Future { return Future { [weak self] completion in guard let self = self else { completion(.failure(XPCСommonClientError.unowned)) return } if self.state == .initializing { completion(.failure(XPCСommonClientError.alreadyConnected)) return } completion(.success(())) }.flatMap { [weak self] _ -> Future in guard let self = self else { return Future(error: XPCСommonClientError.unowned) } return self.intercomm() }.onFailure { [weak self] _ in if let self = self, self.isValidConnection { self.state = .undefined } }.onSuccess { [weak self] _ in if let self = self, self.isValidConnection { self.state = .initializing } }.flatMap { [weak self] intercomm in if !(self?.isValidConnection ?? false) { return Future(value: ()) } return Future { completion in intercomm.version { [weak self] (shortVersion, bundleVersion) in if let self = self { let state: XPCClientState = Environment.shortVersion == shortVersion && Environment.bundleVersion == bundleVersion ? .connected : .requireUpdate CometLogger.shared.vpnEvent(type: PrivadoConstants.Event.hive, userInfo: [ PrivadoConstants.Event.Attributes.shortVersion: Environment.shortVersion, PrivadoConstants.Event.Attributes.bundleVersion: Environment.bundleVersion ], secured: nil) self.state = state } completion(.success(())) } } } } func flushDNS() -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.flushDNS { completion(.success($0)) } } } } func resolveDNS(name: String) -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.resolveDNS(name: name) { completion(.success($0)) } } }.recover { return "127.0.0.1" } } func ifconfig() -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.ifconfig { completion(.success($0)) } } } } func changeDNS(servers: [String], hardware: String) -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.changeDNS(servers: servers, hardware: hardware) { completion(.success($0)) } } } } func currentDNS(hardware: [String]) -> Future<[String: [String]]> { return self .checkCallAllowed() .flatMap { intercomm -> Future<[String: [String]]> in return Future<[String: [String]]> { completion in intercomm.currentDNS(hardware: hardware) { completion(.success($0)) } } } } func hardwarePorts() -> Future<[HardwarePort]> { return self .checkCallAllowed() .flatMap { intercomm -> Future<[HardwarePort]> in return Future<[HardwarePort]> { completion in intercomm.hardwarePorts { completion(.success($0)) } } } } func removeUninstaller() -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.removeUninstaller { completion(.success(())) } } } } } // MARK: - XPCOpenVPNClient extension PrivadoHiveClient: XPCOpenVPNClient { func connect(type: Int, configuration: OpenVPNConfiguration) -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.connect(type: type, ovpnPath: configuration.ovpnPath, configPath: configuration.configPath, username: configuration.username, password: configuration.password) { error in guard let error = error else { completion(.success(())) return } completion(.failure(XPCСommonClientError.proxy(error))) } } } } func disconnect() -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.disconnect { error in guard let error = error else { completion(.success(())) return } completion(.failure(XPCСommonClientError.proxy(error))) } } } } func isConnected() -> Future { return Future { [weak self] completion in guard let self = self else { completion(.failure(XPCСommonClientError.unowned)) return } guard self.state == .connected else { completion(.failure(XPCСommonClientError.notConnected)) return } }.flatMap { [weak self] _ -> Future in guard let self = self else { return Future(error: XPCСommonClientError.unowned) } return self.intercomm() }.onFailure { [weak self] _ in if let self = self { self.state = .undefined } }.flatMap { intercomm -> Future in return Future { completion in intercomm.isConnected { connected in completion(.success(connected)) } } } } } // MARK: - XPCFirewallClient extension PrivadoHiveClient: XPCFirewallClient { func firewallState() -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.firewallState { success, error in guard let error = error else { completion(.success(success)) return } completion(.failure(FirewallError.state(error))) } } } } func firewallEnable(_ enable: Bool) -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.firewallEnable(enable) { success, error in guard let error = error else { completion(.success(success)) return } completion(.failure(FirewallError.enable(error))) } } } } func firewallActivate(rulesPath: String, validate: Bool) -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.firewallActivate(rulesPath: rulesPath, validate: validate) { success, error in guard let error = error else { completion(.success(success)) return } completion(.failure(FirewallError.activate(error))) } } } } func firewallDeactivate() -> Future { return self .checkCallAllowed() .flatMap { intercomm -> Future in return Future { completion in intercomm.firewallDeactivate { success, error in guard let error = error else { completion(.success(success)) return } completion(.failure(FirewallError.deactivate(error))) } } } } func firewallRulesSave() -> Future { return Future(value: ()) } func firewallRulesRestore() -> Future { return Future(value: ()) } } // MARK: - IntercommConsoleClientType extension PrivadoHiveClient: IntercommConsoleClientType { // MARK: - IntercommConsoleClientType func log(message: String, completion: @escaping () -> Void) { print(message) CometLogger.shared.vpnEvent(type: PrivadoConstants.Event.Vpn.ovpn, userInfo: nil, secured: message) } func error(message: String, errNo: Int, completion: @escaping () -> Void) { print(message) CometLogger.shared.vpnEvent(type: PrivadoConstants.Event.error, userInfo: [PrivadoConstants.Event.Attributes.value: errNo], secured: message) completion() } func console(message: String, completion: @escaping () -> Void) { guard let consoleClient = self.consoleClient else { return } consoleClient.console(message: message) completion() } }