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