xref: /aosp_15_r20/external/openscreen/discovery/dnssd/impl/publisher_impl.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
1*3f982cf4SFabien Sanglard // Copyright 2019 The Chromium Authors. All rights reserved.
2*3f982cf4SFabien Sanglard // Use of this source code is governed by a BSD-style license that can be
3*3f982cf4SFabien Sanglard // found in the LICENSE file.
4*3f982cf4SFabien Sanglard 
5*3f982cf4SFabien Sanglard #include "discovery/dnssd/impl/publisher_impl.h"
6*3f982cf4SFabien Sanglard 
7*3f982cf4SFabien Sanglard #include <map>
8*3f982cf4SFabien Sanglard #include <string>
9*3f982cf4SFabien Sanglard #include <utility>
10*3f982cf4SFabien Sanglard #include <vector>
11*3f982cf4SFabien Sanglard 
12*3f982cf4SFabien Sanglard #include "absl/types/optional.h"
13*3f982cf4SFabien Sanglard #include "discovery/common/reporting_client.h"
14*3f982cf4SFabien Sanglard #include "discovery/dnssd/impl/conversion_layer.h"
15*3f982cf4SFabien Sanglard #include "discovery/dnssd/impl/instance_key.h"
16*3f982cf4SFabien Sanglard #include "discovery/dnssd/impl/network_interface_config.h"
17*3f982cf4SFabien Sanglard #include "discovery/mdns/public/mdns_constants.h"
18*3f982cf4SFabien Sanglard #include "platform/api/task_runner.h"
19*3f982cf4SFabien Sanglard #include "platform/base/error.h"
20*3f982cf4SFabien Sanglard #include "util/trace_logging.h"
21*3f982cf4SFabien Sanglard 
22*3f982cf4SFabien Sanglard namespace openscreen {
23*3f982cf4SFabien Sanglard namespace discovery {
24*3f982cf4SFabien Sanglard namespace {
25*3f982cf4SFabien Sanglard 
CreateEndpoint(DnsSdInstance instance,InstanceKey key,const NetworkInterfaceConfig & network_config)26*3f982cf4SFabien Sanglard DnsSdInstanceEndpoint CreateEndpoint(
27*3f982cf4SFabien Sanglard     DnsSdInstance instance,
28*3f982cf4SFabien Sanglard     InstanceKey key,
29*3f982cf4SFabien Sanglard     const NetworkInterfaceConfig& network_config) {
30*3f982cf4SFabien Sanglard   std::vector<IPEndpoint> endpoints;
31*3f982cf4SFabien Sanglard   if (network_config.HasAddressV4()) {
32*3f982cf4SFabien Sanglard     endpoints.push_back({network_config.address_v4(), instance.port()});
33*3f982cf4SFabien Sanglard   }
34*3f982cf4SFabien Sanglard   if (network_config.HasAddressV6()) {
35*3f982cf4SFabien Sanglard     endpoints.push_back({network_config.address_v6(), instance.port()});
36*3f982cf4SFabien Sanglard   }
37*3f982cf4SFabien Sanglard   return DnsSdInstanceEndpoint(
38*3f982cf4SFabien Sanglard       key.instance_id(), key.service_id(), key.domain_id(), instance.txt(),
39*3f982cf4SFabien Sanglard       network_config.network_interface(), std::move(endpoints));
40*3f982cf4SFabien Sanglard }
41*3f982cf4SFabien Sanglard 
UpdateDomain(const DomainName & name,DnsSdInstance instance,const NetworkInterfaceConfig & network_config)42*3f982cf4SFabien Sanglard DnsSdInstanceEndpoint UpdateDomain(
43*3f982cf4SFabien Sanglard     const DomainName& name,
44*3f982cf4SFabien Sanglard     DnsSdInstance instance,
45*3f982cf4SFabien Sanglard     const NetworkInterfaceConfig& network_config) {
46*3f982cf4SFabien Sanglard   return CreateEndpoint(std::move(instance), InstanceKey(name), network_config);
47*3f982cf4SFabien Sanglard }
48*3f982cf4SFabien Sanglard 
CreateEndpoint(DnsSdInstance instance,const NetworkInterfaceConfig & network_config)49*3f982cf4SFabien Sanglard DnsSdInstanceEndpoint CreateEndpoint(
50*3f982cf4SFabien Sanglard     DnsSdInstance instance,
51*3f982cf4SFabien Sanglard     const NetworkInterfaceConfig& network_config) {
52*3f982cf4SFabien Sanglard   InstanceKey key(instance);
53*3f982cf4SFabien Sanglard   return CreateEndpoint(std::move(instance), std::move(key), network_config);
54*3f982cf4SFabien Sanglard }
55*3f982cf4SFabien Sanglard 
56*3f982cf4SFabien Sanglard template <typename T>
FindKey(std::map<DnsSdInstance,T> * instances,const InstanceKey & key)57*3f982cf4SFabien Sanglard inline typename std::map<DnsSdInstance, T>::iterator FindKey(
58*3f982cf4SFabien Sanglard     std::map<DnsSdInstance, T>* instances,
59*3f982cf4SFabien Sanglard     const InstanceKey& key) {
60*3f982cf4SFabien Sanglard   return std::find_if(instances->begin(), instances->end(),
61*3f982cf4SFabien Sanglard                       [&key](const std::pair<DnsSdInstance, T>& pair) {
62*3f982cf4SFabien Sanglard                         return key == InstanceKey(pair.first);
63*3f982cf4SFabien Sanglard                       });
64*3f982cf4SFabien Sanglard }
65*3f982cf4SFabien Sanglard 
66*3f982cf4SFabien Sanglard template <typename T>
EraseInstancesWithServiceId(std::map<DnsSdInstance,T> * instances,const std::string & service_id)67*3f982cf4SFabien Sanglard int EraseInstancesWithServiceId(std::map<DnsSdInstance, T>* instances,
68*3f982cf4SFabien Sanglard                                 const std::string& service_id) {
69*3f982cf4SFabien Sanglard   int removed_count = 0;
70*3f982cf4SFabien Sanglard   for (auto it = instances->begin(); it != instances->end();) {
71*3f982cf4SFabien Sanglard     if (it->first.service_id() == service_id) {
72*3f982cf4SFabien Sanglard       removed_count++;
73*3f982cf4SFabien Sanglard       it = instances->erase(it);
74*3f982cf4SFabien Sanglard     } else {
75*3f982cf4SFabien Sanglard       it++;
76*3f982cf4SFabien Sanglard     }
77*3f982cf4SFabien Sanglard   }
78*3f982cf4SFabien Sanglard 
79*3f982cf4SFabien Sanglard   return removed_count;
80*3f982cf4SFabien Sanglard }
81*3f982cf4SFabien Sanglard 
82*3f982cf4SFabien Sanglard }  // namespace
83*3f982cf4SFabien Sanglard 
PublisherImpl(MdnsService * publisher,ReportingClient * reporting_client,TaskRunner * task_runner,const NetworkInterfaceConfig * network_config)84*3f982cf4SFabien Sanglard PublisherImpl::PublisherImpl(MdnsService* publisher,
85*3f982cf4SFabien Sanglard                              ReportingClient* reporting_client,
86*3f982cf4SFabien Sanglard                              TaskRunner* task_runner,
87*3f982cf4SFabien Sanglard                              const NetworkInterfaceConfig* network_config)
88*3f982cf4SFabien Sanglard     : mdns_publisher_(publisher),
89*3f982cf4SFabien Sanglard       reporting_client_(reporting_client),
90*3f982cf4SFabien Sanglard       task_runner_(task_runner),
91*3f982cf4SFabien Sanglard       network_config_(network_config) {
92*3f982cf4SFabien Sanglard   OSP_DCHECK(mdns_publisher_);
93*3f982cf4SFabien Sanglard   OSP_DCHECK(reporting_client_);
94*3f982cf4SFabien Sanglard   OSP_DCHECK(task_runner_);
95*3f982cf4SFabien Sanglard }
96*3f982cf4SFabien Sanglard 
97*3f982cf4SFabien Sanglard PublisherImpl::~PublisherImpl() = default;
98*3f982cf4SFabien Sanglard 
Register(const DnsSdInstance & instance,Client * client)99*3f982cf4SFabien Sanglard Error PublisherImpl::Register(const DnsSdInstance& instance, Client* client) {
100*3f982cf4SFabien Sanglard   OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
101*3f982cf4SFabien Sanglard   OSP_DCHECK(client != nullptr);
102*3f982cf4SFabien Sanglard 
103*3f982cf4SFabien Sanglard   if (published_instances_.find(instance) != published_instances_.end()) {
104*3f982cf4SFabien Sanglard     UpdateRegistration(instance);
105*3f982cf4SFabien Sanglard   } else if (pending_instances_.find(instance) != pending_instances_.end()) {
106*3f982cf4SFabien Sanglard     return Error::Code::kOperationInProgress;
107*3f982cf4SFabien Sanglard   }
108*3f982cf4SFabien Sanglard 
109*3f982cf4SFabien Sanglard   InstanceKey key(instance);
110*3f982cf4SFabien Sanglard   const IPAddress& address = network_config_->GetAddress();
111*3f982cf4SFabien Sanglard   OSP_DCHECK(address);
112*3f982cf4SFabien Sanglard   pending_instances_.emplace(CreateEndpoint(instance, *network_config_),
113*3f982cf4SFabien Sanglard                              client);
114*3f982cf4SFabien Sanglard 
115*3f982cf4SFabien Sanglard   OSP_DVLOG << "Registering instance '" << instance.instance_id() << "'";
116*3f982cf4SFabien Sanglard 
117*3f982cf4SFabien Sanglard   return mdns_publisher_->StartProbe(this, GetDomainName(key), address);
118*3f982cf4SFabien Sanglard }
119*3f982cf4SFabien Sanglard 
UpdateRegistration(const DnsSdInstance & instance)120*3f982cf4SFabien Sanglard Error PublisherImpl::UpdateRegistration(const DnsSdInstance& instance) {
121*3f982cf4SFabien Sanglard   OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
122*3f982cf4SFabien Sanglard 
123*3f982cf4SFabien Sanglard   // Check if the instance is still pending publication.
124*3f982cf4SFabien Sanglard   auto it = FindKey(&pending_instances_, InstanceKey(instance));
125*3f982cf4SFabien Sanglard 
126*3f982cf4SFabien Sanglard   OSP_DVLOG << "Updating instance '" << instance.instance_id() << "'";
127*3f982cf4SFabien Sanglard 
128*3f982cf4SFabien Sanglard   // If it is a pending instance, update it. Else, try to update a published
129*3f982cf4SFabien Sanglard   // instance.
130*3f982cf4SFabien Sanglard   if (it != pending_instances_.end()) {
131*3f982cf4SFabien Sanglard     // The instance, service, and domain ids have not changed, so only the
132*3f982cf4SFabien Sanglard     // remaining data needs to change. The ongoing probe does not need to be
133*3f982cf4SFabien Sanglard     // modified.
134*3f982cf4SFabien Sanglard     Client* const client = it->second;
135*3f982cf4SFabien Sanglard     pending_instances_.erase(it);
136*3f982cf4SFabien Sanglard     pending_instances_.emplace(CreateEndpoint(instance, *network_config_),
137*3f982cf4SFabien Sanglard                                client);
138*3f982cf4SFabien Sanglard     return Error::None();
139*3f982cf4SFabien Sanglard   } else {
140*3f982cf4SFabien Sanglard     return UpdatePublishedRegistration(instance);
141*3f982cf4SFabien Sanglard   }
142*3f982cf4SFabien Sanglard }
143*3f982cf4SFabien Sanglard 
UpdatePublishedRegistration(const DnsSdInstance & instance)144*3f982cf4SFabien Sanglard Error PublisherImpl::UpdatePublishedRegistration(
145*3f982cf4SFabien Sanglard     const DnsSdInstance& instance) {
146*3f982cf4SFabien Sanglard   OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
147*3f982cf4SFabien Sanglard 
148*3f982cf4SFabien Sanglard   auto published_instance_it =
149*3f982cf4SFabien Sanglard       FindKey(&published_instances_, InstanceKey(instance));
150*3f982cf4SFabien Sanglard 
151*3f982cf4SFabien Sanglard   // Check preconditions called out in header. Specifically, the updated
152*3f982cf4SFabien Sanglard   // instance must be making changes to an already published instance.
153*3f982cf4SFabien Sanglard   if (published_instance_it == published_instances_.end()) {
154*3f982cf4SFabien Sanglard     return Error::Code::kParameterInvalid;
155*3f982cf4SFabien Sanglard   }
156*3f982cf4SFabien Sanglard 
157*3f982cf4SFabien Sanglard   const DnsSdInstanceEndpoint updated_endpoint =
158*3f982cf4SFabien Sanglard       UpdateDomain(GetDomainName(InstanceKey(published_instance_it->second)),
159*3f982cf4SFabien Sanglard                    instance, *network_config_);
160*3f982cf4SFabien Sanglard   if (published_instance_it->second == updated_endpoint) {
161*3f982cf4SFabien Sanglard     return Error::Code::kParameterInvalid;
162*3f982cf4SFabien Sanglard   }
163*3f982cf4SFabien Sanglard 
164*3f982cf4SFabien Sanglard   // Get all instances which have changed. By design, there an only be one
165*3f982cf4SFabien Sanglard   // instance of each DnsType, so use that here to simplify this step. First in
166*3f982cf4SFabien Sanglard   // each pair is the old instances, second is the new instance.
167*3f982cf4SFabien Sanglard   std::map<DnsType,
168*3f982cf4SFabien Sanglard            std::pair<absl::optional<MdnsRecord>, absl::optional<MdnsRecord>>>
169*3f982cf4SFabien Sanglard       changed_records;
170*3f982cf4SFabien Sanglard   const std::vector<MdnsRecord> old_records =
171*3f982cf4SFabien Sanglard       GetDnsRecords(published_instance_it->second);
172*3f982cf4SFabien Sanglard   const std::vector<MdnsRecord> new_records = GetDnsRecords(updated_endpoint);
173*3f982cf4SFabien Sanglard 
174*3f982cf4SFabien Sanglard   // Populate the first part of each pair in |changed_instances|.
175*3f982cf4SFabien Sanglard   for (size_t i = 0; i < old_records.size(); i++) {
176*3f982cf4SFabien Sanglard     const auto key = old_records[i].dns_type();
177*3f982cf4SFabien Sanglard     OSP_DCHECK(changed_records.find(key) == changed_records.end());
178*3f982cf4SFabien Sanglard     auto value = std::make_pair(std::move(old_records[i]), absl::nullopt);
179*3f982cf4SFabien Sanglard     changed_records.emplace(key, std::move(value));
180*3f982cf4SFabien Sanglard   }
181*3f982cf4SFabien Sanglard 
182*3f982cf4SFabien Sanglard   // Populate the second part of each pair in |changed_records|.
183*3f982cf4SFabien Sanglard   for (size_t i = 0; i < new_records.size(); i++) {
184*3f982cf4SFabien Sanglard     const auto key = new_records[i].dns_type();
185*3f982cf4SFabien Sanglard     auto find_it = changed_records.find(key);
186*3f982cf4SFabien Sanglard     if (find_it == changed_records.end()) {
187*3f982cf4SFabien Sanglard       std::pair<absl::optional<MdnsRecord>, absl::optional<MdnsRecord>> value(
188*3f982cf4SFabien Sanglard           absl::nullopt, std::move(new_records[i]));
189*3f982cf4SFabien Sanglard       changed_records.emplace(key, std::move(value));
190*3f982cf4SFabien Sanglard     } else {
191*3f982cf4SFabien Sanglard       find_it->second.second = std::move(new_records[i]);
192*3f982cf4SFabien Sanglard     }
193*3f982cf4SFabien Sanglard   }
194*3f982cf4SFabien Sanglard 
195*3f982cf4SFabien Sanglard   // Apply changes called out in |changed_records|.
196*3f982cf4SFabien Sanglard   Error total_result = Error::None();
197*3f982cf4SFabien Sanglard   for (const auto& pair : changed_records) {
198*3f982cf4SFabien Sanglard     OSP_DCHECK(pair.second.first != absl::nullopt ||
199*3f982cf4SFabien Sanglard                pair.second.second != absl::nullopt);
200*3f982cf4SFabien Sanglard     if (pair.second.first == absl::nullopt) {
201*3f982cf4SFabien Sanglard       TRACE_SCOPED(TraceCategory::kDiscovery, "mdns.RegisterRecord");
202*3f982cf4SFabien Sanglard       auto error = mdns_publisher_->RegisterRecord(pair.second.second.value());
203*3f982cf4SFabien Sanglard       TRACE_SET_RESULT(error);
204*3f982cf4SFabien Sanglard       if (!error.ok()) {
205*3f982cf4SFabien Sanglard         total_result = error;
206*3f982cf4SFabien Sanglard       }
207*3f982cf4SFabien Sanglard     } else if (pair.second.second == absl::nullopt) {
208*3f982cf4SFabien Sanglard       TRACE_SCOPED(TraceCategory::kDiscovery, "mdns.UnregisterRecord");
209*3f982cf4SFabien Sanglard       auto error = mdns_publisher_->UnregisterRecord(pair.second.first.value());
210*3f982cf4SFabien Sanglard       TRACE_SET_RESULT(error);
211*3f982cf4SFabien Sanglard       if (!error.ok()) {
212*3f982cf4SFabien Sanglard         total_result = error;
213*3f982cf4SFabien Sanglard       }
214*3f982cf4SFabien Sanglard     } else if (pair.second.first.value() != pair.second.second.value()) {
215*3f982cf4SFabien Sanglard       TRACE_SCOPED(TraceCategory::kDiscovery, "mdns.UpdateRegisteredRecord");
216*3f982cf4SFabien Sanglard       auto error = mdns_publisher_->UpdateRegisteredRecord(
217*3f982cf4SFabien Sanglard           pair.second.first.value(), pair.second.second.value());
218*3f982cf4SFabien Sanglard       TRACE_SET_RESULT(error);
219*3f982cf4SFabien Sanglard       if (!error.ok()) {
220*3f982cf4SFabien Sanglard         total_result = error;
221*3f982cf4SFabien Sanglard       }
222*3f982cf4SFabien Sanglard     }
223*3f982cf4SFabien Sanglard   }
224*3f982cf4SFabien Sanglard 
225*3f982cf4SFabien Sanglard   // Replace the old instances with the new ones.
226*3f982cf4SFabien Sanglard   published_instances_.erase(published_instance_it);
227*3f982cf4SFabien Sanglard   published_instances_.emplace(instance, std::move(updated_endpoint));
228*3f982cf4SFabien Sanglard 
229*3f982cf4SFabien Sanglard   return total_result;
230*3f982cf4SFabien Sanglard }
231*3f982cf4SFabien Sanglard 
DeregisterAll(const std::string & service)232*3f982cf4SFabien Sanglard ErrorOr<int> PublisherImpl::DeregisterAll(const std::string& service) {
233*3f982cf4SFabien Sanglard   OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
234*3f982cf4SFabien Sanglard 
235*3f982cf4SFabien Sanglard   OSP_DVLOG << "Deregistering all instances";
236*3f982cf4SFabien Sanglard 
237*3f982cf4SFabien Sanglard   int removed_count = 0;
238*3f982cf4SFabien Sanglard   Error error = Error::None();
239*3f982cf4SFabien Sanglard   for (auto it = published_instances_.begin();
240*3f982cf4SFabien Sanglard        it != published_instances_.end();) {
241*3f982cf4SFabien Sanglard     if (it->second.service_id() == service) {
242*3f982cf4SFabien Sanglard       for (const auto& mdns_record : GetDnsRecords(it->second)) {
243*3f982cf4SFabien Sanglard         TRACE_SCOPED(TraceCategory::kDiscovery, "mdns.UnregisterRecord");
244*3f982cf4SFabien Sanglard         auto publisher_error = mdns_publisher_->UnregisterRecord(mdns_record);
245*3f982cf4SFabien Sanglard         TRACE_SET_RESULT(error);
246*3f982cf4SFabien Sanglard         if (!publisher_error.ok()) {
247*3f982cf4SFabien Sanglard           error = publisher_error;
248*3f982cf4SFabien Sanglard         }
249*3f982cf4SFabien Sanglard       }
250*3f982cf4SFabien Sanglard       removed_count++;
251*3f982cf4SFabien Sanglard       it = published_instances_.erase(it);
252*3f982cf4SFabien Sanglard     } else {
253*3f982cf4SFabien Sanglard       it++;
254*3f982cf4SFabien Sanglard     }
255*3f982cf4SFabien Sanglard   }
256*3f982cf4SFabien Sanglard 
257*3f982cf4SFabien Sanglard   removed_count += EraseInstancesWithServiceId(&pending_instances_, service);
258*3f982cf4SFabien Sanglard 
259*3f982cf4SFabien Sanglard   if (!error.ok()) {
260*3f982cf4SFabien Sanglard     return error;
261*3f982cf4SFabien Sanglard   } else {
262*3f982cf4SFabien Sanglard     return removed_count;
263*3f982cf4SFabien Sanglard   }
264*3f982cf4SFabien Sanglard }
265*3f982cf4SFabien Sanglard 
OnDomainFound(const DomainName & requested_name,const DomainName & confirmed_name)266*3f982cf4SFabien Sanglard void PublisherImpl::OnDomainFound(const DomainName& requested_name,
267*3f982cf4SFabien Sanglard                                   const DomainName& confirmed_name) {
268*3f982cf4SFabien Sanglard   TRACE_DEFAULT_SCOPED(TraceCategory::kDiscovery);
269*3f982cf4SFabien Sanglard   OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
270*3f982cf4SFabien Sanglard 
271*3f982cf4SFabien Sanglard   OSP_DVLOG << "Domain successfully claimed: '" << confirmed_name.ToString()
272*3f982cf4SFabien Sanglard             << "' based on requested name: '" << requested_name.ToString()
273*3f982cf4SFabien Sanglard             << "'";
274*3f982cf4SFabien Sanglard 
275*3f982cf4SFabien Sanglard   auto it = FindKey(&pending_instances_, InstanceKey(requested_name));
276*3f982cf4SFabien Sanglard 
277*3f982cf4SFabien Sanglard   if (it == pending_instances_.end()) {
278*3f982cf4SFabien Sanglard     // This will be hit if the instance was deregister'd before the probe phase
279*3f982cf4SFabien Sanglard     // was completed.
280*3f982cf4SFabien Sanglard     return;
281*3f982cf4SFabien Sanglard   }
282*3f982cf4SFabien Sanglard 
283*3f982cf4SFabien Sanglard   DnsSdInstance requested_instance = std::move(it->first);
284*3f982cf4SFabien Sanglard   DnsSdInstanceEndpoint endpoint =
285*3f982cf4SFabien Sanglard       CreateEndpoint(requested_instance, *network_config_);
286*3f982cf4SFabien Sanglard   Client* const client = it->second;
287*3f982cf4SFabien Sanglard   pending_instances_.erase(it);
288*3f982cf4SFabien Sanglard 
289*3f982cf4SFabien Sanglard   InstanceKey requested_key(requested_instance);
290*3f982cf4SFabien Sanglard 
291*3f982cf4SFabien Sanglard   if (requested_name != confirmed_name) {
292*3f982cf4SFabien Sanglard     OSP_DCHECK(HasValidDnsRecordAddress(confirmed_name));
293*3f982cf4SFabien Sanglard     endpoint =
294*3f982cf4SFabien Sanglard         UpdateDomain(confirmed_name, requested_instance, *network_config_);
295*3f982cf4SFabien Sanglard   }
296*3f982cf4SFabien Sanglard 
297*3f982cf4SFabien Sanglard   for (const auto& mdns_record : GetDnsRecords(endpoint)) {
298*3f982cf4SFabien Sanglard     TRACE_SCOPED(TraceCategory::kDiscovery, "mdns.RegisterRecord");
299*3f982cf4SFabien Sanglard     Error result = mdns_publisher_->RegisterRecord(mdns_record);
300*3f982cf4SFabien Sanglard     if (!result.ok()) {
301*3f982cf4SFabien Sanglard       reporting_client_->OnRecoverableError(
302*3f982cf4SFabien Sanglard           Error(Error::Code::kRecordPublicationError, result.ToString()));
303*3f982cf4SFabien Sanglard     }
304*3f982cf4SFabien Sanglard   }
305*3f982cf4SFabien Sanglard 
306*3f982cf4SFabien Sanglard   auto pair = published_instances_.emplace(std::move(requested_instance),
307*3f982cf4SFabien Sanglard                                            std::move(endpoint));
308*3f982cf4SFabien Sanglard   client->OnEndpointClaimed(pair.first->first, pair.first->second);
309*3f982cf4SFabien Sanglard }
310*3f982cf4SFabien Sanglard 
311*3f982cf4SFabien Sanglard }  // namespace discovery
312*3f982cf4SFabien Sanglard }  // namespace openscreen
313