service_discovery_client_mac.mm revision effb81e5f8246d0db0270817048dc992db66e9fb
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/local_discovery/service_discovery_client_mac.h" 6 7#import <Foundation/Foundation.h> 8#import <arpa/inet.h> 9#import <net/if_dl.h> 10 11#include "base/memory/singleton.h" 12#include "base/metrics/histogram.h" 13#include "base/threading/thread.h" 14 15using local_discovery::ServiceWatcherImplMac; 16using local_discovery::ServiceResolverImplMac; 17 18@interface NetServiceBrowserDelegate 19 : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> { 20 @private 21 ServiceWatcherImplMac::NetServiceBrowserContainer* container_; // weak. 22 base::scoped_nsobject<NSMutableArray> services_; 23} 24 25- (id)initWithContainer: 26 (ServiceWatcherImplMac::NetServiceBrowserContainer*)serviceWatcherImpl; 27 28@end 29 30@interface NetServiceDelegate : NSObject <NSNetServiceDelegate> { 31 @private 32 ServiceResolverImplMac::NetServiceContainer* container_; 33} 34 35- (id)initWithContainer: 36 (ServiceResolverImplMac::NetServiceContainer*)serviceResolverImpl; 37 38@end 39 40namespace local_discovery { 41 42namespace { 43 44const char kServiceDiscoveryThreadName[] = "Service Discovery Thread"; 45 46const NSTimeInterval kResolveTimeout = 10.0; 47 48// Extracts the instance name, name type and domain from a full service name or 49// the service type and domain from a service type. Returns true if successful. 50// TODO(justinlin): This current only handles service names with format 51// <name>._<protocol2>._<protocol1>.<domain>. Service names with 52// subtypes will not parse correctly: 53// <name>._<type>._<sub>._<protocol2>._<protocol1>.<domain>. 54bool ExtractServiceInfo(const std::string& service, 55 bool is_service_name, 56 std::string* instance, 57 std::string* type, 58 std::string* domain) { 59 if (service.empty()) 60 return false; 61 62 const size_t last_period = service.find_last_of('.'); 63 if (last_period == std::string::npos || service.length() <= last_period) 64 return false; 65 66 if (!is_service_name) { 67 *instance = std::string(); 68 *type = service.substr(0, last_period) + "."; 69 } else { 70 // Find third last period that delimits type and instance name. 71 size_t type_period = last_period; 72 for (int i = 0; i < 2; ++i) { 73 type_period = service.find_last_of('.', type_period - 1); 74 if (type_period == std::string::npos) 75 return false; 76 } 77 78 *instance = service.substr(0, type_period); 79 *type = service.substr(type_period + 1, last_period - type_period); 80 } 81 *domain = service.substr(last_period + 1) + "."; 82 83 return !domain->empty() && 84 !type->empty() && 85 (!is_service_name || !instance->empty()); 86} 87 88void ParseTxtRecord(NSData* record, std::vector<std::string>* output) { 89 if (record == nil || [record length] <= 1) 90 return; 91 92 VLOG(1) << "ParseTxtRecord: " << [record length]; 93 94 const char* record_bytes = reinterpret_cast<const char*>([record bytes]); 95 const char* const record_end = record_bytes + [record length]; 96 // TODO(justinlin): More strict bounds checking. 97 while (record_bytes < record_end) { 98 uint8 size = *record_bytes++; 99 if (size <= 0) 100 continue; 101 102 if (record_bytes + size <= record_end) { 103 VLOG(1) << "TxtRecord: " 104 << std::string(record_bytes, static_cast<size_t>(size)); 105 output->push_back( 106 [[[NSString alloc] initWithBytes:record_bytes 107 length:size 108 encoding:NSUTF8StringEncoding] UTF8String]); 109 } 110 record_bytes += size; 111 } 112} 113 114} // namespace 115 116ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {} 117ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {} 118 119scoped_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher( 120 const std::string& service_type, 121 const ServiceWatcher::UpdatedCallback& callback) { 122 StartThreadIfNotStarted(); 123 VLOG(1) << "CreateServiceWatcher: " << service_type; 124 return scoped_ptr<ServiceWatcher>(new ServiceWatcherImplMac( 125 service_type, callback, service_discovery_thread_->message_loop_proxy())); 126} 127 128scoped_ptr<ServiceResolver> ServiceDiscoveryClientMac::CreateServiceResolver( 129 const std::string& service_name, 130 const ServiceResolver::ResolveCompleteCallback& callback) { 131 StartThreadIfNotStarted(); 132 VLOG(1) << "CreateServiceResolver: " << service_name; 133 return scoped_ptr<ServiceResolver>(new ServiceResolverImplMac( 134 service_name, callback, service_discovery_thread_->message_loop_proxy())); 135} 136 137scoped_ptr<LocalDomainResolver> 138ServiceDiscoveryClientMac::CreateLocalDomainResolver( 139 const std::string& domain, 140 net::AddressFamily address_family, 141 const LocalDomainResolver::IPAddressCallback& callback) { 142 NOTIMPLEMENTED(); // TODO(noamsml): Implement. 143 VLOG(1) << "CreateLocalDomainResolver: " << domain; 144 return scoped_ptr<LocalDomainResolver>(); 145} 146 147void ServiceDiscoveryClientMac::StartThreadIfNotStarted() { 148 if (!service_discovery_thread_) { 149 service_discovery_thread_.reset( 150 new base::Thread(kServiceDiscoveryThreadName)); 151 // Only TYPE_UI uses an NSRunLoop. 152 base::Thread::Options options(base::MessageLoop::TYPE_UI, 0); 153 service_discovery_thread_->StartWithOptions(options); 154 } 155} 156 157ServiceWatcherImplMac::NetServiceBrowserContainer::NetServiceBrowserContainer( 158 const std::string& service_type, 159 const ServiceWatcher::UpdatedCallback& callback, 160 scoped_refptr<base::MessageLoopProxy> service_discovery_runner) 161 : service_type_(service_type), 162 callback_(callback), 163 callback_runner_(base::MessageLoopProxy::current()), 164 service_discovery_runner_(service_discovery_runner), 165 weak_factory_(this) {} 166 167ServiceWatcherImplMac::NetServiceBrowserContainer:: 168 ~NetServiceBrowserContainer() { 169 DCHECK(IsOnServiceDiscoveryThread()); 170} 171 172void ServiceWatcherImplMac::NetServiceBrowserContainer::Start() { 173 service_discovery_runner_->PostTask( 174 FROM_HERE, 175 base::Bind(&NetServiceBrowserContainer::StartOnDiscoveryThread, 176 weak_factory_.GetWeakPtr())); 177} 178 179void ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverNewServices() { 180 service_discovery_runner_->PostTask( 181 FROM_HERE, 182 base::Bind(&NetServiceBrowserContainer::DiscoverOnDiscoveryThread, 183 weak_factory_.GetWeakPtr())); 184} 185 186void 187ServiceWatcherImplMac::NetServiceBrowserContainer::StartOnDiscoveryThread() { 188 DCHECK(IsOnServiceDiscoveryThread()); 189 190 delegate_.reset([[NetServiceBrowserDelegate alloc] initWithContainer:this]); 191 browser_.reset([[NSNetServiceBrowser alloc] init]); 192 [browser_ setDelegate:delegate_]; 193} 194 195void 196ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverOnDiscoveryThread() { 197 DCHECK(IsOnServiceDiscoveryThread()); 198 std::string instance; 199 std::string type; 200 std::string domain; 201 202 if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain)) 203 return; 204 205 DCHECK(instance.empty()); 206 DVLOG(1) << "Listening for service type '" << type 207 << "' on domain '" << domain << "'"; 208 209 base::Time start_time = base::Time::Now(); 210 [browser_ searchForServicesOfType:[NSString stringWithUTF8String:type.c_str()] 211 inDomain:[NSString stringWithUTF8String:domain.c_str()]]; 212 UMA_HISTOGRAM_TIMES("LocalDiscovery.MacBrowseCallTimes", 213 base::Time::Now() - start_time); 214} 215 216void ServiceWatcherImplMac::NetServiceBrowserContainer::OnServicesUpdate( 217 ServiceWatcher::UpdateType update, 218 const std::string& service) { 219 callback_runner_->PostTask(FROM_HERE, base::Bind(callback_, update, service)); 220} 221 222void ServiceWatcherImplMac::NetServiceBrowserContainer::DeleteSoon() { 223 service_discovery_runner_->DeleteSoon(FROM_HERE, this); 224} 225 226ServiceWatcherImplMac::ServiceWatcherImplMac( 227 const std::string& service_type, 228 const ServiceWatcher::UpdatedCallback& callback, 229 scoped_refptr<base::MessageLoopProxy> service_discovery_runner) 230 : service_type_(service_type), 231 callback_(callback), 232 started_(false), 233 weak_factory_(this) { 234 container_.reset(new NetServiceBrowserContainer( 235 service_type, 236 base::Bind(&ServiceWatcherImplMac::OnServicesUpdate, 237 weak_factory_.GetWeakPtr()), 238 service_discovery_runner)); 239} 240 241ServiceWatcherImplMac::~ServiceWatcherImplMac() {} 242 243void ServiceWatcherImplMac::Start() { 244 DCHECK(!started_); 245 VLOG(1) << "ServiceWatcherImplMac::Start"; 246 container_->Start(); 247 started_ = true; 248} 249 250void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) { 251 DCHECK(started_); 252 VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices"; 253 container_->DiscoverNewServices(); 254} 255 256void ServiceWatcherImplMac::SetActivelyRefreshServices( 257 bool actively_refresh_services) { 258 DCHECK(started_); 259 VLOG(1) << "ServiceWatcherImplMac::SetActivelyRefreshServices"; 260} 261 262std::string ServiceWatcherImplMac::GetServiceType() const { 263 return service_type_; 264} 265 266void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update, 267 const std::string& service) { 268 VLOG(1) << "ServiceWatcherImplMac::OnServicesUpdate: " 269 << service + "." + service_type_; 270 callback_.Run(update, service + "." + service_type_); 271} 272 273ServiceResolverImplMac::NetServiceContainer::NetServiceContainer( 274 const std::string& service_name, 275 const ServiceResolver::ResolveCompleteCallback& callback, 276 scoped_refptr<base::MessageLoopProxy> service_discovery_runner) 277 : service_name_(service_name), 278 callback_(callback), 279 callback_runner_(base::MessageLoopProxy::current()), 280 service_discovery_runner_(service_discovery_runner), 281 weak_factory_(this) {} 282 283ServiceResolverImplMac::NetServiceContainer::~NetServiceContainer() { 284 DCHECK(IsOnServiceDiscoveryThread()); 285} 286 287void ServiceResolverImplMac::NetServiceContainer::StartResolving() { 288 service_discovery_runner_->PostTask( 289 FROM_HERE, 290 base::Bind(&NetServiceContainer::StartResolvingOnDiscoveryThread, 291 weak_factory_.GetWeakPtr())); 292} 293 294void ServiceResolverImplMac::NetServiceContainer::DeleteSoon() { 295 service_discovery_runner_->DeleteSoon(FROM_HERE, this); 296} 297 298void 299ServiceResolverImplMac::NetServiceContainer::StartResolvingOnDiscoveryThread() { 300 DCHECK(IsOnServiceDiscoveryThread()); 301 std::string instance; 302 std::string type; 303 std::string domain; 304 305 // The service object is set ahead of time by tests. 306 if (service_) 307 return; 308 309 if (ExtractServiceInfo(service_name_, true, &instance, &type, &domain)) { 310 VLOG(1) << "ServiceResolverImplMac::ServiceResolverImplMac::" 311 << "StartResolvingOnDiscoveryThread: Success"; 312 delegate_.reset([[NetServiceDelegate alloc] initWithContainer:this]); 313 service_.reset( 314 [[NSNetService alloc] 315 initWithDomain:[[NSString alloc] initWithUTF8String:domain.c_str()] 316 type:[[NSString alloc] initWithUTF8String:type.c_str()] 317 name:[[NSString alloc] initWithUTF8String:instance.c_str()]]); 318 [service_ setDelegate:delegate_]; 319 320 [service_ resolveWithTimeout:kResolveTimeout]; 321 } 322 323 VLOG(1) << "ServiceResolverImplMac::NetServiceContainer::" 324 << "StartResolvingOnDiscoveryThread: " << service_name_ 325 << ", instance: " << instance << ", type: " << type 326 << ", domain: " << domain; 327} 328 329void ServiceResolverImplMac::NetServiceContainer::OnResolveUpdate( 330 RequestStatus status) { 331 if (status == STATUS_SUCCESS) { 332 service_description_.service_name = service_name_; 333 334 for (NSData* address in [service_ addresses]) { 335 const void* bytes = [address bytes]; 336 // TODO(justinlin): Handle IPv6 addresses? 337 if (static_cast<const sockaddr*>(bytes)->sa_family == AF_INET) { 338 const sockaddr_in* sock = static_cast<const sockaddr_in*>(bytes); 339 char addr[INET_ADDRSTRLEN]; 340 inet_ntop(AF_INET, &sock->sin_addr, addr, INET_ADDRSTRLEN); 341 service_description_.address = 342 net::HostPortPair(addr, ntohs(sock->sin_port)); 343 net::ParseIPLiteralToNumber(addr, &service_description_.ip_address); 344 break; 345 } 346 } 347 348 ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata); 349 350 // TODO(justinlin): Implement last_seen. 351 service_description_.last_seen = base::Time::Now(); 352 callback_runner_->PostTask( 353 FROM_HERE, base::Bind(callback_, status, service_description_)); 354 } else { 355 callback_runner_->PostTask( 356 FROM_HERE, base::Bind(callback_, status, ServiceDescription())); 357 } 358} 359 360void ServiceResolverImplMac::NetServiceContainer::SetServiceForTesting( 361 base::scoped_nsobject<NSNetService> service) { 362 service_ = service; 363} 364 365ServiceResolverImplMac::ServiceResolverImplMac( 366 const std::string& service_name, 367 const ServiceResolver::ResolveCompleteCallback& callback, 368 scoped_refptr<base::MessageLoopProxy> service_discovery_runner) 369 : service_name_(service_name), 370 callback_(callback), 371 has_resolved_(false), 372 weak_factory_(this) { 373 container_.reset(new NetServiceContainer( 374 service_name, 375 base::Bind(&ServiceResolverImplMac::OnResolveComplete, 376 weak_factory_.GetWeakPtr()), 377 service_discovery_runner)); 378} 379 380ServiceResolverImplMac::~ServiceResolverImplMac() {} 381 382void ServiceResolverImplMac::StartResolving() { 383 container_->StartResolving(); 384 385 VLOG(1) << "Resolving service " << service_name_; 386} 387 388std::string ServiceResolverImplMac::GetName() const { return service_name_; } 389 390void ServiceResolverImplMac::OnResolveComplete( 391 RequestStatus status, 392 const ServiceDescription& description) { 393 VLOG(1) << "ServiceResolverImplMac::OnResolveComplete: " << service_name_ 394 << ", " << status; 395 396 has_resolved_ = true; 397 398 callback_.Run(status, description); 399} 400 401ServiceResolverImplMac::NetServiceContainer* 402ServiceResolverImplMac::GetContainerForTesting() { 403 return container_.get(); 404} 405 406} // namespace local_discovery 407 408@implementation NetServiceBrowserDelegate 409 410- (id)initWithContainer: 411 (ServiceWatcherImplMac::NetServiceBrowserContainer*)container { 412 if ((self = [super init])) { 413 container_ = container; 414 services_.reset([[NSMutableArray alloc] initWithCapacity:1]); 415 } 416 return self; 417} 418 419- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser 420 didFindService:(NSNetService *)netService 421 moreComing:(BOOL)moreServicesComing { 422 // Start monitoring this service for updates. 423 [netService setDelegate:self]; 424 [netService startMonitoring]; 425 [services_ addObject:netService]; 426 427 container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_ADDED, 428 [[netService name] UTF8String]); 429} 430 431- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser 432 didRemoveService:(NSNetService *)netService 433 moreComing:(BOOL)moreServicesComing { 434 container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_REMOVED, 435 [[netService name] UTF8String]); 436 437 NSUInteger index = [services_ indexOfObject:netService]; 438 if (index != NSNotFound) { 439 // Stop monitoring this service for updates. 440 [[services_ objectAtIndex:index] stopMonitoring]; 441 [services_ removeObjectAtIndex:index]; 442 } 443} 444 445- (void)netService:(NSNetService *)sender 446 didUpdateTXTRecordData:(NSData *)data { 447 container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_CHANGED, 448 [[sender name] UTF8String]); 449} 450 451@end 452 453@implementation NetServiceDelegate 454 455- (id)initWithContainer: 456 (ServiceResolverImplMac::NetServiceContainer*)container { 457 if ((self = [super init])) { 458 container_ = container; 459 } 460 return self; 461} 462 463- (void)netServiceDidResolveAddress:(NSNetService *)sender { 464 container_->OnResolveUpdate(local_discovery::ServiceResolver::STATUS_SUCCESS); 465} 466 467- (void)netService:(NSNetService *)sender 468 didNotResolve:(NSDictionary *)errorDict { 469 container_->OnResolveUpdate( 470 local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT); 471} 472 473@end 474