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"
7 #import <Foundation/Foundation.h>
11 #include "base/memory/singleton.h"
12 #include "base/metrics/histogram.h"
13 #include "base/threading/thread.h"
15 using local_discovery::ServiceWatcherImplMac;
16 using local_discovery::ServiceResolverImplMac;
18 @interface NetServiceBrowserDelegate
19 : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> {
21 ServiceWatcherImplMac::NetServiceBrowserContainer* container_; // weak.
22 base::scoped_nsobject<NSMutableArray> services_;
25 - (id)initWithContainer:
26 (ServiceWatcherImplMac::NetServiceBrowserContainer*)serviceWatcherImpl;
30 @interface NetServiceDelegate : NSObject <NSNetServiceDelegate> {
32 ServiceResolverImplMac::NetServiceContainer* container_;
35 - (id)initWithContainer:
36 (ServiceResolverImplMac::NetServiceContainer*)serviceResolverImpl;
40 namespace local_discovery {
44 const char kServiceDiscoveryThreadName[] = "Service Discovery Thread";
46 const NSTimeInterval kResolveTimeout = 10.0;
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>.
54 bool ExtractServiceInfo(const std::string& service,
56 std::string* instance,
58 std::string* domain) {
62 const size_t last_period = service.find_last_of('.');
63 if (last_period == std::string::npos || service.length() <= last_period)
66 if (!is_service_name) {
67 *instance = std::string();
68 *type = service.substr(0, last_period) + ".";
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)
78 *instance = service.substr(0, type_period);
79 *type = service.substr(type_period + 1, last_period - type_period);
81 *domain = service.substr(last_period + 1) + ".";
83 return !domain->empty() &&
85 (!is_service_name || !instance->empty());
88 void ParseTxtRecord(NSData* record, std::vector<std::string>* output) {
89 if (record == nil || [record length] <= 1)
92 VLOG(1) << "ParseTxtRecord: " << [record length];
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++;
102 if (record_bytes + size <= record_end) {
103 VLOG(1) << "TxtRecord: "
104 << std::string(record_bytes, static_cast<size_t>(size));
106 [[[NSString alloc] initWithBytes:record_bytes
108 encoding:NSUTF8StringEncoding] UTF8String]);
110 record_bytes += size;
116 ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {}
117 ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {}
119 scoped_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()));
128 scoped_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()));
137 scoped_ptr<LocalDomainResolver>
138 ServiceDiscoveryClientMac::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>();
147 void 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);
157 ServiceWatcherImplMac::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),
163 callback_runner_(base::MessageLoopProxy::current()),
164 service_discovery_runner_(service_discovery_runner),
165 weak_factory_(this) {}
167 ServiceWatcherImplMac::NetServiceBrowserContainer::
168 ~NetServiceBrowserContainer() {
169 DCHECK(IsOnServiceDiscoveryThread());
172 void ServiceWatcherImplMac::NetServiceBrowserContainer::Start() {
173 service_discovery_runner_->PostTask(
175 base::Bind(&NetServiceBrowserContainer::StartOnDiscoveryThread,
176 weak_factory_.GetWeakPtr()));
179 void ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverNewServices() {
180 service_discovery_runner_->PostTask(
182 base::Bind(&NetServiceBrowserContainer::DiscoverOnDiscoveryThread,
183 weak_factory_.GetWeakPtr()));
187 ServiceWatcherImplMac::NetServiceBrowserContainer::StartOnDiscoveryThread() {
188 DCHECK(IsOnServiceDiscoveryThread());
190 delegate_.reset([[NetServiceBrowserDelegate alloc] initWithContainer:this]);
191 browser_.reset([[NSNetServiceBrowser alloc] init]);
192 [browser_ setDelegate:delegate_];
196 ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverOnDiscoveryThread() {
197 DCHECK(IsOnServiceDiscoveryThread());
198 std::string instance;
202 if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain))
205 DCHECK(instance.empty());
206 DVLOG(1) << "Listening for service type '" << type
207 << "' on domain '" << domain << "'";
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);
216 void ServiceWatcherImplMac::NetServiceBrowserContainer::OnServicesUpdate(
217 ServiceWatcher::UpdateType update,
218 const std::string& service) {
219 callback_runner_->PostTask(FROM_HERE, base::Bind(callback_, update, service));
222 void ServiceWatcherImplMac::NetServiceBrowserContainer::DeleteSoon() {
223 service_discovery_runner_->DeleteSoon(FROM_HERE, this);
226 ServiceWatcherImplMac::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),
233 weak_factory_(this) {
234 container_.reset(new NetServiceBrowserContainer(
236 base::Bind(&ServiceWatcherImplMac::OnServicesUpdate,
237 weak_factory_.GetWeakPtr()),
238 service_discovery_runner));
241 ServiceWatcherImplMac::~ServiceWatcherImplMac() {}
243 void ServiceWatcherImplMac::Start() {
245 VLOG(1) << "ServiceWatcherImplMac::Start";
250 void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) {
252 VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices";
253 container_->DiscoverNewServices();
256 void ServiceWatcherImplMac::SetActivelyRefreshServices(
257 bool actively_refresh_services) {
259 VLOG(1) << "ServiceWatcherImplMac::SetActivelyRefreshServices";
262 std::string ServiceWatcherImplMac::GetServiceType() const {
263 return service_type_;
266 void 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_);
273 ServiceResolverImplMac::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),
279 callback_runner_(base::MessageLoopProxy::current()),
280 service_discovery_runner_(service_discovery_runner),
281 weak_factory_(this) {}
283 ServiceResolverImplMac::NetServiceContainer::~NetServiceContainer() {
284 DCHECK(IsOnServiceDiscoveryThread());
287 void ServiceResolverImplMac::NetServiceContainer::StartResolving() {
288 service_discovery_runner_->PostTask(
290 base::Bind(&NetServiceContainer::StartResolvingOnDiscoveryThread,
291 weak_factory_.GetWeakPtr()));
294 void ServiceResolverImplMac::NetServiceContainer::DeleteSoon() {
295 service_discovery_runner_->DeleteSoon(FROM_HERE, this);
299 ServiceResolverImplMac::NetServiceContainer::StartResolvingOnDiscoveryThread() {
300 DCHECK(IsOnServiceDiscoveryThread());
301 std::string instance;
305 // The service object is set ahead of time by tests.
309 if (!ExtractServiceInfo(service_name_, true, &instance, &type, &domain))
310 return OnResolveUpdate(ServiceResolver::STATUS_KNOWN_NONEXISTENT);
312 VLOG(1) << "ServiceResolverImplMac::ServiceResolverImplMac::"
313 << "StartResolvingOnDiscoveryThread: Success";
314 delegate_.reset([[NetServiceDelegate alloc] initWithContainer:this]);
316 [[NSNetService alloc]
317 initWithDomain:[[NSString alloc] initWithUTF8String:domain.c_str()]
318 type:[[NSString alloc] initWithUTF8String:type.c_str()]
319 name:[[NSString alloc] initWithUTF8String:instance.c_str()]]);
320 [service_ setDelegate:delegate_];
322 [service_ resolveWithTimeout:kResolveTimeout];
324 VLOG(1) << "ServiceResolverImplMac::NetServiceContainer::"
325 << "StartResolvingOnDiscoveryThread: " << service_name_
326 << ", instance: " << instance << ", type: " << type
327 << ", domain: " << domain;
330 void ServiceResolverImplMac::NetServiceContainer::OnResolveUpdate(
331 RequestStatus status) {
332 if (status == STATUS_SUCCESS) {
333 service_description_.service_name = service_name_;
335 for (NSData* address in [service_ addresses]) {
336 const void* bytes = [address bytes];
337 // TODO(justinlin): Handle IPv6 addresses?
338 if (static_cast<const sockaddr*>(bytes)->sa_family == AF_INET) {
339 const sockaddr_in* sock = static_cast<const sockaddr_in*>(bytes);
340 char addr[INET_ADDRSTRLEN];
341 inet_ntop(AF_INET, &sock->sin_addr, addr, INET_ADDRSTRLEN);
342 service_description_.address =
343 net::HostPortPair(addr, ntohs(sock->sin_port));
344 net::ParseIPLiteralToNumber(addr, &service_description_.ip_address);
349 ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata);
351 // TODO(justinlin): Implement last_seen.
352 service_description_.last_seen = base::Time::Now();
353 callback_runner_->PostTask(
354 FROM_HERE, base::Bind(callback_, status, service_description_));
356 callback_runner_->PostTask(
357 FROM_HERE, base::Bind(callback_, status, ServiceDescription()));
361 void ServiceResolverImplMac::NetServiceContainer::SetServiceForTesting(
362 base::scoped_nsobject<NSNetService> service) {
366 ServiceResolverImplMac::ServiceResolverImplMac(
367 const std::string& service_name,
368 const ServiceResolver::ResolveCompleteCallback& callback,
369 scoped_refptr<base::MessageLoopProxy> service_discovery_runner)
370 : service_name_(service_name),
372 has_resolved_(false),
373 weak_factory_(this) {
374 container_.reset(new NetServiceContainer(
376 base::Bind(&ServiceResolverImplMac::OnResolveComplete,
377 weak_factory_.GetWeakPtr()),
378 service_discovery_runner));
381 ServiceResolverImplMac::~ServiceResolverImplMac() {}
383 void ServiceResolverImplMac::StartResolving() {
384 container_->StartResolving();
386 VLOG(1) << "Resolving service " << service_name_;
389 std::string ServiceResolverImplMac::GetName() const { return service_name_; }
391 void ServiceResolverImplMac::OnResolveComplete(
392 RequestStatus status,
393 const ServiceDescription& description) {
394 VLOG(1) << "ServiceResolverImplMac::OnResolveComplete: " << service_name_
397 has_resolved_ = true;
399 callback_.Run(status, description);
402 ServiceResolverImplMac::NetServiceContainer*
403 ServiceResolverImplMac::GetContainerForTesting() {
404 return container_.get();
407 } // namespace local_discovery
409 @implementation NetServiceBrowserDelegate
411 - (id)initWithContainer:
412 (ServiceWatcherImplMac::NetServiceBrowserContainer*)container {
413 if ((self = [super init])) {
414 container_ = container;
415 services_.reset([[NSMutableArray alloc] initWithCapacity:1]);
420 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
421 didFindService:(NSNetService *)netService
422 moreComing:(BOOL)moreServicesComing {
423 // Start monitoring this service for updates.
424 [netService setDelegate:self];
425 [netService startMonitoring];
426 [services_ addObject:netService];
428 container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_ADDED,
429 [[netService name] UTF8String]);
432 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
433 didRemoveService:(NSNetService *)netService
434 moreComing:(BOOL)moreServicesComing {
435 NSUInteger index = [services_ indexOfObject:netService];
436 if (index != NSNotFound) {
437 container_->OnServicesUpdate(
438 local_discovery::ServiceWatcher::UPDATE_REMOVED,
439 [[netService name] UTF8String]);
441 // Stop monitoring this service for updates.
442 [[services_ objectAtIndex:index] stopMonitoring];
443 [services_ removeObjectAtIndex:index];
447 - (void)netService:(NSNetService *)sender
448 didUpdateTXTRecordData:(NSData *)data {
449 container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_CHANGED,
450 [[sender name] UTF8String]);
455 @implementation NetServiceDelegate
457 - (id)initWithContainer:
458 (ServiceResolverImplMac::NetServiceContainer*)container {
459 if ((self = [super init])) {
460 container_ = container;
465 - (void)netServiceDidResolveAddress:(NSNetService *)sender {
466 container_->OnResolveUpdate(local_discovery::ServiceResolver::STATUS_SUCCESS);
469 - (void)netService:(NSNetService *)sender
470 didNotResolve:(NSDictionary *)errorDict {
471 container_->OnResolveUpdate(
472 local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT);