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