// // ServerManager.swift // AltStore // // Created by Riley Testut on 5/30/19. // Copyright © 2019 Riley Testut. All rights reserved. // import Foundation import Network import AltKit class ServerManager: NSObject { static let shared = ServerManager() private(set) var isDiscovering = false private(set) var discoveredServers = [Server]() private let serviceBrowser = NetServiceBrowser() private var services = Set() private let dispatchQueue = DispatchQueue(label: "io.altstore.ServerManager") private lazy var connectionListener = self.makeListener() private var incomingConnections = [NWConnection]() private let incomingConnectionsSemaphore = DispatchSemaphore(value: 0) private override init() { super.init() self.serviceBrowser.delegate = self self.serviceBrowser.includesPeerToPeer = false } } extension ServerManager { func startDiscovering() { guard !self.isDiscovering else { return } self.isDiscovering = true self.serviceBrowser.searchForServices(ofType: ALTServerServiceType, inDomain: "") self.connectionListener.start(queue: self.dispatchQueue) } func stopDiscovering() { guard self.isDiscovering else { return } self.isDiscovering = false self.discoveredServers.removeAll() self.services.removeAll() self.serviceBrowser.stop() } func connect(to server: Server, completion: @escaping (Result) -> Void) { DispatchQueue.global().async { func finish(_ result: Result) { completion(result) } func start(_ connection: NWConnection) { connection.stateUpdateHandler = { [unowned connection] (state) in switch state { case .failed(let error): print("Failed to connect to service \(server.service?.name ?? "").", error) finish(.failure(ConnectionError.connectionFailed)) case .cancelled: finish(.failure(OperationError.cancelled)) case .ready: let connection = ServerConnection(server: server, connection: connection) finish(.success(connection)) case .waiting: break case .setup: break case .preparing: break @unknown default: break } } connection.start(queue: self.dispatchQueue) } if server.isWiredConnection { print("Waiting for new wired connection...") let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionStartRequest, nil, nil, true) _ = self.incomingConnectionsSemaphore.wait(timeout: .now() + 10.0) if let connection = self.incomingConnections.popLast() { start(connection) } else { finish(.failure(ALTServerError(.connectionFailed))) } } else if let service = server.service { let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: .tcp) start(connection) } } } } private extension ServerManager { func addDiscoveredServer(_ server: Server) { var server = server server.isPreferred = (server.identifier == UserDefaults.standard.preferredServerID) guard !self.discoveredServers.contains(server) else { return } self.discoveredServers.append(server) } func makeListener() -> NWListener { let listener = try! NWListener(using: .tcp, on: NWEndpoint.Port(rawValue: ALTDeviceListeningSocket)!) listener.newConnectionHandler = { [weak self] (connection) in self?.incomingConnections.append(connection) self?.incomingConnectionsSemaphore.signal() } listener.stateUpdateHandler = { (state) in switch state { case .ready: break case .waiting, .setup: print("Listener socket waiting...") case .cancelled: print("Listener socket cancelled.") case .failed(let error): print("Listener socket failed:", error) @unknown default: break } } return listener } } extension ServerManager: NetServiceBrowserDelegate { func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) { print("Discovering servers...") } func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) { print("Stopped discovering servers.") } func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String : NSNumber]) { print("Failed to discovering servers.", errorDict) } func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { service.delegate = self if let txtData = service.txtRecordData(), let server = Server(service: service, txtData: txtData) { self.addDiscoveredServer(server) } else { service.resolve(withTimeout: 3) self.services.insert(service) } } func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) { if let index = self.discoveredServers.firstIndex(where: { $0.service == service }) { self.discoveredServers.remove(at: index) } self.services.remove(service) } } extension ServerManager: NetServiceDelegate { func netServiceDidResolveAddress(_ service: NetService) { guard let data = service.txtRecordData(), let server = Server(service: service, txtData: data) else { return } self.addDiscoveredServer(server) } func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) { print("Error resolving net service \(sender).", errorDict) } func netService(_ sender: NetService, didUpdateTXTRecord data: Data) { let txtDict = NetService.dictionary(fromTXTRecord: data) print("Service \(sender) updated TXT Record:", txtDict) } }