Allow the minimum time between banner trigger visits to be varied via field trials.
[chromium-blink-merge.git] / chrome / browser / local_discovery / service_discovery_client_mac.mm
blob4a09e3e9a8084e10cf6c3a6c3ca2175dfac737a0
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 <arpa/inet.h>
8 #import <Foundation/Foundation.h>
9 #import <net/if_dl.h>
11 #include "base/memory/singleton.h"
12 #include "base/metrics/histogram.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/thread_task_runner_handle.h"
15 #include "base/threading/thread.h"
16 #include "net/base/ip_address_number.h"
18 using local_discovery::ServiceWatcherImplMac;
19 using local_discovery::ServiceResolverImplMac;
21 @interface NetServiceBrowserDelegate
22     : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> {
23  @private
24   ServiceWatcherImplMac::NetServiceBrowserContainer* container_;  // weak.
25   base::scoped_nsobject<NSMutableArray> services_;
28 - (id)initWithContainer:
29         (ServiceWatcherImplMac::NetServiceBrowserContainer*)serviceWatcherImpl;
31 @end
33 @interface NetServiceDelegate : NSObject <NSNetServiceDelegate> {
34   @private
35    ServiceResolverImplMac::NetServiceContainer* container_;
38 - (id)initWithContainer:
39         (ServiceResolverImplMac::NetServiceContainer*)serviceResolverImpl;
41 @end
43 namespace local_discovery {
45 namespace {
47 const char kServiceDiscoveryThreadName[] = "Service Discovery Thread";
49 const NSTimeInterval kResolveTimeout = 10.0;
51 // Extracts the instance name, name type and domain from a full service name or
52 // the service type and domain from a service type. Returns true if successful.
53 // TODO(justinlin): This current only handles service names with format
54 // <name>._<protocol2>._<protocol1>.<domain>. Service names with
55 // subtypes will not parse correctly:
56 // <name>._<type>._<sub>._<protocol2>._<protocol1>.<domain>.
57 bool ExtractServiceInfo(const std::string& service,
58                         bool is_service_name,
59                         std::string* instance,
60                         std::string* type,
61                         std::string* domain) {
62   if (service.empty())
63     return false;
65   const size_t last_period = service.find_last_of('.');
66   if (last_period == std::string::npos || service.length() <= last_period)
67     return false;
69   if (!is_service_name) {
70     *instance = std::string();
71     *type = service.substr(0, last_period) + ".";
72   } else {
73     // Find third last period that delimits type and instance name.
74     size_t type_period = last_period;
75     for (int i = 0; i < 2; ++i) {
76       type_period = service.find_last_of('.', type_period - 1);
77       if (type_period == std::string::npos)
78         return false;
79     }
81     *instance = service.substr(0, type_period);
82     *type = service.substr(type_period + 1, last_period - type_period);
83   }
84   *domain = service.substr(last_period + 1) + ".";
86   return !domain->empty() &&
87          !type->empty() &&
88          (!is_service_name || !instance->empty());
91 void ParseTxtRecord(NSData* record, std::vector<std::string>* output) {
92   if (record == nil || [record length] <= 1)
93     return;
95   VLOG(1) << "ParseTxtRecord: " << [record length];
97   const char* record_bytes = reinterpret_cast<const char*>([record bytes]);
98   const char* const record_end = record_bytes + [record length];
99   // TODO(justinlin): More strict bounds checking.
100   while (record_bytes < record_end) {
101     uint8 size = *record_bytes++;
102     if (size <= 0)
103       continue;
105     if (record_bytes + size <= record_end) {
106       VLOG(1) << "TxtRecord: "
107               << std::string(record_bytes, static_cast<size_t>(size));
108       output->push_back(
109           [[[NSString alloc] initWithBytes:record_bytes
110                              length:size
111                              encoding:NSUTF8StringEncoding] UTF8String]);
112     }
113     record_bytes += size;
114   }
117 }  // namespace
119 ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {}
120 ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {}
122 scoped_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher(
123     const std::string& service_type,
124     const ServiceWatcher::UpdatedCallback& callback) {
125   StartThreadIfNotStarted();
126   VLOG(1) << "CreateServiceWatcher: " << service_type;
127   return scoped_ptr<ServiceWatcher>(new ServiceWatcherImplMac(
128       service_type, callback, service_discovery_thread_->task_runner()));
131 scoped_ptr<ServiceResolver> ServiceDiscoveryClientMac::CreateServiceResolver(
132     const std::string& service_name,
133     const ServiceResolver::ResolveCompleteCallback& callback) {
134   StartThreadIfNotStarted();
135   VLOG(1) << "CreateServiceResolver: " << service_name;
136   return scoped_ptr<ServiceResolver>(new ServiceResolverImplMac(
137       service_name, callback, service_discovery_thread_->task_runner()));
140 scoped_ptr<LocalDomainResolver>
141 ServiceDiscoveryClientMac::CreateLocalDomainResolver(
142     const std::string& domain,
143     net::AddressFamily address_family,
144     const LocalDomainResolver::IPAddressCallback& callback) {
145   NOTIMPLEMENTED();  // TODO(noamsml): Implement.
146   VLOG(1) << "CreateLocalDomainResolver: " << domain;
147   return scoped_ptr<LocalDomainResolver>();
150 void ServiceDiscoveryClientMac::StartThreadIfNotStarted() {
151   if (!service_discovery_thread_) {
152     service_discovery_thread_.reset(
153         new base::Thread(kServiceDiscoveryThreadName));
154     // Only TYPE_UI uses an NSRunLoop.
155     base::Thread::Options options(base::MessageLoop::TYPE_UI, 0);
156     service_discovery_thread_->StartWithOptions(options);
157   }
160 ServiceWatcherImplMac::NetServiceBrowserContainer::NetServiceBrowserContainer(
161     const std::string& service_type,
162     const ServiceWatcher::UpdatedCallback& callback,
163     scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
164     : service_type_(service_type),
165       callback_(callback),
166       callback_runner_(base::ThreadTaskRunnerHandle::Get()),
167       service_discovery_runner_(service_discovery_runner),
168       weak_factory_(this) {
171 ServiceWatcherImplMac::NetServiceBrowserContainer::
172     ~NetServiceBrowserContainer() {
173   DCHECK(IsOnServiceDiscoveryThread());
176 void ServiceWatcherImplMac::NetServiceBrowserContainer::Start() {
177   service_discovery_runner_->PostTask(
178       FROM_HERE,
179       base::Bind(&NetServiceBrowserContainer::StartOnDiscoveryThread,
180                  weak_factory_.GetWeakPtr()));
183 void ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverNewServices() {
184   service_discovery_runner_->PostTask(
185       FROM_HERE,
186       base::Bind(&NetServiceBrowserContainer::DiscoverOnDiscoveryThread,
187                  weak_factory_.GetWeakPtr()));
190 void
191 ServiceWatcherImplMac::NetServiceBrowserContainer::StartOnDiscoveryThread() {
192   DCHECK(IsOnServiceDiscoveryThread());
194   delegate_.reset([[NetServiceBrowserDelegate alloc] initWithContainer:this]);
195   browser_.reset([[NSNetServiceBrowser alloc] init]);
196   [browser_ setDelegate:delegate_];
199 void
200 ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverOnDiscoveryThread() {
201   DCHECK(IsOnServiceDiscoveryThread());
202   std::string instance;
203   std::string type;
204   std::string domain;
206   if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain))
207     return;
209   DCHECK(instance.empty());
210   DVLOG(1) << "Listening for service type '" << type
211            << "' on domain '" << domain << "'";
213   base::Time start_time = base::Time::Now();
214   [browser_ searchForServicesOfType:[NSString stringWithUTF8String:type.c_str()]
215             inDomain:[NSString stringWithUTF8String:domain.c_str()]];
216   UMA_HISTOGRAM_TIMES("LocalDiscovery.MacBrowseCallTimes",
217                       base::Time::Now() - start_time);
220 void ServiceWatcherImplMac::NetServiceBrowserContainer::OnServicesUpdate(
221     ServiceWatcher::UpdateType update,
222     const std::string& service) {
223   callback_runner_->PostTask(FROM_HERE, base::Bind(callback_, update, service));
226 void ServiceWatcherImplMac::NetServiceBrowserContainer::DeleteSoon() {
227   service_discovery_runner_->DeleteSoon(FROM_HERE, this);
230 ServiceWatcherImplMac::ServiceWatcherImplMac(
231     const std::string& service_type,
232     const ServiceWatcher::UpdatedCallback& callback,
233     scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
234     : service_type_(service_type),
235       callback_(callback),
236       started_(false),
237       weak_factory_(this) {
238   container_.reset(new NetServiceBrowserContainer(
239       service_type,
240       base::Bind(&ServiceWatcherImplMac::OnServicesUpdate,
241                  weak_factory_.GetWeakPtr()),
242       service_discovery_runner));
245 ServiceWatcherImplMac::~ServiceWatcherImplMac() {}
247 void ServiceWatcherImplMac::Start() {
248   DCHECK(!started_);
249   VLOG(1) << "ServiceWatcherImplMac::Start";
250   container_->Start();
251   started_ = true;
254 void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) {
255   DCHECK(started_);
256   VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices";
257   container_->DiscoverNewServices();
260 void ServiceWatcherImplMac::SetActivelyRefreshServices(
261     bool actively_refresh_services) {
262   DCHECK(started_);
263   VLOG(1) << "ServiceWatcherImplMac::SetActivelyRefreshServices";
266 std::string ServiceWatcherImplMac::GetServiceType() const {
267   return service_type_;
270 void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update,
271                                              const std::string& service) {
272   VLOG(1) << "ServiceWatcherImplMac::OnServicesUpdate: "
273           << service + "." + service_type_;
274   callback_.Run(update, service + "." + service_type_);
277 ServiceResolverImplMac::NetServiceContainer::NetServiceContainer(
278     const std::string& service_name,
279     const ServiceResolver::ResolveCompleteCallback& callback,
280     scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
281     : service_name_(service_name),
282       callback_(callback),
283       callback_runner_(base::ThreadTaskRunnerHandle::Get()),
284       service_discovery_runner_(service_discovery_runner),
285       weak_factory_(this) {
288 ServiceResolverImplMac::NetServiceContainer::~NetServiceContainer() {
289   DCHECK(IsOnServiceDiscoveryThread());
292 void ServiceResolverImplMac::NetServiceContainer::StartResolving() {
293   service_discovery_runner_->PostTask(
294       FROM_HERE,
295       base::Bind(&NetServiceContainer::StartResolvingOnDiscoveryThread,
296                  weak_factory_.GetWeakPtr()));
299 void ServiceResolverImplMac::NetServiceContainer::DeleteSoon() {
300   service_discovery_runner_->DeleteSoon(FROM_HERE, this);
303 void
304 ServiceResolverImplMac::NetServiceContainer::StartResolvingOnDiscoveryThread() {
305   DCHECK(IsOnServiceDiscoveryThread());
306   std::string instance;
307   std::string type;
308   std::string domain;
310   // The service object is set ahead of time by tests.
311   if (service_)
312     return;
314   if (!ExtractServiceInfo(service_name_, true, &instance, &type, &domain))
315     return OnResolveUpdate(ServiceResolver::STATUS_KNOWN_NONEXISTENT);
317   VLOG(1) << "ServiceResolverImplMac::ServiceResolverImplMac::"
318           << "StartResolvingOnDiscoveryThread: Success";
319   delegate_.reset([[NetServiceDelegate alloc] initWithContainer:this]);
320   service_.reset(
321       [[NSNetService alloc]
322           initWithDomain:[[NSString alloc] initWithUTF8String:domain.c_str()]
323           type:[[NSString alloc] initWithUTF8String:type.c_str()]
324           name:[[NSString alloc] initWithUTF8String:instance.c_str()]]);
325   [service_ setDelegate:delegate_];
327   [service_ resolveWithTimeout:kResolveTimeout];
329   VLOG(1) << "ServiceResolverImplMac::NetServiceContainer::"
330           << "StartResolvingOnDiscoveryThread: " << service_name_
331           << ", instance: " << instance << ", type: " << type
332           << ", domain: " << domain;
335 void ServiceResolverImplMac::NetServiceContainer::OnResolveUpdate(
336     RequestStatus status) {
337   if (status == STATUS_SUCCESS) {
338     service_description_.service_name = service_name_;
340     for (NSData* address in [service_ addresses]) {
341       const void* bytes = [address bytes];
342       // TODO(justinlin): Handle IPv6 addresses?
343       if (static_cast<const sockaddr*>(bytes)->sa_family == AF_INET) {
344         const sockaddr_in* sock = static_cast<const sockaddr_in*>(bytes);
345         char addr[INET_ADDRSTRLEN];
346         inet_ntop(AF_INET, &sock->sin_addr, addr, INET_ADDRSTRLEN);
347         service_description_.address =
348             net::HostPortPair(addr, ntohs(sock->sin_port));
349         net::ParseIPLiteralToNumber(addr, &service_description_.ip_address);
350         break;
351       }
352     }
354     ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata);
356     // TODO(justinlin): Implement last_seen.
357     service_description_.last_seen = base::Time::Now();
358     callback_runner_->PostTask(
359         FROM_HERE, base::Bind(callback_, status, service_description_));
360   } else {
361     callback_runner_->PostTask(
362         FROM_HERE, base::Bind(callback_, status, ServiceDescription()));
363   }
366 void ServiceResolverImplMac::NetServiceContainer::SetServiceForTesting(
367     base::scoped_nsobject<NSNetService> service) {
368   service_ = service;
371 ServiceResolverImplMac::ServiceResolverImplMac(
372     const std::string& service_name,
373     const ServiceResolver::ResolveCompleteCallback& callback,
374     scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
375     : service_name_(service_name),
376       callback_(callback),
377       has_resolved_(false),
378       weak_factory_(this) {
379   container_.reset(new NetServiceContainer(
380       service_name,
381       base::Bind(&ServiceResolverImplMac::OnResolveComplete,
382                  weak_factory_.GetWeakPtr()),
383       service_discovery_runner));
386 ServiceResolverImplMac::~ServiceResolverImplMac() {}
388 void ServiceResolverImplMac::StartResolving() {
389   container_->StartResolving();
391   VLOG(1) << "Resolving service " << service_name_;
394 std::string ServiceResolverImplMac::GetName() const { return service_name_; }
396 void ServiceResolverImplMac::OnResolveComplete(
397     RequestStatus status,
398     const ServiceDescription& description) {
399   VLOG(1) << "ServiceResolverImplMac::OnResolveComplete: " << service_name_
400           << ", " << status;
402   has_resolved_ = true;
404   callback_.Run(status, description);
407 ServiceResolverImplMac::NetServiceContainer*
408 ServiceResolverImplMac::GetContainerForTesting() {
409   return container_.get();
412 }  // namespace local_discovery
414 @implementation NetServiceBrowserDelegate
416 - (id)initWithContainer:
417           (ServiceWatcherImplMac::NetServiceBrowserContainer*)container {
418   if ((self = [super init])) {
419     container_ = container;
420     services_.reset([[NSMutableArray alloc] initWithCapacity:1]);
421   }
422   return self;
425 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
426         didFindService:(NSNetService *)netService
427         moreComing:(BOOL)moreServicesComing {
428   // Start monitoring this service for updates.
429   [netService setDelegate:self];
430   [netService startMonitoring];
431   [services_ addObject:netService];
433   container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_ADDED,
434                                [[netService name] UTF8String]);
437 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
438         didRemoveService:(NSNetService *)netService
439         moreComing:(BOOL)moreServicesComing {
440   NSUInteger index = [services_ indexOfObject:netService];
441   if (index != NSNotFound) {
442     container_->OnServicesUpdate(
443         local_discovery::ServiceWatcher::UPDATE_REMOVED,
444         [[netService name] UTF8String]);
446     // Stop monitoring this service for updates.
447     [[services_ objectAtIndex:index] stopMonitoring];
448     [services_ removeObjectAtIndex:index];
449   }
452 - (void)netService:(NSNetService *)sender
453         didUpdateTXTRecordData:(NSData *)data {
454   container_->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_CHANGED,
455                                [[sender name] UTF8String]);
458 @end
460 @implementation NetServiceDelegate
462 - (id)initWithContainer:
463           (ServiceResolverImplMac::NetServiceContainer*)container {
464   if ((self = [super init])) {
465     container_ = container;
466   }
467   return self;
470 - (void)netServiceDidResolveAddress:(NSNetService *)sender {
471   container_->OnResolveUpdate(local_discovery::ServiceResolver::STATUS_SUCCESS);
474 - (void)netService:(NSNetService *)sender
475         didNotResolve:(NSDictionary *)errorDict {
476   container_->OnResolveUpdate(
477       local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT);
480 @end