Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / local_discovery / service_discovery_client_mac.mm
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
15 using local_discovery::ServiceWatcherImplMac;
16 using 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
40 namespace local_discovery {
41
42 namespace {
43
44 const char kServiceDiscoveryThreadName[] = "Service Discovery Thread";
45
46 const 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>.
54 bool 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
88 void 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
116 ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {}
117 ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {}
118
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()));
126 }
127
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()));
135 }
136
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>();
145 }
146
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);
154   }
155 }
156
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),
162       callback_(callback),
163       callback_runner_(base::MessageLoopProxy::current()),
164       service_discovery_runner_(service_discovery_runner),
165       weak_factory_(this) {}
166
167 ServiceWatcherImplMac::NetServiceBrowserContainer::
168     ~NetServiceBrowserContainer() {
169   DCHECK(IsOnServiceDiscoveryThread());
170 }
171
172 void ServiceWatcherImplMac::NetServiceBrowserContainer::Start() {
173   service_discovery_runner_->PostTask(
174       FROM_HERE,
175       base::Bind(&NetServiceBrowserContainer::StartOnDiscoveryThread,
176                  weak_factory_.GetWeakPtr()));
177 }
178
179 void ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverNewServices() {
180   service_discovery_runner_->PostTask(
181       FROM_HERE,
182       base::Bind(&NetServiceBrowserContainer::DiscoverOnDiscoveryThread,
183                  weak_factory_.GetWeakPtr()));
184 }
185
186 void
187 ServiceWatcherImplMac::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
195 void
196 ServiceWatcherImplMac::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
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));
220 }
221
222 void ServiceWatcherImplMac::NetServiceBrowserContainer::DeleteSoon() {
223   service_discovery_runner_->DeleteSoon(FROM_HERE, this);
224 }
225
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),
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
241 ServiceWatcherImplMac::~ServiceWatcherImplMac() {}
242
243 void ServiceWatcherImplMac::Start() {
244   DCHECK(!started_);
245   VLOG(1) << "ServiceWatcherImplMac::Start";
246   container_->Start();
247   started_ = true;
248 }
249
250 void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) {
251   DCHECK(started_);
252   VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices";
253   container_->DiscoverNewServices();
254 }
255
256 void ServiceWatcherImplMac::SetActivelyRefreshServices(
257     bool actively_refresh_services) {
258   DCHECK(started_);
259   VLOG(1) << "ServiceWatcherImplMac::SetActivelyRefreshServices";
260 }
261
262 std::string ServiceWatcherImplMac::GetServiceType() const {
263   return service_type_;
264 }
265
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_);
271 }
272
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),
278       callback_(callback),
279       callback_runner_(base::MessageLoopProxy::current()),
280       service_discovery_runner_(service_discovery_runner),
281       weak_factory_(this) {}
282
283 ServiceResolverImplMac::NetServiceContainer::~NetServiceContainer() {
284   DCHECK(IsOnServiceDiscoveryThread());
285 }
286
287 void ServiceResolverImplMac::NetServiceContainer::StartResolving() {
288   service_discovery_runner_->PostTask(
289       FROM_HERE,
290       base::Bind(&NetServiceContainer::StartResolvingOnDiscoveryThread,
291                  weak_factory_.GetWeakPtr()));
292 }
293
294 void ServiceResolverImplMac::NetServiceContainer::DeleteSoon() {
295   service_discovery_runner_->DeleteSoon(FROM_HERE, this);
296 }
297
298 void
299 ServiceResolverImplMac::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
329 void 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
360 void ServiceResolverImplMac::NetServiceContainer::SetServiceForTesting(
361     base::scoped_nsobject<NSNetService> service) {
362   service_ = service;
363 }
364
365 ServiceResolverImplMac::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
380 ServiceResolverImplMac::~ServiceResolverImplMac() {}
381
382 void ServiceResolverImplMac::StartResolving() {
383   container_->StartResolving();
384
385   VLOG(1) << "Resolving service " << service_name_;
386 }
387
388 std::string ServiceResolverImplMac::GetName() const { return service_name_; }
389
390 void 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
401 ServiceResolverImplMac::NetServiceContainer*
402 ServiceResolverImplMac::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