1 /*
2 * Copyright (c) 2021, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * This file includes implementation of mDNS publisher.
32 */
33
34 #define OTBR_LOG_TAG "MDNS"
35
36 #include "mdns/mdns.hpp"
37
38 #include <assert.h>
39
40 #include <algorithm>
41 #include <functional>
42
43 #include "common/code_utils.hpp"
44 #include "utils/dns_utils.hpp"
45
46 namespace otbr {
47
48 namespace Mdns {
49
PublishService(const std::string & aHostName,const std::string & aName,const std::string & aType,const SubTypeList & aSubTypeList,uint16_t aPort,const TxtList & aTxtList,ResultCallback && aCallback)50 void Publisher::PublishService(const std::string &aHostName,
51 const std::string &aName,
52 const std::string &aType,
53 const SubTypeList &aSubTypeList,
54 uint16_t aPort,
55 const TxtList & aTxtList,
56 ResultCallback && aCallback)
57 {
58 mServiceRegistrationBeginTime[std::make_pair(aName, aType)] = Clock::now();
59
60 PublishServiceImpl(aHostName, aName, aType, aSubTypeList, aPort, aTxtList, std::move(aCallback));
61 }
62
PublishHost(const std::string & aName,const std::vector<uint8_t> & aAddress,ResultCallback && aCallback)63 void Publisher::PublishHost(const std::string &aName, const std::vector<uint8_t> &aAddress, ResultCallback &&aCallback)
64 {
65 mHostRegistrationBeginTime[aName] = Clock::now();
66
67 PublishHostImpl(aName, aAddress, std::move(aCallback));
68 }
69
OnServiceResolveFailed(const std::string & aType,const std::string & aInstanceName,int32_t aErrorCode)70 void Publisher::OnServiceResolveFailed(const std::string &aType, const std::string &aInstanceName, int32_t aErrorCode)
71 {
72 UpdateMdnsResponseCounters(mTelemetryInfo.mServiceResolutions, DnsErrorToOtbrError(aErrorCode));
73 UpdateServiceInstanceResolutionEmaLatency(aInstanceName, aType, DnsErrorToOtbrError(aErrorCode));
74 OnServiceResolveFailedImpl(aType, aInstanceName, aErrorCode);
75 }
76
OnHostResolveFailed(const std::string & aHostName,int32_t aErrorCode)77 void Publisher::OnHostResolveFailed(const std::string &aHostName, int32_t aErrorCode)
78 {
79 UpdateMdnsResponseCounters(mTelemetryInfo.mHostResolutions, DnsErrorToOtbrError(aErrorCode));
80 UpdateHostResolutionEmaLatency(aHostName, DnsErrorToOtbrError(aErrorCode));
81 OnHostResolveFailedImpl(aHostName, aErrorCode);
82 }
83
EncodeTxtData(const TxtList & aTxtList,std::vector<uint8_t> & aTxtData)84 otbrError Publisher::EncodeTxtData(const TxtList &aTxtList, std::vector<uint8_t> &aTxtData)
85 {
86 otbrError error = OTBR_ERROR_NONE;
87
88 for (const auto &txtEntry : aTxtList)
89 {
90 const auto & name = txtEntry.mName;
91 const auto & value = txtEntry.mValue;
92 const size_t entryLength = name.length() + 1 + value.size();
93
94 VerifyOrExit(entryLength <= kMaxTextEntrySize, error = OTBR_ERROR_INVALID_ARGS);
95
96 aTxtData.push_back(static_cast<uint8_t>(entryLength));
97 aTxtData.insert(aTxtData.end(), name.begin(), name.end());
98 aTxtData.push_back('=');
99 aTxtData.insert(aTxtData.end(), value.begin(), value.end());
100 }
101
102 exit:
103 return error;
104 }
105
DecodeTxtData(Publisher::TxtList & aTxtList,const uint8_t * aTxtData,uint16_t aTxtLength)106 otbrError Publisher::DecodeTxtData(Publisher::TxtList &aTxtList, const uint8_t *aTxtData, uint16_t aTxtLength)
107 {
108 otbrError error = OTBR_ERROR_NONE;
109
110 for (uint16_t r = 0; r < aTxtLength;)
111 {
112 uint16_t entrySize = aTxtData[r];
113 uint16_t keyStart = r + 1;
114 uint16_t entryEnd = keyStart + entrySize;
115 uint16_t keyEnd = keyStart;
116 uint16_t valStart;
117
118 while (keyEnd < entryEnd && aTxtData[keyEnd] != '=')
119 {
120 keyEnd++;
121 }
122
123 valStart = keyEnd;
124 if (valStart < entryEnd && aTxtData[valStart] == '=')
125 {
126 valStart++;
127 }
128
129 aTxtList.emplace_back(reinterpret_cast<const char *>(&aTxtData[keyStart]), keyEnd - keyStart,
130 &aTxtData[valStart], entryEnd - valStart);
131
132 r += entrySize + 1;
133 VerifyOrExit(r <= aTxtLength, error = OTBR_ERROR_PARSE);
134 }
135
136 exit:
137 return error;
138 }
139
RemoveSubscriptionCallbacks(uint64_t aSubscriberId)140 void Publisher::RemoveSubscriptionCallbacks(uint64_t aSubscriberId)
141 {
142 size_t erased;
143
144 OTBR_UNUSED_VARIABLE(erased);
145
146 assert(aSubscriberId > 0);
147
148 erased = mDiscoveredCallbacks.erase(aSubscriberId);
149
150 assert(erased == 1);
151 }
152
AddSubscriptionCallbacks(Publisher::DiscoveredServiceInstanceCallback aInstanceCallback,Publisher::DiscoveredHostCallback aHostCallback)153 uint64_t Publisher::AddSubscriptionCallbacks(Publisher::DiscoveredServiceInstanceCallback aInstanceCallback,
154 Publisher::DiscoveredHostCallback aHostCallback)
155 {
156 uint64_t subscriberId = mNextSubscriberId++;
157
158 assert(subscriberId > 0);
159
160 mDiscoveredCallbacks.emplace(subscriberId, std::make_pair(std::move(aInstanceCallback), std::move(aHostCallback)));
161 return subscriberId;
162 }
163
OnServiceResolved(const std::string & aType,const DiscoveredInstanceInfo & aInstanceInfo)164 void Publisher::OnServiceResolved(const std::string &aType, const DiscoveredInstanceInfo &aInstanceInfo)
165 {
166 otbrLogInfo("Service %s is resolved successfully: %s %s host %s addresses %zu", aType.c_str(),
167 aInstanceInfo.mRemoved ? "remove" : "add", aInstanceInfo.mName.c_str(), aInstanceInfo.mHostName.c_str(),
168 aInstanceInfo.mAddresses.size());
169
170 DnsUtils::CheckServiceNameSanity(aType);
171
172 assert(aInstanceInfo.mNetifIndex > 0);
173
174 if (!aInstanceInfo.mRemoved)
175 {
176 DnsUtils::CheckHostnameSanity(aInstanceInfo.mHostName);
177 }
178
179 UpdateMdnsResponseCounters(mTelemetryInfo.mServiceResolutions, OTBR_ERROR_NONE);
180 UpdateServiceInstanceResolutionEmaLatency(aInstanceInfo.mName, aType, OTBR_ERROR_NONE);
181
182 for (const auto &subCallback : mDiscoveredCallbacks)
183 {
184 if (subCallback.second.first != nullptr)
185 {
186 subCallback.second.first(aType, aInstanceInfo);
187 }
188 }
189 }
190
OnServiceRemoved(uint32_t aNetifIndex,const std::string & aType,const std::string & aInstanceName)191 void Publisher::OnServiceRemoved(uint32_t aNetifIndex, const std::string &aType, const std::string &aInstanceName)
192 {
193 DiscoveredInstanceInfo instanceInfo;
194
195 otbrLogInfo("Service %s.%s is removed from netif %u.", aInstanceName.c_str(), aType.c_str(), aNetifIndex);
196
197 instanceInfo.mRemoved = true;
198 instanceInfo.mNetifIndex = aNetifIndex;
199 instanceInfo.mName = aInstanceName;
200
201 OnServiceResolved(aType, instanceInfo);
202 }
203
OnHostResolved(const std::string & aHostName,const Publisher::DiscoveredHostInfo & aHostInfo)204 void Publisher::OnHostResolved(const std::string &aHostName, const Publisher::DiscoveredHostInfo &aHostInfo)
205 {
206 otbrLogInfo("Host %s is resolved successfully: host %s addresses %zu ttl %u", aHostName.c_str(),
207 aHostInfo.mHostName.c_str(), aHostInfo.mAddresses.size(), aHostInfo.mTtl);
208
209 if (!aHostInfo.mHostName.empty())
210 {
211 DnsUtils::CheckHostnameSanity(aHostInfo.mHostName);
212 }
213
214 UpdateMdnsResponseCounters(mTelemetryInfo.mHostResolutions, OTBR_ERROR_NONE);
215 UpdateHostResolutionEmaLatency(aHostName, OTBR_ERROR_NONE);
216
217 for (const auto &subCallback : mDiscoveredCallbacks)
218 {
219 if (subCallback.second.second != nullptr)
220 {
221 subCallback.second.second(aHostName, aHostInfo);
222 }
223 }
224 }
225
SortSubTypeList(SubTypeList aSubTypeList)226 Publisher::SubTypeList Publisher::SortSubTypeList(SubTypeList aSubTypeList)
227 {
228 std::sort(aSubTypeList.begin(), aSubTypeList.end());
229 return aSubTypeList;
230 }
231
SortTxtList(TxtList aTxtList)232 Publisher::TxtList Publisher::SortTxtList(TxtList aTxtList)
233 {
234 std::sort(aTxtList.begin(), aTxtList.end(),
235 [](const TxtEntry &aLhs, const TxtEntry &aRhs) { return aLhs.mName < aRhs.mName; });
236 return aTxtList;
237 }
238
MakeFullServiceName(const std::string & aName,const std::string & aType)239 std::string Publisher::MakeFullServiceName(const std::string &aName, const std::string &aType)
240 {
241 return aName + "." + aType + ".local";
242 }
243
MakeFullHostName(const std::string & aName)244 std::string Publisher::MakeFullHostName(const std::string &aName)
245 {
246 return aName + ".local";
247 }
248
AddServiceRegistration(ServiceRegistrationPtr && aServiceReg)249 void Publisher::AddServiceRegistration(ServiceRegistrationPtr &&aServiceReg)
250 {
251 mServiceRegistrations.emplace(MakeFullServiceName(aServiceReg->mName, aServiceReg->mType), std::move(aServiceReg));
252 }
253
RemoveServiceRegistration(const std::string & aName,const std::string & aType,otbrError aError)254 void Publisher::RemoveServiceRegistration(const std::string &aName, const std::string &aType, otbrError aError)
255 {
256 auto it = mServiceRegistrations.find(MakeFullServiceName(aName, aType));
257 ServiceRegistrationPtr serviceReg;
258
259 otbrLogInfo("Removing service %s.%s", aName.c_str(), aType.c_str());
260 VerifyOrExit(it != mServiceRegistrations.end());
261
262 // Keep the ServiceRegistration around before calling `Complete`
263 // to invoke the callback. This is for avoiding invalid access
264 // to the ServiceRegistration when it's freed from the callback.
265 serviceReg = std::move(it->second);
266 mServiceRegistrations.erase(it);
267 serviceReg->Complete(aError);
268
269 exit:
270 return;
271 }
272
FindServiceRegistration(const std::string & aName,const std::string & aType)273 Publisher::ServiceRegistration *Publisher::FindServiceRegistration(const std::string &aName, const std::string &aType)
274 {
275 auto it = mServiceRegistrations.find(MakeFullServiceName(aName, aType));
276
277 return it != mServiceRegistrations.end() ? it->second.get() : nullptr;
278 }
279
HandleDuplicateServiceRegistration(const std::string & aHostName,const std::string & aName,const std::string & aType,const SubTypeList & aSubTypeList,uint16_t aPort,const TxtList & aTxtList,ResultCallback && aCallback)280 Publisher::ResultCallback Publisher::HandleDuplicateServiceRegistration(const std::string &aHostName,
281 const std::string &aName,
282 const std::string &aType,
283 const SubTypeList &aSubTypeList,
284 uint16_t aPort,
285 const TxtList & aTxtList,
286 ResultCallback && aCallback)
287 {
288 ServiceRegistration *serviceReg = FindServiceRegistration(aName, aType);
289
290 VerifyOrExit(serviceReg != nullptr);
291
292 if (serviceReg->IsOutdated(aHostName, aName, aType, aSubTypeList, aPort, aTxtList))
293 {
294 otbrLogInfo("Removing existing service %s.%s: outdated", aName.c_str(), aType.c_str());
295 RemoveServiceRegistration(aName, aType, OTBR_ERROR_ABORTED);
296 }
297 else if (serviceReg->IsCompleted())
298 {
299 // Returns success if the same service has already been
300 // registered with exactly the same parameters.
301 std::move(aCallback)(OTBR_ERROR_NONE);
302 }
303 else
304 {
305 // If the same service is being registered with the same parameters,
306 // let's join the waiting queue for the result.
307 serviceReg->mCallback = std::bind(
308 [](std::shared_ptr<ResultCallback> aExistingCallback, std::shared_ptr<ResultCallback> aNewCallback,
309 otbrError aError) {
310 std::move (*aExistingCallback)(aError);
311 std::move (*aNewCallback)(aError);
312 },
313 std::make_shared<ResultCallback>(std::move(serviceReg->mCallback)),
314 std::make_shared<ResultCallback>(std::move(aCallback)), std::placeholders::_1);
315 }
316
317 exit:
318 return std::move(aCallback);
319 }
320
HandleDuplicateHostRegistration(const std::string & aName,const std::vector<uint8_t> & aAddress,ResultCallback && aCallback)321 Publisher::ResultCallback Publisher::HandleDuplicateHostRegistration(const std::string & aName,
322 const std::vector<uint8_t> &aAddress,
323 ResultCallback && aCallback)
324 {
325 HostRegistration *hostReg = FindHostRegistration(aName);
326
327 VerifyOrExit(hostReg != nullptr);
328
329 if (hostReg->IsOutdated(aName, aAddress))
330 {
331 otbrLogInfo("Removing existing host %s: outdated", aName.c_str());
332 RemoveHostRegistration(hostReg->mName, OTBR_ERROR_ABORTED);
333 }
334 else if (hostReg->IsCompleted())
335 {
336 // Returns success if the same service has already been
337 // registered with exactly the same parameters.
338 std::move(aCallback)(OTBR_ERROR_NONE);
339 }
340 else
341 {
342 // If the same service is being registered with the same parameters,
343 // let's join the waiting queue for the result.
344 hostReg->mCallback = std::bind(
345 [](std::shared_ptr<ResultCallback> aExistingCallback, std::shared_ptr<ResultCallback> aNewCallback,
346 otbrError aError) {
347 std::move (*aExistingCallback)(aError);
348 std::move (*aNewCallback)(aError);
349 },
350 std::make_shared<ResultCallback>(std::move(hostReg->mCallback)),
351 std::make_shared<ResultCallback>(std::move(aCallback)), std::placeholders::_1);
352 }
353
354 exit:
355 return std::move(aCallback);
356 }
357
AddHostRegistration(HostRegistrationPtr && aHostReg)358 void Publisher::AddHostRegistration(HostRegistrationPtr &&aHostReg)
359 {
360 mHostRegistrations.emplace(MakeFullHostName(aHostReg->mName), std::move(aHostReg));
361 }
362
RemoveHostRegistration(const std::string & aName,otbrError aError)363 void Publisher::RemoveHostRegistration(const std::string &aName, otbrError aError)
364 {
365 auto it = mHostRegistrations.find(MakeFullHostName(aName));
366 HostRegistrationPtr hostReg;
367
368 otbrLogInfo("Removing host %s", aName.c_str());
369 VerifyOrExit(it != mHostRegistrations.end());
370
371 // Keep the HostRegistration around before calling `Complete`
372 // to invoke the callback. This is for avoiding invalid access
373 // to the HostRegistration when it's freed from the callback.
374 hostReg = std::move(it->second);
375 mHostRegistrations.erase(it);
376 hostReg->Complete(aError);
377
378 exit:
379 return;
380 }
381
FindHostRegistration(const std::string & aName)382 Publisher::HostRegistration *Publisher::FindHostRegistration(const std::string &aName)
383 {
384 auto it = mHostRegistrations.find(MakeFullHostName(aName));
385
386 return it != mHostRegistrations.end() ? it->second.get() : nullptr;
387 }
388
~Registration(void)389 Publisher::Registration::~Registration(void)
390 {
391 TriggerCompleteCallback(OTBR_ERROR_ABORTED);
392 }
393
IsOutdated(const std::string & aHostName,const std::string & aName,const std::string & aType,const SubTypeList & aSubTypeList,uint16_t aPort,const TxtList & aTxtList) const394 bool Publisher::ServiceRegistration::IsOutdated(const std::string &aHostName,
395 const std::string &aName,
396 const std::string &aType,
397 const SubTypeList &aSubTypeList,
398 uint16_t aPort,
399 const TxtList & aTxtList) const
400 {
401 return !(mHostName == aHostName && mName == aName && mType == aType && mSubTypeList == aSubTypeList &&
402 mPort == aPort && mTxtList == aTxtList);
403 }
404
Complete(otbrError aError)405 void Publisher::ServiceRegistration::Complete(otbrError aError)
406 {
407 OnComplete(aError);
408 Registration::TriggerCompleteCallback(aError);
409 }
410
OnComplete(otbrError aError)411 void Publisher::ServiceRegistration::OnComplete(otbrError aError)
412 {
413 if (!IsCompleted())
414 {
415 mPublisher->UpdateMdnsResponseCounters(mPublisher->mTelemetryInfo.mServiceRegistrations, aError);
416 mPublisher->UpdateServiceRegistrationEmaLatency(mName, mType, aError);
417 }
418 }
419
IsOutdated(const std::string & aName,const std::vector<uint8_t> & aAddress) const420 bool Publisher::HostRegistration::IsOutdated(const std::string &aName, const std::vector<uint8_t> &aAddress) const
421 {
422 return !(mName == aName && mAddress == aAddress);
423 }
424
Complete(otbrError aError)425 void Publisher::HostRegistration::Complete(otbrError aError)
426 {
427 OnComplete(aError);
428 Registration::TriggerCompleteCallback(aError);
429 }
430
OnComplete(otbrError aError)431 void Publisher::HostRegistration::OnComplete(otbrError aError)
432 {
433 if (!IsCompleted())
434 {
435 mPublisher->UpdateMdnsResponseCounters(mPublisher->mTelemetryInfo.mHostRegistrations, aError);
436 mPublisher->UpdateHostRegistrationEmaLatency(mName, aError);
437 }
438 }
439
UpdateMdnsResponseCounters(otbr::MdnsResponseCounters & aCounters,otbrError aError)440 void Publisher::UpdateMdnsResponseCounters(otbr::MdnsResponseCounters &aCounters, otbrError aError)
441 {
442 switch (aError)
443 {
444 case OTBR_ERROR_NONE:
445 ++aCounters.mSuccess;
446 break;
447 case OTBR_ERROR_NOT_FOUND:
448 ++aCounters.mNotFound;
449 break;
450 case OTBR_ERROR_INVALID_ARGS:
451 ++aCounters.mInvalidArgs;
452 break;
453 case OTBR_ERROR_DUPLICATED:
454 ++aCounters.mDuplicated;
455 break;
456 case OTBR_ERROR_NOT_IMPLEMENTED:
457 ++aCounters.mNotImplemented;
458 break;
459 case OTBR_ERROR_MDNS:
460 default:
461 ++aCounters.mUnknownError;
462 break;
463 }
464 }
465
UpdateEmaLatency(uint32_t & aEmaLatency,uint32_t aLatency,otbrError aError)466 void Publisher::UpdateEmaLatency(uint32_t &aEmaLatency, uint32_t aLatency, otbrError aError)
467 {
468 VerifyOrExit(aError != OTBR_ERROR_ABORTED);
469
470 if (!aEmaLatency)
471 {
472 aEmaLatency = aLatency;
473 }
474 else
475 {
476 aEmaLatency =
477 (aLatency * MdnsTelemetryInfo::kEmaFactorNumerator +
478 aEmaLatency * (MdnsTelemetryInfo::kEmaFactorDenominator - MdnsTelemetryInfo::kEmaFactorNumerator)) /
479 MdnsTelemetryInfo::kEmaFactorDenominator;
480 }
481
482 exit:
483 return;
484 }
485
UpdateServiceRegistrationEmaLatency(const std::string & aInstanceName,const std::string & aType,otbrError aError)486 void Publisher::UpdateServiceRegistrationEmaLatency(const std::string &aInstanceName,
487 const std::string &aType,
488 otbrError aError)
489 {
490 auto it = mServiceRegistrationBeginTime.find(std::make_pair(aInstanceName, aType));
491
492 if (it != mServiceRegistrationBeginTime.end())
493 {
494 uint32_t latency = std::chrono::duration_cast<Milliseconds>(Clock::now() - it->second).count();
495 UpdateEmaLatency(mTelemetryInfo.mServiceRegistrationEmaLatency, latency, aError);
496 mServiceRegistrationBeginTime.erase(it);
497 }
498 }
499
UpdateHostRegistrationEmaLatency(const std::string & aHostName,otbrError aError)500 void Publisher::UpdateHostRegistrationEmaLatency(const std::string &aHostName, otbrError aError)
501 {
502 auto it = mHostRegistrationBeginTime.find(aHostName);
503
504 if (it != mHostRegistrationBeginTime.end())
505 {
506 uint32_t latency = std::chrono::duration_cast<Milliseconds>(Clock::now() - it->second).count();
507 UpdateEmaLatency(mTelemetryInfo.mHostRegistrationEmaLatency, latency, aError);
508 mHostRegistrationBeginTime.erase(it);
509 }
510 }
511
UpdateServiceInstanceResolutionEmaLatency(const std::string & aInstanceName,const std::string & aType,otbrError aError)512 void Publisher::UpdateServiceInstanceResolutionEmaLatency(const std::string &aInstanceName,
513 const std::string &aType,
514 otbrError aError)
515 {
516 auto it = mServiceInstanceResolutionBeginTime.find(std::make_pair(aInstanceName, aType));
517
518 if (it != mServiceInstanceResolutionBeginTime.end())
519 {
520 uint32_t latency = std::chrono::duration_cast<Milliseconds>(Clock::now() - it->second).count();
521 UpdateEmaLatency(mTelemetryInfo.mServiceResolutionEmaLatency, latency, aError);
522 mServiceInstanceResolutionBeginTime.erase(it);
523 }
524 }
525
UpdateHostResolutionEmaLatency(const std::string & aHostName,otbrError aError)526 void Publisher::UpdateHostResolutionEmaLatency(const std::string &aHostName, otbrError aError)
527 {
528 auto it = mHostResolutionBeginTime.find(aHostName);
529
530 if (it != mHostResolutionBeginTime.end())
531 {
532 uint32_t latency = std::chrono::duration_cast<Milliseconds>(Clock::now() - it->second).count();
533 UpdateEmaLatency(mTelemetryInfo.mHostResolutionEmaLatency, latency, aError);
534 mHostResolutionBeginTime.erase(it);
535 }
536 }
537
538 } // namespace Mdns
539
540 } // namespace otbr
541