service_discovery_client_mac.mm revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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  NSUInteger index = [services_ indexOfObject:netService];
435  if (index != NSNotFound) {
436    container_->OnServicesUpdate(
437        local_discovery::ServiceWatcher::UPDATE_REMOVED,
438        [[netService name] UTF8String]);
439
440    // Stop monitoring this service for updates.
441    [[services_ objectAtIndex:index] stopMonitoring];
442    [services_ removeObjectAtIndex:index];
443  }
444}
445
446- (void)netService:(NSNetService *)sender
447        didUpdateTXTRecordData:(NSData *)data {
448  container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_CHANGED,
449                               [[sender name] UTF8String]);
450}
451
452@end
453
454@implementation NetServiceDelegate
455
456- (id)initWithContainer:
457          (ServiceResolverImplMac::NetServiceContainer*)container {
458  if ((self = [super init])) {
459    container_ = container;
460  }
461  return self;
462}
463
464- (void)netServiceDidResolveAddress:(NSNetService *)sender {
465  container_->OnResolveUpdate(local_discovery::ServiceResolver::STATUS_SUCCESS);
466}
467
468- (void)netService:(NSNetService *)sender
469        didNotResolve:(NSDictionary *)errorDict {
470  container_->OnResolveUpdate(
471      local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT);
472}
473
474@end
475