// // DiscoveryManager.m // Moonlight // // Created by Diego Waxemberg on 1/1/15. // Copyright (c) 2015 Moonlight Stream. All rights reserved. // #import "DiscoveryManager.h" #import "CryptoManager.h" #import "HttpManager.h" #import "Utils.h" #import "DataManager.h" #import "DiscoveryWorker.h" #import "ServerInfoResponse.h" #import "IdManager.h" @implementation DiscoveryManager { NSMutableArray* _hostQueue; id _callback; MDNSManager* _mdnsMan; NSOperationQueue* _opQueue; NSString* _uniqueId; NSData* _cert; BOOL shouldDiscover; } - (id)initWithHosts:(NSArray *)hosts andCallback:(id)callback { self = [super init]; // Using addHostToDiscovery ensures no duplicates // will make it into the list from the database _callback = callback; shouldDiscover = NO; _hostQueue = [NSMutableArray array]; for (TemporaryHost* host in hosts) { [self addHostToDiscovery:host]; } [_callback updateAllHosts:_hostQueue]; _opQueue = [[NSOperationQueue alloc] init]; _mdnsMan = [[MDNSManager alloc] initWithCallback:self]; [CryptoManager generateKeyPairUsingSSL]; _uniqueId = [IdManager getUniqueId]; _cert = [CryptoManager readCertFromFile]; return self; } - (void) discoverHost:(NSString *)hostAddress withCallback:(void (^)(TemporaryHost *, NSString*))callback { HttpManager* hMan = [[HttpManager alloc] initWithHost:hostAddress uniqueId:_uniqueId serverCert:nil]; ServerInfoResponse* serverInfoResponse = [[ServerInfoResponse alloc] init]; [hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResponse withUrlRequest:[hMan newServerInfoRequest:false] fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]]; TemporaryHost* host = nil; if ([serverInfoResponse isStatusOk]) { host = [[TemporaryHost alloc] init]; host.activeAddress = host.address = hostAddress; host.state = StateOnline; [serverInfoResponse populateHost:host]; if (![self addHostToDiscovery:host]) { callback(nil, @"Host information updated"); } else { callback(host, nil); } } else { callback(nil, @"Could not connect to host. Ensure GameStream is enabled in GeForce Experience on your PC."); } } - (void) startDiscovery { if (shouldDiscover) { return; } Log(LOG_I, @"Starting discovery"); shouldDiscover = YES; [_mdnsMan searchForHosts]; @synchronized (_hostQueue) { for (TemporaryHost* host in _hostQueue) { [_opQueue addOperation:[self createWorkerForHost:host]]; } } } - (void) stopDiscovery { if (!shouldDiscover) { return; } Log(LOG_I, @"Stopping discovery"); shouldDiscover = NO; [_mdnsMan stopSearching]; [_opQueue cancelAllOperations]; } - (void) stopDiscoveryBlocking { Log(LOG_I, @"Stopping discovery and waiting for workers to stop"); if (shouldDiscover) { shouldDiscover = NO; [_mdnsMan stopSearching]; [_opQueue cancelAllOperations]; } // Ensure we always wait, just in case discovery // was stopped already but in an async manner that // left operations in progress. [_opQueue waitUntilAllOperationsAreFinished]; Log(LOG_I, @"All discovery workers stopped"); } - (BOOL) addHostToDiscovery:(TemporaryHost *)host { if (host.uuid.length == 0) { return NO; } TemporaryHost *existingHost = [self getHostInDiscovery:host.uuid]; if (existingHost != nil) { // NB: Our logic here depends on the fact that we never propagate // the entire TemporaryHost to existingHost. In particular, when mDNS // discovers a PC and we poll it, we will do so over HTTP which will // not have accurate pair state. The fields explicitly copied below // are accurate though. // Update address of existing host if (host.address != nil) { existingHost.address = host.address; } if (host.localAddress != nil) { existingHost.localAddress = host.localAddress; } if (host.ipv6Address != nil) { existingHost.ipv6Address = host.ipv6Address; } if (host.externalAddress != nil) { existingHost.externalAddress = host.externalAddress; } existingHost.activeAddress = host.activeAddress; existingHost.state = host.state; return NO; } else { @synchronized (_hostQueue) { [_hostQueue addObject:host]; if (shouldDiscover) { [_opQueue addOperation:[self createWorkerForHost:host]]; } } return YES; } } - (void) removeHostFromDiscovery:(TemporaryHost *)host { @synchronized (_hostQueue) { for (DiscoveryWorker* worker in [_opQueue operations]) { if ([worker getHost] == host) { [worker cancel]; } } [_hostQueue removeObject:host]; } } // Override from MDNSCallback - called in a worker thread - (void)updateHost:(TemporaryHost*)host { // Discover the hosts before adding to eliminate duplicates Log(LOG_D, @"Found host through MDNS: %@:", host.name); // Since this is on a background thread, we do not need to use the opQueue DiscoveryWorker* worker = (DiscoveryWorker*)[self createWorkerForHost:host]; [worker discoverHost]; if ([self addHostToDiscovery:host]) { Log(LOG_I, @"Found new host through MDNS: %@:", host.name); @synchronized (_hostQueue) { [_callback updateAllHosts:_hostQueue]; } } else { Log(LOG_D, @"Found existing host through MDNS: %@", host.name); } } - (TemporaryHost*) getHostInDiscovery:(NSString*)uuidString { @synchronized (_hostQueue) { for (TemporaryHost* discoveredHost in _hostQueue) { if (discoveredHost.uuid.length > 0 && [discoveredHost.uuid isEqualToString:uuidString]) { return discoveredHost; } } } return nil; } - (NSOperation*) createWorkerForHost:(TemporaryHost*)host { DiscoveryWorker* worker = [[DiscoveryWorker alloc] initWithHost:host uniqueId:_uniqueId]; return worker; } @end