service_discovery_client_mac.mm revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 14using local_discovery::ServiceWatcherImplMac; 15using local_discovery::ServiceResolverImplMac; 16 17@interface NetServiceBrowserDelegate 18 : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> { 19 @private 20 ServiceWatcherImplMac* serviceWatcherImpl_; // weak. 21 base::scoped_nsobject<NSMutableArray> services_; 22} 23 24- (id)initWithServiceWatcher:(ServiceWatcherImplMac*)serviceWatcherImpl; 25 26@end 27 28@interface NetServiceDelegate : NSObject <NSNetServiceDelegate> { 29 @private 30 ServiceResolverImplMac* serviceResolverImpl_; 31} 32 33-(id) initWithServiceResolver:(ServiceResolverImplMac*)serviceResolverImpl; 34 35@end 36 37namespace local_discovery { 38 39namespace { 40 41const NSTimeInterval kResolveTimeout = 10.0; 42 43// Extracts the instance name, name type and domain from a full service name or 44// the service type and domain from a service type. Returns true if successful. 45// TODO(justinlin): This current only handles service names with format 46// <name>._<protocol2>._<protocol1>.<domain>. Service names with 47// subtypes will not parse correctly: 48// <name>._<type>._<sub>._<protocol2>._<protocol1>.<domain>. 49bool ExtractServiceInfo(const std::string& service, 50 bool is_service_name, 51 std::string* instance, 52 std::string* type, 53 std::string* domain) { 54 if (service.empty()) 55 return false; 56 57 const size_t last_period = service.find_last_of('.'); 58 if (last_period == std::string::npos || service.length() <= last_period) 59 return false; 60 61 if (!is_service_name) { 62 *instance = std::string(); 63 *type = service.substr(0, last_period) + "."; 64 } else { 65 // Find third last period that delimits type and instance name. 66 size_t type_period = last_period; 67 for (int i = 0; i < 2; ++i) { 68 type_period = service.find_last_of('.', type_period - 1); 69 if (type_period == std::string::npos) 70 return false; 71 } 72 73 *instance = service.substr(0, type_period); 74 *type = service.substr(type_period + 1, last_period - type_period); 75 } 76 *domain = service.substr(last_period + 1) + "."; 77 78 return !domain->empty() && 79 !type->empty() && 80 (!is_service_name || !instance->empty()); 81} 82 83void ParseTxtRecord(NSData* record, std::vector<std::string>* output) { 84 if (record == nil || [record length] <= 1) 85 return; 86 87 VLOG(1) << "ParseTxtRecord: " << [record length]; 88 89 const char* record_bytes = reinterpret_cast<const char*>([record bytes]); 90 const char* const record_end = record_bytes + [record length]; 91 // TODO(justinlin): More strict bounds checking. 92 while (record_bytes < record_end) { 93 uint8 size = *record_bytes++; 94 if (size <= 0) 95 continue; 96 97 if (record_bytes + size <= record_end) { 98 VLOG(1) << "TxtRecord: " 99 << std::string(record_bytes, static_cast<size_t>(size)); 100 output->push_back( 101 [[[NSString alloc] initWithBytes:record_bytes 102 length:size 103 encoding:NSUTF8StringEncoding] UTF8String]); 104 } 105 record_bytes += size; 106 } 107} 108 109} // namespace 110 111ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {} 112ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {} 113 114scoped_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher( 115 const std::string& service_type, 116 const ServiceWatcher::UpdatedCallback& callback) { 117 VLOG(1) << "CreateServiceWatcher: " << service_type; 118 return scoped_ptr<ServiceWatcher>(new ServiceWatcherImplMac(service_type, 119 callback)); 120} 121 122scoped_ptr<ServiceResolver> ServiceDiscoveryClientMac::CreateServiceResolver( 123 const std::string& service_name, 124 const ServiceResolver::ResolveCompleteCallback& callback) { 125 VLOG(1) << "CreateServiceResolver: " << service_name; 126 return scoped_ptr<ServiceResolver>(new ServiceResolverImplMac(service_name, 127 callback)); 128} 129 130scoped_ptr<LocalDomainResolver> 131ServiceDiscoveryClientMac::CreateLocalDomainResolver( 132 const std::string& domain, 133 net::AddressFamily address_family, 134 const LocalDomainResolver::IPAddressCallback& callback) { 135 NOTIMPLEMENTED(); // TODO(justinlin): Implement. 136 VLOG(1) << "CreateLocalDomainResolver: " << domain; 137 return scoped_ptr<LocalDomainResolver>(); 138} 139 140ServiceWatcherImplMac::ServiceWatcherImplMac( 141 const std::string& service_type, 142 const ServiceWatcher::UpdatedCallback& callback) 143 : service_type_(service_type), callback_(callback), started_(false) {} 144 145ServiceWatcherImplMac::~ServiceWatcherImplMac() {} 146 147void ServiceWatcherImplMac::Start() { 148 DCHECK(!started_); 149 VLOG(1) << "ServiceWatcherImplMac::Start"; 150 delegate_.reset([[NetServiceBrowserDelegate alloc] 151 initWithServiceWatcher:this]); 152 browser_.reset([[NSNetServiceBrowser alloc] init]); 153 [browser_ setDelegate:delegate_]; 154 started_ = true; 155} 156 157void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) { 158 DCHECK(started_); 159 VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices"; 160 std::string instance; 161 std::string type; 162 std::string domain; 163 164 if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain)) 165 return; 166 167 DCHECK(instance.empty()); 168 DVLOG(1) << "Listening for service type '" << type 169 << "' on domain '" << domain << "'"; 170 171 base::Time start_time = base::Time::Now(); 172 [browser_ searchForServicesOfType:[NSString stringWithUTF8String:type.c_str()] 173 inDomain:[NSString stringWithUTF8String:domain.c_str()]]; 174 UMA_HISTOGRAM_TIMES("LocalDiscovery.MacBrowseCallTimes", 175 base::Time::Now() - start_time); 176} 177 178void ServiceWatcherImplMac::SetActivelyRefreshServices( 179 bool actively_refresh_services) { 180 DCHECK(started_); 181 VLOG(1) << "ServiceWatcherImplMac::SetActivelyRefreshServices"; 182} 183 184std::string ServiceWatcherImplMac::GetServiceType() const { 185 return service_type_; 186} 187 188void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update, 189 const std::string& service) { 190 VLOG(1) << "ServiceWatcherImplMac::OnServicesUpdate: " 191 << service + "." + service_type_; 192 callback_.Run(update, service + "." + service_type_); 193} 194 195ServiceResolverImplMac::ServiceResolverImplMac( 196 const std::string& service_name, 197 const ServiceResolver::ResolveCompleteCallback& callback) 198 : service_name_(service_name), callback_(callback), has_resolved_(false) { 199 std::string instance; 200 std::string type; 201 std::string domain; 202 203 if (ExtractServiceInfo(service_name, true, &instance, &type, &domain)) { 204 VLOG(1) << "ServiceResolverImplMac::ServiceResolverImplMac: Success"; 205 delegate_.reset([[NetServiceDelegate alloc] initWithServiceResolver:this]); 206 service_.reset( 207 [[NSNetService alloc] 208 initWithDomain:[[NSString alloc] initWithUTF8String:domain.c_str()] 209 type:[[NSString alloc] initWithUTF8String:type.c_str()] 210 name:[[NSString alloc] initWithUTF8String:instance.c_str()]]); 211 [service_ setDelegate:delegate_]; 212 } 213 VLOG(1) << "ServiceResolverImplMac::ServiceResolverImplMac: " 214 << service_name 215 << ", instance: " << instance 216 << ", type: " << type 217 << ", domain: " << domain; 218} 219 220ServiceResolverImplMac::~ServiceResolverImplMac() {} 221 222void ServiceResolverImplMac::StartResolving() { 223 if (!service_.get()) 224 return; 225 226 VLOG(1) << "Resolving service " << service_name_; 227 [service_ resolveWithTimeout:kResolveTimeout]; 228} 229 230std::string ServiceResolverImplMac::GetName() const { 231 return service_name_; 232} 233 234void ServiceResolverImplMac::OnResolveUpdate(RequestStatus status) { 235 VLOG(1) << "ServiceResolverImplMac::OnResolveUpdate: " << service_name_ 236 << ", " << status; 237 if (status == STATUS_SUCCESS) { 238 service_description_.service_name = service_name_; 239 240 for (NSData* address in [service_ addresses]) { 241 const void* bytes = [address bytes]; 242 // TODO(justinlin): Handle IPv6 addresses? 243 if (static_cast<const sockaddr*>(bytes)->sa_family == AF_INET) { 244 const sockaddr_in* sock = static_cast<const sockaddr_in*>(bytes); 245 char addr[INET_ADDRSTRLEN]; 246 inet_ntop(AF_INET, &sock->sin_addr, addr, INET_ADDRSTRLEN); 247 service_description_.address = 248 net::HostPortPair(addr, ntohs(sock->sin_port)); 249 net::ParseIPLiteralToNumber(addr, &service_description_.ip_address); 250 break; 251 } 252 } 253 254 ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata); 255 256 // TODO(justinlin): Implement last_seen. 257 service_description_.last_seen = base::Time::Now(); 258 callback_.Run(status, service_description_); 259 } else { 260 callback_.Run(status, ServiceDescription()); 261 } 262 has_resolved_ = true; 263} 264 265void ServiceResolverImplMac::SetServiceForTesting( 266 base::scoped_nsobject<NSNetService> service) { 267 service_ = service; 268} 269 270} // namespace local_discovery 271 272@implementation NetServiceBrowserDelegate 273 274- (id)initWithServiceWatcher:(ServiceWatcherImplMac*)serviceWatcherImpl { 275 if ((self = [super init])) { 276 serviceWatcherImpl_ = serviceWatcherImpl; 277 services_.reset([[NSMutableArray alloc] initWithCapacity:1]); 278 } 279 return self; 280} 281 282- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser 283 didFindService:(NSNetService *)netService 284 moreComing:(BOOL)moreServicesComing { 285 // Start monitoring this service for updates. 286 [netService setDelegate:self]; 287 [netService startMonitoring]; 288 [services_ addObject:netService]; 289 290 serviceWatcherImpl_->OnServicesUpdate( 291 local_discovery::ServiceWatcher::UPDATE_ADDED, 292 [[netService name] UTF8String]); 293} 294 295- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser 296 didRemoveService:(NSNetService *)netService 297 moreComing:(BOOL)moreServicesComing { 298 serviceWatcherImpl_->OnServicesUpdate( 299 local_discovery::ServiceWatcher::UPDATE_REMOVED, 300 [[netService name] UTF8String]); 301 302 NSUInteger index = [services_ indexOfObject:netService]; 303 if (index != NSNotFound) { 304 // Stop monitoring this service for updates. 305 [[services_ objectAtIndex:index] stopMonitoring]; 306 [services_ removeObjectAtIndex:index]; 307 } 308} 309 310- (void)netService:(NSNetService *)sender 311 didUpdateTXTRecordData:(NSData *)data { 312 serviceWatcherImpl_->OnServicesUpdate( 313 local_discovery::ServiceWatcher::UPDATE_CHANGED, 314 [[sender name] UTF8String]); 315} 316 317@end 318 319@implementation NetServiceDelegate 320 321-(id) initWithServiceResolver:(ServiceResolverImplMac*)serviceResolverImpl { 322 if ((self = [super init])) { 323 serviceResolverImpl_ = serviceResolverImpl; 324 } 325 return self; 326} 327 328- (void)netServiceDidResolveAddress:(NSNetService *)sender { 329 serviceResolverImpl_->OnResolveUpdate( 330 local_discovery::ServiceResolver::STATUS_SUCCESS); 331} 332 333- (void)netService:(NSNetService *)sender 334 didNotResolve:(NSDictionary *)errorDict { 335 serviceResolverImpl_->OnResolveUpdate( 336 local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT); 337} 338 339@end 340