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.
5 #include "chrome/browser/local_discovery/service_discovery_client_mac.h"
8 #import <Foundation/Foundation.h>
11 #include "base/memory/singleton.h"
12 #include "base/metrics/histogram.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/thread_task_runner_handle.h"
15 #include "base/threading/thread.h"
16 #include "net/base/ip_address_number.h"
18 using local_discovery::ServiceWatcherImplMac;
19 using local_discovery::ServiceResolverImplMac;
21 @interface NetServiceBrowserDelegate
22 : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> {
24 ServiceWatcherImplMac::NetServiceBrowserContainer* container_; // weak.
25 base::scoped_nsobject<NSMutableArray> services_;
28 - (id)initWithContainer:
29 (ServiceWatcherImplMac::NetServiceBrowserContainer*)serviceWatcherImpl;
33 @interface NetServiceDelegate : NSObject <NSNetServiceDelegate> {
35 ServiceResolverImplMac::NetServiceContainer* container_;
38 - (id)initWithContainer:
39 (ServiceResolverImplMac::NetServiceContainer*)serviceResolverImpl;
43 namespace local_discovery {
47 const char kServiceDiscoveryThreadName[] = "Service Discovery Thread";
49 const NSTimeInterval kResolveTimeout = 10.0;
51 // Extracts the instance name, name type and domain from a full service name or
52 // the service type and domain from a service type. Returns true if successful.
53 // TODO(justinlin): This current only handles service names with format
54 // <name>._<protocol2>._<protocol1>.<domain>. Service names with
55 // subtypes will not parse correctly:
56 // <name>._<type>._<sub>._<protocol2>._<protocol1>.<domain>.
57 bool ExtractServiceInfo(const std::string& service,
59 std::string* instance,
61 std::string* domain) {
65 const size_t last_period = service.find_last_of('.');
66 if (last_period == std::string::npos || service.length() <= last_period)
69 if (!is_service_name) {
70 *instance = std::string();
71 *type = service.substr(0, last_period) + ".";
73 // Find third last period that delimits type and instance name.
74 size_t type_period = last_period;
75 for (int i = 0; i < 2; ++i) {
76 type_period = service.find_last_of('.', type_period - 1);
77 if (type_period == std::string::npos)
81 *instance = service.substr(0, type_period);
82 *type = service.substr(type_period + 1, last_period - type_period);
84 *domain = service.substr(last_period + 1) + ".";
86 return !domain->empty() &&
88 (!is_service_name || !instance->empty());
91 void ParseTxtRecord(NSData* record, std::vector<std::string>* output) {
92 if (record == nil || [record length] <= 1)
95 VLOG(1) << "ParseTxtRecord: " << [record length];
97 const char* record_bytes = reinterpret_cast<const char*>([record bytes]);
98 const char* const record_end = record_bytes + [record length];
99 // TODO(justinlin): More strict bounds checking.
100 while (record_bytes < record_end) {
101 uint8 size = *record_bytes++;
105 if (record_bytes + size <= record_end) {
106 VLOG(1) << "TxtRecord: "
107 << std::string(record_bytes, static_cast<size_t>(size));
109 [[[NSString alloc] initWithBytes:record_bytes
111 encoding:NSUTF8StringEncoding] UTF8String]);
113 record_bytes += size;
119 ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {}
120 ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {}
122 scoped_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher(
123 const std::string& service_type,
124 const ServiceWatcher::UpdatedCallback& callback) {
125 StartThreadIfNotStarted();
126 VLOG(1) << "CreateServiceWatcher: " << service_type;
127 return scoped_ptr<ServiceWatcher>(new ServiceWatcherImplMac(
128 service_type, callback, service_discovery_thread_->task_runner()));
131 scoped_ptr<ServiceResolver> ServiceDiscoveryClientMac::CreateServiceResolver(
132 const std::string& service_name,
133 const ServiceResolver::ResolveCompleteCallback& callback) {
134 StartThreadIfNotStarted();
135 VLOG(1) << "CreateServiceResolver: " << service_name;
136 return scoped_ptr<ServiceResolver>(new ServiceResolverImplMac(
137 service_name, callback, service_discovery_thread_->task_runner()));
140 scoped_ptr<LocalDomainResolver>
141 ServiceDiscoveryClientMac::CreateLocalDomainResolver(
142 const std::string& domain,
143 net::AddressFamily address_family,
144 const LocalDomainResolver::IPAddressCallback& callback) {
145 NOTIMPLEMENTED(); // TODO(noamsml): Implement.
146 VLOG(1) << "CreateLocalDomainResolver: " << domain;
147 return scoped_ptr<LocalDomainResolver>();
150 void ServiceDiscoveryClientMac::StartThreadIfNotStarted() {
151 if (!service_discovery_thread_) {
152 service_discovery_thread_.reset(
153 new base::Thread(kServiceDiscoveryThreadName));
154 // Only TYPE_UI uses an NSRunLoop.
155 base::Thread::Options options(base::MessageLoop::TYPE_UI, 0);
156 service_discovery_thread_->StartWithOptions(options);
160 ServiceWatcherImplMac::NetServiceBrowserContainer::NetServiceBrowserContainer(
161 const std::string& service_type,
162 const ServiceWatcher::UpdatedCallback& callback,
163 scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
164 : service_type_(service_type),
166 callback_runner_(base::ThreadTaskRunnerHandle::Get()),
167 service_discovery_runner_(service_discovery_runner),
168 weak_factory_(this) {
171 ServiceWatcherImplMac::NetServiceBrowserContainer::
172 ~NetServiceBrowserContainer() {
173 DCHECK(IsOnServiceDiscoveryThread());
176 void ServiceWatcherImplMac::NetServiceBrowserContainer::Start() {
177 service_discovery_runner_->PostTask(
179 base::Bind(&NetServiceBrowserContainer::StartOnDiscoveryThread,
180 weak_factory_.GetWeakPtr()));
183 void ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverNewServices() {
184 service_discovery_runner_->PostTask(
186 base::Bind(&NetServiceBrowserContainer::DiscoverOnDiscoveryThread,
187 weak_factory_.GetWeakPtr()));
191 ServiceWatcherImplMac::NetServiceBrowserContainer::StartOnDiscoveryThread() {
192 DCHECK(IsOnServiceDiscoveryThread());
194 delegate_.reset([[NetServiceBrowserDelegate alloc] initWithContainer:this]);
195 browser_.reset([[NSNetServiceBrowser alloc] init]);
196 [browser_ setDelegate:delegate_];
200 ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverOnDiscoveryThread() {
201 DCHECK(IsOnServiceDiscoveryThread());
202 std::string instance;
206 if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain))
209 DCHECK(instance.empty());
210 DVLOG(1) << "Listening for service type '" << type
211 << "' on domain '" << domain << "'";
213 base::Time start_time = base::Time::Now();
214 [browser_ searchForServicesOfType:[NSString stringWithUTF8String:type.c_str()]
215 inDomain:[NSString stringWithUTF8String:domain.c_str()]];
216 UMA_HISTOGRAM_TIMES("LocalDiscovery.MacBrowseCallTimes",
217 base::Time::Now() - start_time);
220 void ServiceWatcherImplMac::NetServiceBrowserContainer::OnServicesUpdate(
221 ServiceWatcher::UpdateType update,
222 const std::string& service) {
223 callback_runner_->PostTask(FROM_HERE, base::Bind(callback_, update, service));
226 void ServiceWatcherImplMac::NetServiceBrowserContainer::DeleteSoon() {
227 service_discovery_runner_->DeleteSoon(FROM_HERE, this);
230 ServiceWatcherImplMac::ServiceWatcherImplMac(
231 const std::string& service_type,
232 const ServiceWatcher::UpdatedCallback& callback,
233 scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
234 : service_type_(service_type),
237 weak_factory_(this) {
238 container_.reset(new NetServiceBrowserContainer(
240 base::Bind(&ServiceWatcherImplMac::OnServicesUpdate,
241 weak_factory_.GetWeakPtr()),
242 service_discovery_runner));
245 ServiceWatcherImplMac::~ServiceWatcherImplMac() {}
247 void ServiceWatcherImplMac::Start() {
249 VLOG(1) << "ServiceWatcherImplMac::Start";
254 void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) {
256 VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices";
257 container_->DiscoverNewServices();
260 void ServiceWatcherImplMac::SetActivelyRefreshServices(
261 bool actively_refresh_services) {
263 VLOG(1) << "ServiceWatcherImplMac::SetActivelyRefreshServices";
266 std::string ServiceWatcherImplMac::GetServiceType() const {
267 return service_type_;
270 void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update,
271 const std::string& service) {
272 VLOG(1) << "ServiceWatcherImplMac::OnServicesUpdate: "
273 << service + "." + service_type_;
274 callback_.Run(update, service + "." + service_type_);
277 ServiceResolverImplMac::NetServiceContainer::NetServiceContainer(
278 const std::string& service_name,
279 const ServiceResolver::ResolveCompleteCallback& callback,
280 scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
281 : service_name_(service_name),
283 callback_runner_(base::ThreadTaskRunnerHandle::Get()),
284 service_discovery_runner_(service_discovery_runner),
285 weak_factory_(this) {
288 ServiceResolverImplMac::NetServiceContainer::~NetServiceContainer() {
289 DCHECK(IsOnServiceDiscoveryThread());
292 void ServiceResolverImplMac::NetServiceContainer::StartResolving() {
293 service_discovery_runner_->PostTask(
295 base::Bind(&NetServiceContainer::StartResolvingOnDiscoveryThread,
296 weak_factory_.GetWeakPtr()));
299 void ServiceResolverImplMac::NetServiceContainer::DeleteSoon() {
300 service_discovery_runner_->DeleteSoon(FROM_HERE, this);
304 ServiceResolverImplMac::NetServiceContainer::StartResolvingOnDiscoveryThread() {
305 DCHECK(IsOnServiceDiscoveryThread());
306 std::string instance;
310 // The service object is set ahead of time by tests.
314 if (!ExtractServiceInfo(service_name_, true, &instance, &type, &domain))
315 return OnResolveUpdate(ServiceResolver::STATUS_KNOWN_NONEXISTENT);
317 VLOG(1) << "ServiceResolverImplMac::ServiceResolverImplMac::"
318 << "StartResolvingOnDiscoveryThread: Success";
319 delegate_.reset([[NetServiceDelegate alloc] initWithContainer:this]);
321 [[NSNetService alloc]
322 initWithDomain:[[NSString alloc] initWithUTF8String:domain.c_str()]
323 type:[[NSString alloc] initWithUTF8String:type.c_str()]
324 name:[[NSString alloc] initWithUTF8String:instance.c_str()]]);
325 [service_ setDelegate:delegate_];
327 [service_ resolveWithTimeout:kResolveTimeout];
329 VLOG(1) << "ServiceResolverImplMac::NetServiceContainer::"
330 << "StartResolvingOnDiscoveryThread: " << service_name_
331 << ", instance: " << instance << ", type: " << type
332 << ", domain: " << domain;
335 void ServiceResolverImplMac::NetServiceContainer::OnResolveUpdate(
336 RequestStatus status) {
337 if (status == STATUS_SUCCESS) {
338 service_description_.service_name = service_name_;
340 for (NSData* address in [service_ addresses]) {
341 const void* bytes = [address bytes];
342 // TODO(justinlin): Handle IPv6 addresses?
343 if (static_cast<const sockaddr*>(bytes)->sa_family == AF_INET) {
344 const sockaddr_in* sock = static_cast<const sockaddr_in*>(bytes);
345 char addr[INET_ADDRSTRLEN];
346 inet_ntop(AF_INET, &sock->sin_addr, addr, INET_ADDRSTRLEN);
347 service_description_.address =
348 net::HostPortPair(addr, ntohs(sock->sin_port));
349 net::ParseIPLiteralToNumber(addr, &service_description_.ip_address);
354 ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata);
356 // TODO(justinlin): Implement last_seen.
357 service_description_.last_seen = base::Time::Now();
358 callback_runner_->PostTask(
359 FROM_HERE, base::Bind(callback_, status, service_description_));
361 callback_runner_->PostTask(
362 FROM_HERE, base::Bind(callback_, status, ServiceDescription()));
366 void ServiceResolverImplMac::NetServiceContainer::SetServiceForTesting(
367 base::scoped_nsobject<NSNetService> service) {
371 ServiceResolverImplMac::ServiceResolverImplMac(
372 const std::string& service_name,
373 const ServiceResolver::ResolveCompleteCallback& callback,
374 scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
375 : service_name_(service_name),
377 has_resolved_(false),
378 weak_factory_(this) {
379 container_.reset(new NetServiceContainer(
381 base::Bind(&ServiceResolverImplMac::OnResolveComplete,
382 weak_factory_.GetWeakPtr()),
383 service_discovery_runner));
386 ServiceResolverImplMac::~ServiceResolverImplMac() {}
388 void ServiceResolverImplMac::StartResolving() {
389 container_->StartResolving();
391 VLOG(1) << "Resolving service " << service_name_;
394 std::string ServiceResolverImplMac::GetName() const { return service_name_; }
396 void ServiceResolverImplMac::OnResolveComplete(
397 RequestStatus status,
398 const ServiceDescription& description) {
399 VLOG(1) << "ServiceResolverImplMac::OnResolveComplete: " << service_name_
402 has_resolved_ = true;
404 callback_.Run(status, description);
407 ServiceResolverImplMac::NetServiceContainer*
408 ServiceResolverImplMac::GetContainerForTesting() {
409 return container_.get();
412 } // namespace local_discovery
414 @implementation NetServiceBrowserDelegate
416 - (id)initWithContainer:
417 (ServiceWatcherImplMac::NetServiceBrowserContainer*)container {
418 if ((self = [super init])) {
419 container_ = container;
420 services_.reset([[NSMutableArray alloc] initWithCapacity:1]);
425 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
426 didFindService:(NSNetService *)netService
427 moreComing:(BOOL)moreServicesComing {
428 // Start monitoring this service for updates.
429 [netService setDelegate:self];
430 [netService startMonitoring];
431 [services_ addObject:netService];
433 container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_ADDED,
434 [[netService name] UTF8String]);
437 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
438 didRemoveService:(NSNetService *)netService
439 moreComing:(BOOL)moreServicesComing {
440 NSUInteger index = [services_ indexOfObject:netService];
441 if (index != NSNotFound) {
442 container_->OnServicesUpdate(
443 local_discovery::ServiceWatcher::UPDATE_REMOVED,
444 [[netService name] UTF8String]);
446 // Stop monitoring this service for updates.
447 [[services_ objectAtIndex:index] stopMonitoring];
448 [services_ removeObjectAtIndex:index];
452 - (void)netService:(NSNetService *)sender
453 didUpdateTXTRecordData:(NSData *)data {
454 container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_CHANGED,
455 [[sender name] UTF8String]);
460 @implementation NetServiceDelegate
462 - (id)initWithContainer:
463 (ServiceResolverImplMac::NetServiceContainer*)container {
464 if ((self = [super init])) {
465 container_ = container;
470 - (void)netServiceDidResolveAddress:(NSNetService *)sender {
471 container_->OnResolveUpdate(local_discovery::ServiceResolver::STATUS_SUCCESS);
474 - (void)netService:(NSNetService *)sender
475 didNotResolve:(NSDictionary *)errorDict {
476 container_->OnResolveUpdate(
477 local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT);