15c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Copyright 2014 The Chromium Authors. All rights reserved.
2eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// found in the LICENSE file.
4eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
5eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include <utility>
6eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
7eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/logging.h"
8eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/memory/singleton.h"
9eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/message_loop/message_loop_proxy.h"
10eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/stl_util.h"
115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "chrome/common/local_discovery/service_discovery_client_impl.h"
12eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "net/dns/dns_protocol.h"
13eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "net/dns/record_rdata.h"
14eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
15eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochnamespace local_discovery {
16eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
17424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)namespace {
18424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)// TODO(noamsml): Make this configurable through the LocalDomainResolver
19424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)// interface.
20424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)const int kLocalDomainSecondAddressTimeoutMs = 100;
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)const int kInitialRequeryTimeSeconds = 1;
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)const int kMaxRequeryTimeSeconds = 2; // Time for last requery
24424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)}
25424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
26eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceDiscoveryClientImpl::ServiceDiscoveryClientImpl(
27eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsClient* mdns_client) : mdns_client_(mdns_client) {
28eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
29eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
30eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceDiscoveryClientImpl::~ServiceDiscoveryClientImpl() {
31eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
32eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
33eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochscoped_ptr<ServiceWatcher> ServiceDiscoveryClientImpl::CreateServiceWatcher(
34eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const std::string& service_type,
357dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    const ServiceWatcher::UpdatedCallback& callback) {
36eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return scoped_ptr<ServiceWatcher>(new ServiceWatcherImpl(
3768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      service_type, callback, mdns_client_));
38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
40eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochscoped_ptr<ServiceResolver> ServiceDiscoveryClientImpl::CreateServiceResolver(
41eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const std::string& service_name,
42eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const ServiceResolver::ResolveCompleteCallback& callback) {
43eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return scoped_ptr<ServiceResolver>(new ServiceResolverImpl(
44eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      service_name, callback, mdns_client_));
45eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
46eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
47a3f7b4e666c476898878fa745f637129375cd889Ben Murdochscoped_ptr<LocalDomainResolver>
48a3f7b4e666c476898878fa745f637129375cd889Ben MurdochServiceDiscoveryClientImpl::CreateLocalDomainResolver(
49a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      const std::string& domain,
50a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      net::AddressFamily address_family,
51a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      const LocalDomainResolver::IPAddressCallback& callback) {
52a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  return scoped_ptr<LocalDomainResolver>(new LocalDomainResolverImpl(
53a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      domain, address_family, callback, mdns_client_));
54a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch}
55a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
56eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceWatcherImpl::ServiceWatcherImpl(
57eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const std::string& service_type,
587dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    const ServiceWatcher::UpdatedCallback& callback,
59eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsClient* mdns_client)
607dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    : service_type_(service_type), callback_(callback), started_(false),
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      actively_refresh_services_(false), mdns_client_(mdns_client) {
62eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
63eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
647dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochvoid ServiceWatcherImpl::Start() {
65eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(!started_);
66eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  listener_ = mdns_client_->CreateListener(
67eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::dns_protocol::kTypePTR, service_type_, this);
687dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  started_ = listener_->Start();
697dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (started_)
707dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    ReadCachedServices();
71eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
72eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
73eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceWatcherImpl::~ServiceWatcherImpl() {
74eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
75eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
76eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::DiscoverNewServices(bool force_update) {
77eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(started_);
78eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (force_update)
79eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    services_.clear();
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  SendQuery(kInitialRequeryTimeSeconds, force_update);
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::SetActivelyRefreshServices(
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    bool actively_refresh_services) {
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DCHECK(started_);
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  actively_refresh_services_ = actively_refresh_services;
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for (ServiceListenersMap::iterator i = services_.begin();
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)       i != services_.end(); i++) {
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    i->second->SetActiveRefresh(actively_refresh_services);
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
92eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
93eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
94eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::ReadCachedServices() {
95eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(started_);
96eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  CreateTransaction(false /*network*/, true /*cache*/, false /*force refresh*/,
97eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                    &transaction_cache_);
98eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
99eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
100eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool ServiceWatcherImpl::CreateTransaction(
101eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    bool network, bool cache, bool force_refresh,
102eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    scoped_ptr<net::MDnsTransaction>* transaction) {
103eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  int transaction_flags = 0;
104eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (network)
105eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    transaction_flags |= net::MDnsTransaction::QUERY_NETWORK;
106eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
107eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (cache)
108eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    transaction_flags |= net::MDnsTransaction::QUERY_CACHE;
109eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
110eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // TODO(noamsml): Add flag for force_refresh when supported.
111eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
112eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (transaction_flags) {
113eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    *transaction = mdns_client_->CreateTransaction(
114eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        net::dns_protocol::kTypePTR, service_type_, transaction_flags,
115eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        base::Bind(&ServiceWatcherImpl::OnTransactionResponse,
116eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                   base::Unretained(this), transaction));
117eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return (*transaction)->Start();
118eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
119eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
120eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return true;
121eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
122eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
123eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochstd::string ServiceWatcherImpl::GetServiceType() const {
124eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return listener_->GetName();
125eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
126eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
127eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::OnRecordUpdate(
128eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsListener::UpdateType update,
129eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const net::RecordParsed* record) {
130eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(started_);
131eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (record->type() == net::dns_protocol::kTypePTR) {
132eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(record->name() == GetServiceType());
133eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const net::PtrRecordRdata* rdata = record->rdata<net::PtrRecordRdata>();
134eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
135eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    switch (update) {
136eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case net::MDnsListener::RECORD_ADDED:
137eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        AddService(rdata->ptrdomain());
138eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        break;
139eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case net::MDnsListener::RECORD_CHANGED:
140eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        NOTREACHED();
141eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        break;
142eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case net::MDnsListener::RECORD_REMOVED:
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        RemovePTR(rdata->ptrdomain());
144eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        break;
145eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
146eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else {
147eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(record->type() == net::dns_protocol::kTypeSRV ||
148eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch           record->type() == net::dns_protocol::kTypeTXT);
149eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(services_.find(record->name()) != services_.end());
150eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (record->type() == net::dns_protocol::kTypeSRV) {
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if (update == net::MDnsListener::RECORD_REMOVED) {
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        RemoveSRV(record->name());
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      } else if (update == net::MDnsListener::RECORD_ADDED) {
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        AddSRV(record->name());
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      }
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // If this is the first time we see an SRV record, do not send
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // an UPDATE_CHANGED.
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (record->type() != net::dns_protocol::kTypeSRV ||
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        update != net::MDnsListener::RECORD_ADDED) {
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      DeferUpdate(UPDATE_CHANGED, record->name());
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
165eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
166eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
167eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
168eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::OnCachePurged() {
169eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // Not yet implemented.
170eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
171eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
172eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::OnTransactionResponse(
173eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    scoped_ptr<net::MDnsTransaction>* transaction,
174eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsTransaction::Result result,
175eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const net::RecordParsed* record) {
176eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(started_);
177eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (result == net::MDnsTransaction::RESULT_RECORD) {
178eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const net::PtrRecordRdata* rdata = record->rdata<net::PtrRecordRdata>();
179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(rdata);
180eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    AddService(rdata->ptrdomain());
181eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else if (result == net::MDnsTransaction::RESULT_DONE) {
182eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    transaction->reset();
183eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
184eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
185eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // Do nothing for NSEC records. It is an error for hosts to broadcast an NSEC
186eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // record for PTR records on any name.
187eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
188eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
189eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceWatcherImpl::ServiceListeners::ServiceListeners(
190eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const std::string& service_name,
191eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ServiceWatcherImpl* watcher,
1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    net::MDnsClient* mdns_client)
1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    : service_name_(service_name), mdns_client_(mdns_client),
1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      update_pending_(false), has_ptr_(true), has_srv_(false) {
195eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  srv_listener_ = mdns_client->CreateListener(
196eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::dns_protocol::kTypeSRV, service_name, watcher);
197eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  txt_listener_ = mdns_client->CreateListener(
198eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::dns_protocol::kTypeTXT, service_name, watcher);
199eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
200eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
201eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceWatcherImpl::ServiceListeners::~ServiceListeners() {
202eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
203eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
204eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool ServiceWatcherImpl::ServiceListeners::Start() {
205eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (!srv_listener_->Start())
206eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return false;
207eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return txt_listener_->Start();
208eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
209eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::ServiceListeners::SetActiveRefresh(
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    bool active_refresh) {
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  srv_listener_->SetActiveRefresh(active_refresh);
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (active_refresh && !has_srv_) {
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    DCHECK(has_ptr_);
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    srv_transaction_ = mdns_client_->CreateTransaction(
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        net::dns_protocol::kTypeSRV, service_name_,
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        net::MDnsTransaction::SINGLE_RESULT |
2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        net::MDnsTransaction::QUERY_CACHE | net::MDnsTransaction::QUERY_NETWORK,
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        base::Bind(&ServiceWatcherImpl::ServiceListeners::OnSRVRecord,
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                   base::Unretained(this)));
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    srv_transaction_->Start();
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  } else if (!active_refresh) {
2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    srv_transaction_.reset();
2255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
2275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::ServiceListeners::OnSRVRecord(
2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    net::MDnsTransaction::Result result,
2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const net::RecordParsed* record) {
2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  set_has_srv(record != NULL);
2325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::ServiceListeners::set_has_srv(bool has_srv) {
2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  has_srv_ = has_srv;
2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  srv_transaction_.reset();
2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
240eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::AddService(const std::string& service) {
241eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(started_);
242eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  std::pair<ServiceListenersMap::iterator, bool> found = services_.insert(
2437dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      make_pair(service, linked_ptr<ServiceListeners>(NULL)));
2445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
245eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (found.second) {  // Newly inserted.
246eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    found.first->second = linked_ptr<ServiceListeners>(
247eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        new ServiceListeners(service, this, mdns_client_));
248eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    bool success = found.first->second->Start();
2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    found.first->second->SetActiveRefresh(actively_refresh_services_);
250eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DeferUpdate(UPDATE_ADDED, service);
251eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
252eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(success);
253eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  found.first->second->set_has_ptr(true);
2565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
2575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::AddSRV(const std::string& service) {
2595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DCHECK(started_);
2605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  ServiceListenersMap::iterator found = services_.find(service);
2625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (found != services_.end()) {
2635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    found->second->set_has_srv(true);
2645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
265eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
266eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
267eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::DeferUpdate(ServiceWatcher::UpdateType update_type,
268eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                     const std::string& service_name) {
269eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  ServiceListenersMap::iterator found = services_.find(service_name);
270eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
271eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (found != services_.end() && !found->second->update_pending()) {
272eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    found->second->set_update_pending(true);
273eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    base::MessageLoop::current()->PostTask(
274eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        FROM_HERE,
275eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        base::Bind(&ServiceWatcherImpl::DeliverDeferredUpdate, AsWeakPtr(),
276eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                   update_type, service_name));
277eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
278eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
279eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
280eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::DeliverDeferredUpdate(
281eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ServiceWatcher::UpdateType update_type, const std::string& service_name) {
282eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  ServiceListenersMap::iterator found = services_.find(service_name);
283eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
284eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (found != services_.end()) {
285eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    found->second->set_update_pending(false);
2867dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if (!callback_.is_null())
2877dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      callback_.Run(update_type, service_name);
288eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
289eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
290eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
2915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::RemovePTR(const std::string& service) {
292eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(started_);
2935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
294eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  ServiceListenersMap::iterator found = services_.find(service);
295eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (found != services_.end()) {
2965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    found->second->set_has_ptr(false);
2975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (!found->second->has_ptr_or_srv()) {
2995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      services_.erase(found);
3005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if (!callback_.is_null())
3015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        callback_.Run(UPDATE_REMOVED, service);
3025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
3035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
3045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
3055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::RemoveSRV(const std::string& service) {
3075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DCHECK(started_);
3085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  ServiceListenersMap::iterator found = services_.find(service);
3105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (found != services_.end()) {
3115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    found->second->set_has_srv(false);
3125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (!found->second->has_ptr_or_srv()) {
3145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      services_.erase(found);
3155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if (!callback_.is_null())
3165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        callback_.Run(UPDATE_REMOVED, service);
3175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
318eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
319eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
320eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
321eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceWatcherImpl::OnNsecRecord(const std::string& name,
322eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                      unsigned rrtype) {
323eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // Do nothing. It is an error for hosts to broadcast an NSEC record for PTR
324eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // on any name.
325eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
326eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
3275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::ScheduleQuery(int timeout_seconds) {
3285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (timeout_seconds <= kMaxRequeryTimeSeconds) {
3295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    base::MessageLoop::current()->PostDelayedTask(
3305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        FROM_HERE,
3315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        base::Bind(&ServiceWatcherImpl::SendQuery,
3325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                   AsWeakPtr(),
3335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                   timeout_seconds * 2 /*next_timeout_seconds*/,
3345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                   false /*force_update*/),
3355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        base::TimeDelta::FromSeconds(timeout_seconds));
3365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
3375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
3385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ServiceWatcherImpl::SendQuery(int next_timeout_seconds,
3405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                   bool force_update) {
3415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  CreateTransaction(true /*network*/, false /*cache*/, force_update,
3425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    &transaction_network_);
3435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  ScheduleQuery(next_timeout_seconds);
3445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
3455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
346eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceResolverImpl::ServiceResolverImpl(
347eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const std::string& service_name,
348eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const ResolveCompleteCallback& callback,
349eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsClient* mdns_client)
350eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    : service_name_(service_name), callback_(callback),
3517dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      metadata_resolved_(false), address_resolved_(false),
3527dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      mdns_client_(mdns_client) {
353eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
354eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
3557dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochvoid ServiceResolverImpl::StartResolving() {
356eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  address_resolved_ = false;
357eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  metadata_resolved_ = false;
3584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  service_staging_ = ServiceDescription();
3594e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  service_staging_.service_name = service_name_;
360eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
3617dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (!CreateTxtTransaction() || !CreateSrvTransaction()) {
3627dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    ServiceNotFound(ServiceResolver::STATUS_REQUEST_TIMEOUT);
3637dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  }
364eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
365eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
366eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceResolverImpl::~ServiceResolverImpl() {
367eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
368eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
369eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool ServiceResolverImpl::CreateTxtTransaction() {
370eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  txt_transaction_ = mdns_client_->CreateTransaction(
371eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::dns_protocol::kTypeTXT, service_name_,
372eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::MDnsTransaction::SINGLE_RESULT | net::MDnsTransaction::QUERY_CACHE |
373eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::MDnsTransaction::QUERY_NETWORK,
374eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      base::Bind(&ServiceResolverImpl::TxtRecordTransactionResponse,
375eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                 AsWeakPtr()));
376eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return txt_transaction_->Start();
377eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
378eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
379eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// TODO(noamsml): quick-resolve for AAAA records.  Since A records tend to be in
380eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceResolverImpl::CreateATransaction() {
381eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  a_transaction_ = mdns_client_->CreateTransaction(
382eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::dns_protocol::kTypeA,
3837dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      service_staging_.address.host(),
384eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::MDnsTransaction::SINGLE_RESULT | net::MDnsTransaction::QUERY_CACHE,
385eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      base::Bind(&ServiceResolverImpl::ARecordTransactionResponse,
386eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                 AsWeakPtr()));
387eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  a_transaction_->Start();
388eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
389eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
390eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool ServiceResolverImpl::CreateSrvTransaction() {
391eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  srv_transaction_ = mdns_client_->CreateTransaction(
392eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::dns_protocol::kTypeSRV, service_name_,
393eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::MDnsTransaction::SINGLE_RESULT | net::MDnsTransaction::QUERY_CACHE |
394eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      net::MDnsTransaction::QUERY_NETWORK,
395eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      base::Bind(&ServiceResolverImpl::SrvRecordTransactionResponse,
396eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                 AsWeakPtr()));
397eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return srv_transaction_->Start();
398eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
399eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
400eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochstd::string ServiceResolverImpl::GetName() const {
401eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return service_name_;
402eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
403eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
404eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceResolverImpl::SrvRecordTransactionResponse(
405eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsTransaction::Result status, const net::RecordParsed* record) {
406eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  srv_transaction_.reset();
407eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (status == net::MDnsTransaction::RESULT_RECORD) {
408eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(record);
4097dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    service_staging_.address = RecordToAddress(record);
4107dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    service_staging_.last_seen = record->time_created();
411eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    CreateATransaction();
412eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else {
413eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ServiceNotFound(MDnsStatusToRequestStatus(status));
414eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
415eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
416eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
417eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceResolverImpl::TxtRecordTransactionResponse(
418eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsTransaction::Result status, const net::RecordParsed* record) {
419eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  txt_transaction_.reset();
420eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (status == net::MDnsTransaction::RESULT_RECORD) {
421eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(record);
4227dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    service_staging_.metadata = RecordToMetadata(record);
423eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else {
4247dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    service_staging_.metadata = std::vector<std::string>();
425eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
426eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
427eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  metadata_resolved_ = true;
428eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  AlertCallbackIfReady();
429eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
430eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
431eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceResolverImpl::ARecordTransactionResponse(
432eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsTransaction::Result status, const net::RecordParsed* record) {
433eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  a_transaction_.reset();
434eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
435eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (status == net::MDnsTransaction::RESULT_RECORD) {
436eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    DCHECK(record);
4377dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    service_staging_.ip_address = RecordToIPAddress(record);
438eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else {
4397dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    service_staging_.ip_address = net::IPAddressNumber();
440eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
441eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
442eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  address_resolved_ = true;
443eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  AlertCallbackIfReady();
444eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
445eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
446eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceResolverImpl::AlertCallbackIfReady() {
447eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (metadata_resolved_ && address_resolved_) {
448eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    txt_transaction_.reset();
449eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    srv_transaction_.reset();
450eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    a_transaction_.reset();
4517dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if (!callback_.is_null())
4527dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      callback_.Run(STATUS_SUCCESS, service_staging_);
453eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
454eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
455eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
456eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid ServiceResolverImpl::ServiceNotFound(
457eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ServiceResolver::RequestStatus status) {
458eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  txt_transaction_.reset();
459eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  srv_transaction_.reset();
460eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  a_transaction_.reset();
4617dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (!callback_.is_null())
4627dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    callback_.Run(status, ServiceDescription());
463eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
464eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
465eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochServiceResolver::RequestStatus ServiceResolverImpl::MDnsStatusToRequestStatus(
466eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    net::MDnsTransaction::Result status) const {
467eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  switch (status) {
468eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    case net::MDnsTransaction::RESULT_RECORD:
469eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return ServiceResolver::STATUS_SUCCESS;
470eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    case net::MDnsTransaction::RESULT_NO_RESULTS:
471eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return ServiceResolver::STATUS_REQUEST_TIMEOUT;
472eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    case net::MDnsTransaction::RESULT_NSEC:
473eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return ServiceResolver::STATUS_KNOWN_NONEXISTENT;
474eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    case net::MDnsTransaction::RESULT_DONE:  // Pass through.
475eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    default:
476eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      NOTREACHED();
477eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return ServiceResolver::STATUS_REQUEST_TIMEOUT;
478eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
479eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
480eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
481eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochconst std::vector<std::string>& ServiceResolverImpl::RecordToMetadata(
482eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const net::RecordParsed* record) const {
483eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(record->type() == net::dns_protocol::kTypeTXT);
484eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  const net::TxtRecordRdata* txt_rdata = record->rdata<net::TxtRecordRdata>();
485eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(txt_rdata);
486eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return txt_rdata->texts();
487eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
488eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
489eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochnet::HostPortPair ServiceResolverImpl::RecordToAddress(
490eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const net::RecordParsed* record) const {
491eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(record->type() == net::dns_protocol::kTypeSRV);
492eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  const net::SrvRecordRdata* srv_rdata = record->rdata<net::SrvRecordRdata>();
493eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(srv_rdata);
494eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return net::HostPortPair(srv_rdata->target(), srv_rdata->port());
495eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
496eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
497eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochconst net::IPAddressNumber& ServiceResolverImpl::RecordToIPAddress(
498eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    const net::RecordParsed* record) const {
499eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(record->type() == net::dns_protocol::kTypeA);
500eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  const net::ARecordRdata* a_rdata = record->rdata<net::ARecordRdata>();
501eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(a_rdata);
502eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return a_rdata->address();
503eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
504eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
505a3f7b4e666c476898878fa745f637129375cd889Ben MurdochLocalDomainResolverImpl::LocalDomainResolverImpl(
506a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    const std::string& domain,
507a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    net::AddressFamily address_family,
508a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    const IPAddressCallback& callback,
509a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    net::MDnsClient* mdns_client)
510a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    : domain_(domain), address_family_(address_family), callback_(callback),
511424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      transactions_finished_(0), mdns_client_(mdns_client) {
512a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch}
513a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
514a3f7b4e666c476898878fa745f637129375cd889Ben MurdochLocalDomainResolverImpl::~LocalDomainResolverImpl() {
515424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  timeout_callback_.Cancel();
516a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch}
517a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
518a3f7b4e666c476898878fa745f637129375cd889Ben Murdochvoid LocalDomainResolverImpl::Start() {
519a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  if (address_family_ == net::ADDRESS_FAMILY_IPV4 ||
520a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      address_family_ == net::ADDRESS_FAMILY_UNSPECIFIED) {
521a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    transaction_a_ = CreateTransaction(net::dns_protocol::kTypeA);
522a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    transaction_a_->Start();
523a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  }
524a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
525a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  if (address_family_ == net::ADDRESS_FAMILY_IPV6 ||
526a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      address_family_ == net::ADDRESS_FAMILY_UNSPECIFIED) {
527a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    transaction_aaaa_ = CreateTransaction(net::dns_protocol::kTypeAAAA);
528a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    transaction_aaaa_->Start();
529a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  }
530a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch}
531a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
532a3f7b4e666c476898878fa745f637129375cd889Ben Murdochscoped_ptr<net::MDnsTransaction> LocalDomainResolverImpl::CreateTransaction(
533a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    uint16 type) {
534a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  return mdns_client_->CreateTransaction(
535a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      type, domain_, net::MDnsTransaction::SINGLE_RESULT |
536a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch                     net::MDnsTransaction::QUERY_CACHE |
537a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch                     net::MDnsTransaction::QUERY_NETWORK,
538a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      base::Bind(&LocalDomainResolverImpl::OnTransactionComplete,
539a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch                 base::Unretained(this)));
540a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch}
541a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
542a3f7b4e666c476898878fa745f637129375cd889Ben Murdochvoid LocalDomainResolverImpl::OnTransactionComplete(
543a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    net::MDnsTransaction::Result result, const net::RecordParsed* record) {
544424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  transactions_finished_++;
545a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
546a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  if (result == net::MDnsTransaction::RESULT_RECORD) {
547a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    if (record->type() == net::dns_protocol::kTypeA) {
548a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      const net::ARecordRdata* rdata = record->rdata<net::ARecordRdata>();
549424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      address_ipv4_ = rdata->address();
550a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    } else {
551a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      DCHECK_EQ(net::dns_protocol::kTypeAAAA, record->type());
552a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      const net::AAAARecordRdata* rdata = record->rdata<net::AAAARecordRdata>();
553424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      address_ipv6_ = rdata->address();
554a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    }
555a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  }
556a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
557424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  if (transactions_finished_ == 1 &&
558424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      address_family_ == net::ADDRESS_FAMILY_UNSPECIFIED) {
559424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    timeout_callback_.Reset(base::Bind(
560424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        &LocalDomainResolverImpl::SendResolvedAddresses,
561424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        base::Unretained(this)));
562424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
563424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    base::MessageLoop::current()->PostDelayedTask(
564424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        FROM_HERE,
565424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        timeout_callback_.callback(),
566424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        base::TimeDelta::FromMilliseconds(kLocalDomainSecondAddressTimeoutMs));
567424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  } else if (transactions_finished_ == 2
568424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      || address_family_ != net::ADDRESS_FAMILY_UNSPECIFIED) {
569424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    SendResolvedAddresses();
570424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
571424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)}
572424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
573424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)bool LocalDomainResolverImpl::IsSuccess() {
574424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  return !address_ipv4_.empty() || !address_ipv6_.empty();
575424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)}
576424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
577424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)void LocalDomainResolverImpl::SendResolvedAddresses() {
578424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  transaction_a_.reset();
579424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  transaction_aaaa_.reset();
580424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  timeout_callback_.Cancel();
581424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  callback_.Run(IsSuccess(), address_ipv4_, address_ipv6_);
582a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch}
583a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
584eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}  // namespace local_discovery
585