Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020-2022 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include "Advertiser.h"
19 :
20 : #include <inttypes.h>
21 : #include <stdio.h>
22 :
23 : #include "MinimalMdnsServer.h"
24 : #include "ServiceNaming.h"
25 :
26 : #include <app/icd/server/ICDServerConfig.h>
27 : #include <crypto/RandUtils.h>
28 : #include <lib/dnssd/Advertiser_ImplMinimalMdnsAllocator.h>
29 : #include <lib/dnssd/minimal_mdns/AddressPolicy.h>
30 : #include <lib/dnssd/minimal_mdns/ResponseSender.h>
31 : #include <lib/dnssd/minimal_mdns/Server.h>
32 : #include <lib/dnssd/minimal_mdns/core/FlatAllocatedQName.h>
33 : #include <lib/dnssd/minimal_mdns/responders/IP.h>
34 : #include <lib/dnssd/minimal_mdns/responders/Ptr.h>
35 : #include <lib/dnssd/minimal_mdns/responders/QueryResponder.h>
36 : #include <lib/dnssd/minimal_mdns/responders/Srv.h>
37 : #include <lib/dnssd/minimal_mdns/responders/Txt.h>
38 : #include <lib/support/BytesToHex.h>
39 : #include <lib/support/CHIPMem.h>
40 : #include <lib/support/IntrusiveList.h>
41 : #include <lib/support/StringBuilder.h>
42 :
43 : // Enable detailed mDNS logging for received queries
44 : #undef DETAIL_LOGGING
45 : // #define DETAIL_LOGGING
46 :
47 : namespace chip {
48 : namespace Dnssd {
49 : namespace {
50 :
51 : using chip::Platform::UniquePtr;
52 : using namespace mdns::Minimal;
53 :
54 : #ifdef DETAIL_LOGGING
55 : const char * ToString(QClass qClass)
56 : {
57 : switch (qClass)
58 : {
59 : case QClass::IN:
60 : return "IN";
61 : default:
62 : return "???";
63 : }
64 : }
65 :
66 : const char * ToString(QType qType)
67 : {
68 : switch (qType)
69 : {
70 : case QType::ANY:
71 : return "ANY";
72 : case QType::A:
73 : return "A";
74 : case QType::AAAA:
75 : return "AAAA";
76 : case QType::TXT:
77 : return "TXT";
78 : case QType::SRV:
79 : return "SRV";
80 : case QType::PTR:
81 : return "PTR";
82 : default:
83 : return "???";
84 : }
85 : }
86 :
87 : void LogQuery(const QueryData & data)
88 : {
89 : StringBuilder<128> logString;
90 :
91 : logString.Add("QUERY ").Add(ToString(data.GetClass())).Add("/").Add(ToString(data.GetType())).Add(": ");
92 :
93 : SerializedQNameIterator name = data.GetName();
94 : while (name.Next())
95 : {
96 : logString.Add(name.Value()).Add(".");
97 : }
98 :
99 : ChipLogDetail(Discovery, "%s", logString.c_str());
100 : }
101 : #else
102 24 : void LogQuery(const QueryData & data) {}
103 : #endif
104 :
105 : // Max number of records for operational = PTR, SRV, TXT, A, AAAA, I subtype.
106 : constexpr size_t kMaxOperationalRecords = 6;
107 :
108 : /// Represents an allocated operational responder.
109 : ///
110 : /// Wraps a QueryResponderAllocator.
111 : class OperationalQueryAllocator : public chip::IntrusiveListNodeBase<>
112 : {
113 : public:
114 : using Allocator = QueryResponderAllocator<kMaxOperationalRecords>;
115 :
116 : /// Prefer to use `::New` for allocations instead of this direct call
117 7 : explicit OperationalQueryAllocator(Allocator * allocator) : mAllocator(allocator) {}
118 7 : ~OperationalQueryAllocator()
119 : {
120 7 : chip::Platform::Delete(mAllocator);
121 7 : mAllocator = nullptr;
122 7 : }
123 :
124 125 : Allocator * GetAllocator() { return mAllocator; }
125 : const Allocator * GetAllocator() const { return mAllocator; }
126 :
127 : /// Allocate a new entry for this type.
128 : ///
129 : /// May return null on allocation failures.
130 7 : static OperationalQueryAllocator * New()
131 : {
132 7 : Allocator * allocator = chip::Platform::New<Allocator>();
133 :
134 7 : if (allocator == nullptr)
135 : {
136 0 : return nullptr;
137 : }
138 :
139 7 : OperationalQueryAllocator * result = chip::Platform::New<OperationalQueryAllocator>(allocator);
140 7 : if (result == nullptr)
141 : {
142 0 : chip::Platform::Delete(allocator);
143 0 : return nullptr;
144 : }
145 :
146 7 : return result;
147 : }
148 :
149 : private:
150 : Allocator * mAllocator = nullptr;
151 : };
152 :
153 : enum BroadcastAdvertiseType
154 : {
155 : kStarted, // Advertise at startup of all records added, as required by RFC 6762.
156 : kRemovingAll, // sent a TTL 0 for all records, as records are removed
157 : };
158 :
159 : class AdvertiserMinMdns : public ServiceAdvertiser,
160 : public MdnsPacketDelegate, // receive query packets
161 : public ParserDelegate // parses queries
162 : {
163 : public:
164 3 : AdvertiserMinMdns() : mResponseSender(&GlobalMinimalMdnsServer::Server())
165 : {
166 3 : GlobalMinimalMdnsServer::Instance().SetQueryDelegate(this);
167 :
168 3 : CHIP_ERROR err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissionable.GetQueryResponder());
169 :
170 3 : if (err != CHIP_NO_ERROR)
171 : {
172 0 : ChipLogError(Discovery, "Failed to set up commissionable responder: %" CHIP_ERROR_FORMAT, err.Format());
173 : }
174 :
175 3 : err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissioner.GetQueryResponder());
176 3 : if (err != CHIP_NO_ERROR)
177 : {
178 0 : ChipLogError(Discovery, "Failed to set up commissioner responder: %" CHIP_ERROR_FORMAT, err.Format());
179 : }
180 3 : }
181 3 : ~AdvertiserMinMdns() override { ClearServices(); }
182 :
183 : // Service advertiser
184 : CHIP_ERROR Init(chip::Inet::EndPointManager<chip::Inet::UDPEndPoint> * udpEndPointManager) override;
185 0 : bool IsInitialized() override { return mIsInitialized; }
186 : void Shutdown() override;
187 : CHIP_ERROR RemoveServices() override;
188 : CHIP_ERROR Advertise(const OperationalAdvertisingParameters & params) override;
189 : CHIP_ERROR Advertise(const CommissionAdvertisingParameters & params) override;
190 : CHIP_ERROR FinalizeServiceUpdate() override;
191 : CHIP_ERROR GetCommissionableInstanceName(char * instanceName, size_t maxLength) const override;
192 : CHIP_ERROR UpdateCommissionableInstanceName() override;
193 :
194 : // MdnsPacketDelegate
195 : void OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info) override;
196 :
197 : // ParserDelegate
198 24 : void OnHeader(ConstHeaderRef & header) override { mMessageId = header.GetMessageId(); }
199 0 : void OnResource(ResourceType type, const ResourceData & data) override {}
200 : void OnQuery(const QueryData & data) override;
201 :
202 : private:
203 : /// Advertise available records configured within the server.
204 : ///
205 : /// Establishes a type of 'Advertise all currently configured items'
206 : /// for a specific purpose (e.g. boot time advertises everything, shut-down
207 : /// removes all records by advertising a 0 TTL)
208 : void AdvertiseRecords(BroadcastAdvertiseType type);
209 :
210 : FullQName GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params);
211 : FullQName GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator,
212 : const OperationalAdvertisingParameters & params);
213 :
214 : struct CommonTxtEntryStorage
215 : {
216 : // +2 for all to account for '=' and terminating nullchar
217 : char sessionIdleIntervalBuf[KeySize(TxtFieldKey::kSessionIdleInterval) + ValSize(TxtFieldKey::kSessionIdleInterval) + 2];
218 : char sessionActiveIntervalBuf[KeySize(TxtFieldKey::kSessionActiveInterval) + ValSize(TxtFieldKey::kSessionActiveInterval) +
219 : 2];
220 : char sessionActiveThresholdBuf[KeySize(TxtFieldKey::kSessionActiveThreshold) +
221 : ValSize(TxtFieldKey::kSessionActiveThreshold) + 2];
222 : char tcpSupportedBuf[KeySize(TxtFieldKey::kTcpSupported) + ValSize(TxtFieldKey::kTcpSupported) + 2];
223 : char operatingICDAsLITBuf[KeySize(TxtFieldKey::kLongIdleTimeICD) + ValSize(TxtFieldKey::kLongIdleTimeICD) + 2];
224 : };
225 : template <class Derived>
226 19 : CHIP_ERROR AddCommonTxtEntries(const BaseAdvertisingParams<Derived> & params, CommonTxtEntryStorage & storage,
227 : char ** txtFields, size_t & numTxtFields)
228 : {
229 :
230 19 : if (const auto & optionalMrp = params.GetLocalMRPConfig(); optionalMrp.has_value())
231 : {
232 5 : auto mrp = *optionalMrp;
233 :
234 : // An ICD operating as a LIT shall not advertise its slow polling interval.
235 : // Don't include the SII key in the advertisement when operating as so.
236 5 : if (params.GetICDModeToAdvertise() != ICDModeAdvertise::kLIT)
237 : {
238 5 : if (mrp.mIdleRetransTimeout > kMaxRetryInterval)
239 : {
240 0 : ChipLogProgress(Discovery,
241 : "MRP retry interval idle value exceeds allowed range of 1 hour, using maximum available");
242 0 : mrp.mIdleRetransTimeout = kMaxRetryInterval;
243 : }
244 5 : size_t writtenCharactersNumber =
245 5 : static_cast<size_t>(snprintf(storage.sessionIdleIntervalBuf, sizeof(storage.sessionIdleIntervalBuf),
246 : "SII=%" PRIu32, mrp.mIdleRetransTimeout.count()));
247 5 : VerifyOrReturnError((writtenCharactersNumber > 0) &&
248 : (writtenCharactersNumber < sizeof(storage.sessionIdleIntervalBuf)),
249 : CHIP_ERROR_INVALID_STRING_LENGTH);
250 :
251 5 : txtFields[numTxtFields++] = storage.sessionIdleIntervalBuf;
252 : }
253 :
254 : {
255 5 : if (mrp.mActiveRetransTimeout > kMaxRetryInterval)
256 : {
257 2 : ChipLogProgress(Discovery,
258 : "MRP retry interval active value exceeds allowed range of 1 hour, using maximum available");
259 2 : mrp.mActiveRetransTimeout = kMaxRetryInterval;
260 : }
261 5 : size_t writtenCharactersNumber =
262 5 : static_cast<size_t>(snprintf(storage.sessionActiveIntervalBuf, sizeof(storage.sessionActiveIntervalBuf),
263 : "SAI=%" PRIu32, mrp.mActiveRetransTimeout.count()));
264 5 : VerifyOrReturnError((writtenCharactersNumber > 0) &&
265 : (writtenCharactersNumber < sizeof(storage.sessionActiveIntervalBuf)),
266 : CHIP_ERROR_INVALID_STRING_LENGTH);
267 5 : txtFields[numTxtFields++] = storage.sessionActiveIntervalBuf;
268 : }
269 :
270 : {
271 5 : size_t writtenCharactersNumber =
272 5 : static_cast<size_t>(snprintf(storage.sessionActiveThresholdBuf, sizeof(storage.sessionActiveThresholdBuf),
273 5 : "SAT=%u", mrp.mActiveThresholdTime.count()));
274 5 : VerifyOrReturnError((writtenCharactersNumber > 0) &&
275 : (writtenCharactersNumber < sizeof(storage.sessionActiveThresholdBuf)),
276 : CHIP_ERROR_INVALID_STRING_LENGTH);
277 5 : txtFields[numTxtFields++] = storage.sessionActiveThresholdBuf;
278 : }
279 : }
280 :
281 19 : if (params.GetTCPSupportModes() != TCPModeAdvertise::kNone)
282 : {
283 3 : size_t writtenCharactersNumber = static_cast<size_t>(snprintf(storage.tcpSupportedBuf, sizeof(storage.tcpSupportedBuf),
284 3 : "T=%d", static_cast<int>(params.GetTCPSupportModes())));
285 3 : VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.tcpSupportedBuf)),
286 : CHIP_ERROR_INVALID_STRING_LENGTH);
287 3 : txtFields[numTxtFields++] = storage.tcpSupportedBuf;
288 : }
289 :
290 19 : if (params.GetICDModeToAdvertise() != ICDModeAdvertise::kNone)
291 : {
292 2 : size_t writtenCharactersNumber =
293 2 : static_cast<size_t>(snprintf(storage.operatingICDAsLITBuf, sizeof(storage.operatingICDAsLITBuf), "ICD=%d",
294 2 : (params.GetICDModeToAdvertise() == ICDModeAdvertise::kLIT)));
295 2 : VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.operatingICDAsLITBuf)),
296 : CHIP_ERROR_INVALID_STRING_LENGTH);
297 2 : txtFields[numTxtFields++] = storage.operatingICDAsLITBuf;
298 : }
299 19 : return CHIP_NO_ERROR;
300 : }
301 :
302 : IntrusiveList<OperationalQueryAllocator> mOperationalResponders;
303 :
304 : // Max number of records for commissionable = 7 x PTR (base + 6 sub types - _S, _L, _D, _T, _C, _A), SRV, TXT, A, AAAA
305 : static constexpr size_t kMaxCommissionRecords = 11;
306 : QueryResponderAllocator<kMaxCommissionRecords> mQueryResponderAllocatorCommissionable;
307 : QueryResponderAllocator<kMaxCommissionRecords> mQueryResponderAllocatorCommissioner;
308 :
309 : OperationalQueryAllocator::Allocator * FindOperationalAllocator(const FullQName & qname);
310 : OperationalQueryAllocator::Allocator * FindEmptyOperationalAllocator();
311 :
312 : void ClearServices();
313 :
314 : ResponseSender mResponseSender;
315 : uint8_t mCommissionableInstanceName[sizeof(uint64_t)];
316 :
317 : bool mIsInitialized = false;
318 :
319 : // current request handling
320 : const chip::Inet::IPPacketInfo * mCurrentSource = nullptr;
321 : uint16_t mMessageId = 0;
322 :
323 : const char * mEmptyTextEntries[1] = {
324 : "=",
325 : };
326 : };
327 :
328 24 : void AdvertiserMinMdns::OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info)
329 : {
330 : #ifdef DETAIL_LOGGING
331 : char srcAddressString[chip::Inet::IPAddress::kMaxStringLength];
332 : VerifyOrDie(info->SrcAddress.ToString(srcAddressString) != nullptr);
333 : ChipLogDetail(Discovery, "Received an mDNS query from %s", srcAddressString);
334 : #endif
335 :
336 24 : mCurrentSource = info;
337 24 : if (!ParsePacket(data, this))
338 : {
339 0 : ChipLogError(Discovery, "Failed to parse mDNS query");
340 : }
341 24 : mCurrentSource = nullptr;
342 24 : }
343 :
344 24 : void AdvertiserMinMdns::OnQuery(const QueryData & data)
345 : {
346 24 : if (mCurrentSource == nullptr)
347 : {
348 0 : ChipLogError(Discovery, "INTERNAL CONSISTENCY ERROR: missing query source");
349 0 : return;
350 : }
351 :
352 24 : LogQuery(data);
353 :
354 24 : const ResponseConfiguration defaultResponseConfiguration;
355 24 : CHIP_ERROR err = mResponseSender.Respond(mMessageId, data, mCurrentSource, defaultResponseConfiguration);
356 24 : if (err != CHIP_NO_ERROR)
357 : {
358 0 : ChipLogError(Discovery, "Failed to reply to query: %" CHIP_ERROR_FORMAT, err.Format());
359 : }
360 : }
361 :
362 13 : CHIP_ERROR AdvertiserMinMdns::Init(chip::Inet::EndPointManager<chip::Inet::UDPEndPoint> * udpEndPointManager)
363 : {
364 : // TODO: Per API documentation, Init() should be a no-op if mIsInitialized
365 : // is true. But we don't handle updates to our set of interfaces right now,
366 : // so rely on the logic in this function to shut down and restart the
367 : // GlobalMinimalMdnsServer to handle that.
368 13 : GlobalMinimalMdnsServer::Server().ShutdownEndpoints();
369 :
370 13 : if (!mIsInitialized)
371 : {
372 2 : UpdateCommissionableInstanceName();
373 : }
374 :
375 : // Re-set the server in the response sender in case this has been swapped in the
376 : // GlobalMinimalMdnsServer (used for testing).
377 13 : mResponseSender.SetServer(&GlobalMinimalMdnsServer::Server());
378 :
379 13 : ReturnErrorOnFailure(GlobalMinimalMdnsServer::Instance().StartServer(udpEndPointManager, kMdnsPort));
380 :
381 13 : ChipLogProgress(Discovery, "CHIP minimal mDNS started advertising.");
382 :
383 13 : AdvertiseRecords(BroadcastAdvertiseType::kStarted);
384 :
385 13 : mIsInitialized = true;
386 :
387 13 : return CHIP_NO_ERROR;
388 : }
389 :
390 3 : void AdvertiserMinMdns::Shutdown()
391 : {
392 3 : VerifyOrReturn(mIsInitialized);
393 :
394 2 : AdvertiseRecords(BroadcastAdvertiseType::kRemovingAll);
395 :
396 2 : GlobalMinimalMdnsServer::Server().Shutdown();
397 2 : mIsInitialized = false;
398 : }
399 :
400 17 : CHIP_ERROR AdvertiserMinMdns::RemoveServices()
401 : {
402 17 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
403 :
404 : // Send a "goodbye" packet for each RR being removed, as defined in RFC 6762.
405 : // This allows mDNS clients to remove stale cached records which may not be re-added with
406 : // subsequent Advertise() calls. In the case the same records are re-added, this extra
407 : // is not harmful though suboptimal, so this is a subject to improvement in the future.
408 16 : AdvertiseRecords(BroadcastAdvertiseType::kRemovingAll);
409 16 : ClearServices();
410 :
411 16 : return CHIP_NO_ERROR;
412 : }
413 :
414 19 : void AdvertiserMinMdns::ClearServices()
415 : {
416 26 : while (mOperationalResponders.begin() != mOperationalResponders.end())
417 : {
418 7 : auto it = mOperationalResponders.begin();
419 :
420 : // Need to free the memory once it is out of the list
421 7 : OperationalQueryAllocator * ptr = &*it;
422 :
423 : // Mark as unused
424 7 : ptr->GetAllocator()->Clear();
425 :
426 7 : CHIP_ERROR err = mResponseSender.RemoveQueryResponder(ptr->GetAllocator()->GetQueryResponder());
427 7 : if (err != CHIP_NO_ERROR)
428 : {
429 0 : ChipLogError(Discovery, "Failed to remove query responder: %" CHIP_ERROR_FORMAT, err.Format());
430 : }
431 :
432 7 : mOperationalResponders.Remove(ptr);
433 :
434 : // Finally release the memory
435 7 : chip::Platform::Delete(ptr);
436 : }
437 :
438 19 : mQueryResponderAllocatorCommissionable.Clear();
439 19 : mQueryResponderAllocatorCommissioner.Clear();
440 19 : }
441 :
442 8 : OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindOperationalAllocator(const FullQName & qname)
443 : {
444 19 : for (auto & it : mOperationalResponders)
445 : {
446 12 : if (it.GetAllocator()->GetResponder(QType::SRV, qname) != nullptr)
447 : {
448 1 : return it.GetAllocator();
449 : }
450 : }
451 :
452 7 : return nullptr;
453 : }
454 :
455 7 : OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindEmptyOperationalAllocator()
456 : {
457 7 : OperationalQueryAllocator * result = OperationalQueryAllocator::New();
458 :
459 7 : if (result == nullptr)
460 : {
461 0 : return nullptr;
462 : }
463 :
464 7 : CHIP_ERROR err = mResponseSender.AddQueryResponder(result->GetAllocator()->GetQueryResponder());
465 :
466 7 : if (err != CHIP_NO_ERROR)
467 : {
468 0 : ChipLogError(Discovery, "Failed to register query responder: %" CHIP_ERROR_FORMAT, err.Format());
469 0 : Platform::Delete(result);
470 0 : return nullptr;
471 : }
472 :
473 7 : mOperationalResponders.PushBack(result);
474 7 : return result->GetAllocator();
475 : }
476 :
477 8 : CHIP_ERROR AdvertiserMinMdns::Advertise(const OperationalAdvertisingParameters & params)
478 : {
479 8 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
480 :
481 8 : char nameBuffer[Operational::kInstanceNameMaxLength + 1] = "";
482 :
483 : // need to set server name
484 8 : ReturnErrorOnFailure(MakeInstanceName(nameBuffer, sizeof(nameBuffer), params.GetPeerId()));
485 :
486 8 : QNamePart nameCheckParts[] = { nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain };
487 8 : FullQName nameCheck = FullQName(nameCheckParts);
488 8 : auto * operationalAllocator = FindOperationalAllocator(nameCheck);
489 8 : if (operationalAllocator != nullptr)
490 : {
491 1 : operationalAllocator->Clear();
492 : }
493 : else
494 : {
495 7 : operationalAllocator = FindEmptyOperationalAllocator();
496 7 : if (operationalAllocator == nullptr)
497 : {
498 0 : ChipLogError(Discovery, "Failed to find an open operational allocator");
499 0 : return CHIP_ERROR_NO_MEMORY;
500 : }
501 : }
502 :
503 8 : FullQName serviceName = operationalAllocator->AllocateQName(kOperationalServiceName, kOperationalProtocol, kLocalDomain);
504 : FullQName instanceName =
505 8 : operationalAllocator->AllocateQName(nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain);
506 :
507 8 : ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac()));
508 8 : FullQName hostName = operationalAllocator->AllocateQName(nameBuffer, kLocalDomain);
509 :
510 8 : if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0))
511 : {
512 0 : ChipLogError(Discovery, "Failed to allocate QNames.");
513 0 : return CHIP_ERROR_NO_MEMORY;
514 : }
515 :
516 16 : if (!operationalAllocator->AddResponder<PtrResponder>(serviceName, instanceName)
517 8 : .SetReportAdditional(instanceName)
518 8 : .SetReportInServiceListing(true)
519 8 : .IsValid())
520 : {
521 0 : ChipLogError(Discovery, "Failed to add service PTR record mDNS responder");
522 0 : return CHIP_ERROR_NO_MEMORY;
523 : }
524 :
525 : // We are the sole owner of our instanceName, so records keyed on the
526 : // instanceName should have the cache-flush bit set.
527 8 : SrvResourceRecord srvRecord(instanceName, hostName, params.GetPort());
528 8 : srvRecord.SetCacheFlush(true);
529 8 : if (!operationalAllocator->AddResponder<SrvResponder>(srvRecord).SetReportAdditional(hostName).IsValid())
530 : {
531 0 : ChipLogError(Discovery, "Failed to add SRV record mDNS responder");
532 0 : return CHIP_ERROR_NO_MEMORY;
533 : }
534 :
535 8 : TxtResourceRecord txtRecord(instanceName, GetOperationalTxtEntries(operationalAllocator, params));
536 8 : txtRecord.SetCacheFlush(true);
537 8 : if (!operationalAllocator->AddResponder<TxtResponder>(txtRecord).SetReportAdditional(hostName).IsValid())
538 : {
539 0 : ChipLogError(Discovery, "Failed to add TXT record mDNS responder");
540 0 : return CHIP_ERROR_NO_MEMORY;
541 : }
542 :
543 8 : if (!operationalAllocator->AddResponder<IPv6Responder>(hostName).IsValid())
544 : {
545 0 : ChipLogError(Discovery, "Failed to add IPv6 mDNS responder");
546 0 : return CHIP_ERROR_NO_MEMORY;
547 : }
548 :
549 8 : if (params.IsIPv4Enabled())
550 : {
551 8 : if (!operationalAllocator->AddResponder<IPv4Responder>(hostName).IsValid())
552 : {
553 0 : ChipLogError(Discovery, "Failed to add IPv4 mDNS responder");
554 0 : return CHIP_ERROR_NO_MEMORY;
555 : }
556 : }
557 8 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
558 8 : DiscoveryFilter(DiscoveryFilterType::kCompressedFabricId, params.GetPeerId().GetCompressedFabricId()));
559 8 : FullQName compressedFabricIdSubtype = operationalAllocator->AllocateQName(
560 : nameBuffer, kSubtypeServiceNamePart, kOperationalServiceName, kOperationalProtocol, kLocalDomain);
561 8 : VerifyOrReturnError(compressedFabricIdSubtype.nameCount != 0, CHIP_ERROR_NO_MEMORY);
562 :
563 16 : if (!operationalAllocator->AddResponder<PtrResponder>(compressedFabricIdSubtype, instanceName)
564 8 : .SetReportAdditional(instanceName)
565 8 : .SetReportInServiceListing(true)
566 8 : .IsValid())
567 : {
568 0 : ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder");
569 0 : return CHIP_ERROR_NO_MEMORY;
570 : }
571 :
572 8 : ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Operational device'; instance name: %s.", instanceName.names[0]);
573 :
574 8 : AdvertiseRecords(BroadcastAdvertiseType::kStarted);
575 :
576 8 : ChipLogProgress(Discovery, "mDNS service published: %s.%s", StringOrNullMarker(instanceName.names[1]),
577 : StringOrNullMarker(instanceName.names[2]));
578 :
579 8 : return CHIP_NO_ERROR;
580 8 : }
581 :
582 19 : CHIP_ERROR AdvertiserMinMdns::FinalizeServiceUpdate()
583 : {
584 19 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
585 19 : return CHIP_NO_ERROR;
586 : }
587 :
588 12 : CHIP_ERROR AdvertiserMinMdns::GetCommissionableInstanceName(char * instanceName, size_t maxLength) const
589 : {
590 12 : if (maxLength < (Commission::kInstanceNameMaxLength + 1))
591 : {
592 0 : return CHIP_ERROR_NO_MEMORY;
593 : }
594 :
595 12 : return chip::Encoding::BytesToUppercaseHexString(&mCommissionableInstanceName[0], sizeof(mCommissionableInstanceName),
596 12 : instanceName, maxLength);
597 : }
598 :
599 8 : CHIP_ERROR AdvertiserMinMdns::UpdateCommissionableInstanceName()
600 : {
601 8 : uint64_t random_instance_name = chip::Crypto::GetRandU64();
602 : static_assert(sizeof(mCommissionableInstanceName) == sizeof(random_instance_name), "Not copying the right amount of data");
603 8 : memcpy(&mCommissionableInstanceName[0], &random_instance_name, sizeof(mCommissionableInstanceName));
604 8 : return CHIP_NO_ERROR;
605 : }
606 :
607 11 : CHIP_ERROR AdvertiserMinMdns::Advertise(const CommissionAdvertisingParameters & params)
608 : {
609 11 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
610 :
611 11 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
612 : {
613 11 : mQueryResponderAllocatorCommissionable.Clear();
614 : }
615 : else
616 : {
617 0 : mQueryResponderAllocatorCommissioner.Clear();
618 : }
619 :
620 : // TODO: need to detect colisions here
621 11 : char nameBuffer[64] = "";
622 11 : ReturnErrorOnFailure(GetCommissionableInstanceName(nameBuffer, sizeof(nameBuffer)));
623 :
624 : QueryResponderAllocator<kMaxCommissionRecords> * allocator =
625 11 : params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable
626 11 : : &mQueryResponderAllocatorCommissioner;
627 11 : const char * serviceType = params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode
628 : ? kCommissionableServiceName
629 11 : : kCommissionerServiceName;
630 :
631 11 : FullQName serviceName = allocator->AllocateQName(serviceType, kCommissionProtocol, kLocalDomain);
632 11 : FullQName instanceName = allocator->AllocateQName(nameBuffer, serviceType, kCommissionProtocol, kLocalDomain);
633 :
634 11 : ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac()));
635 11 : FullQName hostName = allocator->AllocateQName(nameBuffer, kLocalDomain);
636 :
637 11 : if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0))
638 : {
639 0 : ChipLogError(Discovery, "Failed to allocate QNames.");
640 0 : return CHIP_ERROR_NO_MEMORY;
641 : }
642 :
643 22 : if (!allocator->AddResponder<PtrResponder>(serviceName, instanceName)
644 11 : .SetReportAdditional(instanceName)
645 11 : .SetReportInServiceListing(true)
646 11 : .IsValid())
647 : {
648 0 : ChipLogError(Discovery, "Failed to add service PTR record mDNS responder");
649 0 : return CHIP_ERROR_NO_MEMORY;
650 : }
651 :
652 11 : SrvResourceRecord srvRecord(instanceName, hostName, params.GetPort());
653 11 : srvRecord.SetCacheFlush(true);
654 11 : if (!allocator->AddResponder<SrvResponder>(srvRecord).SetReportAdditional(hostName).IsValid())
655 : {
656 0 : ChipLogError(Discovery, "Failed to add SRV record mDNS responder");
657 0 : return CHIP_ERROR_NO_MEMORY;
658 : }
659 11 : if (!allocator->AddResponder<IPv6Responder>(hostName).IsValid())
660 : {
661 0 : ChipLogError(Discovery, "Failed to add IPv6 mDNS responder");
662 0 : return CHIP_ERROR_NO_MEMORY;
663 : }
664 :
665 11 : if (params.IsIPv4Enabled())
666 : {
667 11 : if (!allocator->AddResponder<IPv4Responder>(hostName).IsValid())
668 : {
669 0 : ChipLogError(Discovery, "Failed to add IPv4 mDNS responder");
670 0 : return CHIP_ERROR_NO_MEMORY;
671 : }
672 : }
673 :
674 11 : if (const auto & vendorId = params.GetVendorId(); vendorId.has_value())
675 : {
676 10 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kVendorId, *vendorId));
677 : FullQName vendorServiceName =
678 10 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
679 10 : VerifyOrReturnError(vendorServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY);
680 :
681 20 : if (!allocator->AddResponder<PtrResponder>(vendorServiceName, instanceName)
682 10 : .SetReportAdditional(instanceName)
683 10 : .SetReportInServiceListing(true)
684 10 : .IsValid())
685 : {
686 0 : ChipLogError(Discovery, "Failed to add vendor PTR record mDNS responder");
687 0 : return CHIP_ERROR_NO_MEMORY;
688 : }
689 : }
690 :
691 11 : if (const auto & deviceType = params.GetDeviceType(); deviceType.has_value())
692 : {
693 3 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kDeviceType, *deviceType));
694 : FullQName vendorServiceName =
695 3 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
696 3 : VerifyOrReturnError(vendorServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY);
697 :
698 6 : if (!allocator->AddResponder<PtrResponder>(vendorServiceName, instanceName)
699 3 : .SetReportAdditional(instanceName)
700 3 : .SetReportInServiceListing(true)
701 3 : .IsValid())
702 : {
703 0 : ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder");
704 0 : return CHIP_ERROR_NO_MEMORY;
705 : }
706 : }
707 :
708 : // the following sub types only apply to commissionable node advertisements
709 11 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
710 : {
711 : {
712 11 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
713 11 : DiscoveryFilter(DiscoveryFilterType::kShortDiscriminator, params.GetShortDiscriminator()));
714 : FullQName shortServiceName =
715 11 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
716 11 : VerifyOrReturnError(shortServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY);
717 :
718 22 : if (!allocator->AddResponder<PtrResponder>(shortServiceName, instanceName)
719 11 : .SetReportAdditional(instanceName)
720 11 : .SetReportInServiceListing(true)
721 11 : .IsValid())
722 : {
723 0 : ChipLogError(Discovery, "Failed to add short discriminator PTR record mDNS responder");
724 0 : return CHIP_ERROR_NO_MEMORY;
725 : }
726 : }
727 :
728 : {
729 11 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
730 11 : DiscoveryFilter(DiscoveryFilterType::kLongDiscriminator, params.GetLongDiscriminator()));
731 : FullQName longServiceName =
732 11 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
733 11 : VerifyOrReturnError(longServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY);
734 22 : if (!allocator->AddResponder<PtrResponder>(longServiceName, instanceName)
735 11 : .SetReportAdditional(instanceName)
736 11 : .SetReportInServiceListing(true)
737 11 : .IsValid())
738 : {
739 0 : ChipLogError(Discovery, "Failed to add long discriminator PTR record mDNS responder");
740 0 : return CHIP_ERROR_NO_MEMORY;
741 : }
742 : }
743 :
744 11 : if (params.GetCommissioningMode() != CommissioningMode::kDisabled)
745 : {
746 10 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kCommissioningMode));
747 : FullQName longServiceName =
748 10 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
749 10 : VerifyOrReturnError(longServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY);
750 20 : if (!allocator->AddResponder<PtrResponder>(longServiceName, instanceName)
751 10 : .SetReportAdditional(instanceName)
752 10 : .SetReportInServiceListing(true)
753 10 : .IsValid())
754 : {
755 0 : ChipLogError(Discovery, "Failed to add commissioning mode PTR record mDNS responder");
756 0 : return CHIP_ERROR_NO_MEMORY;
757 : }
758 : }
759 : }
760 :
761 11 : TxtResourceRecord txtRecord(instanceName, GetCommissioningTxtEntries(params));
762 11 : txtRecord.SetCacheFlush(true);
763 11 : if (!allocator->AddResponder<TxtResponder>(txtRecord).SetReportAdditional(hostName).IsValid())
764 : {
765 0 : ChipLogError(Discovery, "Failed to add TXT record mDNS responder");
766 0 : return CHIP_ERROR_NO_MEMORY;
767 : }
768 :
769 11 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
770 : {
771 11 : ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissionable node device'; instance name: %s.",
772 : StringOrNullMarker(instanceName.names[0]));
773 : }
774 : else
775 : {
776 0 : ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissioner device'; instance name: %s.",
777 : StringOrNullMarker(instanceName.names[0]));
778 : }
779 :
780 11 : AdvertiseRecords(BroadcastAdvertiseType::kStarted);
781 :
782 11 : ChipLogProgress(Discovery, "mDNS service published: %s.%s", StringOrNullMarker(instanceName.names[1]),
783 : StringOrNullMarker(instanceName.names[2]));
784 :
785 11 : return CHIP_NO_ERROR;
786 11 : }
787 :
788 8 : FullQName AdvertiserMinMdns::GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator,
789 : const OperationalAdvertisingParameters & params)
790 : {
791 : char * txtFields[OperationalAdvertisingParameters::kTxtMaxNumber];
792 8 : size_t numTxtFields = 0;
793 :
794 : struct CommonTxtEntryStorage commonStorage;
795 8 : AddCommonTxtEntries<OperationalAdvertisingParameters>(params, commonStorage, txtFields, numTxtFields);
796 8 : if (numTxtFields == 0)
797 : {
798 5 : return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1);
799 : }
800 :
801 3 : return allocator->AllocateQNameFromArray(txtFields, numTxtFields);
802 : }
803 :
804 11 : FullQName AdvertiserMinMdns::GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params)
805 : {
806 : char * txtFields[CommissionAdvertisingParameters::kTxtMaxNumber];
807 11 : size_t numTxtFields = 0;
808 :
809 : QueryResponderAllocator<kMaxCommissionRecords> * allocator =
810 11 : params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable
811 11 : : &mQueryResponderAllocatorCommissioner;
812 :
813 : char txtVidPid[chip::Dnssd::kKeyVendorProductMaxLength + 4];
814 : {
815 11 : const auto & productId = params.GetProductId();
816 11 : const auto & vendorId = params.GetVendorId();
817 11 : if (productId.has_value() && vendorId.has_value())
818 : {
819 10 : snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d+%d", *vendorId, *productId);
820 10 : txtFields[numTxtFields++] = txtVidPid;
821 : }
822 1 : else if (vendorId.has_value())
823 : {
824 0 : snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d", *vendorId);
825 0 : txtFields[numTxtFields++] = txtVidPid;
826 : }
827 : }
828 :
829 : char txtDeviceType[chip::Dnssd::kKeyDeviceTypeMaxLength + 4];
830 11 : if (const auto & deviceType = params.GetDeviceType(); deviceType.has_value())
831 : {
832 3 : snprintf(txtDeviceType, sizeof(txtDeviceType), "DT=%" PRIu32, *deviceType);
833 3 : txtFields[numTxtFields++] = txtDeviceType;
834 : }
835 :
836 : char txtDeviceName[chip::Dnssd::kKeyDeviceNameMaxLength + 4];
837 11 : if (const auto & deviceName = params.GetDeviceName(); deviceName.has_value())
838 : {
839 3 : snprintf(txtDeviceName, sizeof(txtDeviceName), "DN=%s", *deviceName);
840 3 : txtFields[numTxtFields++] = txtDeviceName;
841 : }
842 : CommonTxtEntryStorage commonStorage;
843 11 : AddCommonTxtEntries<CommissionAdvertisingParameters>(params, commonStorage, txtFields, numTxtFields);
844 :
845 : // the following sub types only apply to commissionable node advertisements
846 : char txtDiscriminator[chip::Dnssd::kKeyLongDiscriminatorMaxLength + 3];
847 : char txtCommissioningMode[chip::Dnssd::kKeyCommissioningModeMaxLength + 4];
848 : char txtRotatingDeviceId[chip::Dnssd::kKeyRotatingDeviceIdMaxLength + 4];
849 : char txtPairingHint[chip::Dnssd::kKeyPairingInstructionMaxLength + 4];
850 : char txtPairingInstr[chip::Dnssd::kKeyPairingInstructionMaxLength + 4];
851 :
852 : // the following sub types only apply to commissioner discovery advertisements
853 : char txtCommissionerPasscode[chip::Dnssd::kKeyCommissionerPasscodeMaxLength + 4];
854 :
855 11 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
856 : {
857 : // a discriminator always exists
858 11 : snprintf(txtDiscriminator, sizeof(txtDiscriminator), "D=%d", params.GetLongDiscriminator());
859 11 : txtFields[numTxtFields++] = txtDiscriminator;
860 :
861 11 : snprintf(txtCommissioningMode, sizeof(txtCommissioningMode), "CM=%d", static_cast<int>(params.GetCommissioningMode()));
862 11 : txtFields[numTxtFields++] = txtCommissioningMode;
863 :
864 11 : if (const auto & rotatingDeviceId = params.GetRotatingDeviceId(); rotatingDeviceId.has_value())
865 : {
866 3 : snprintf(txtRotatingDeviceId, sizeof(txtRotatingDeviceId), "RI=%s", *rotatingDeviceId);
867 3 : txtFields[numTxtFields++] = txtRotatingDeviceId;
868 : }
869 :
870 11 : if (const auto & pairingHint = params.GetPairingHint(); pairingHint.has_value())
871 : {
872 10 : snprintf(txtPairingHint, sizeof(txtPairingHint), "PH=%d", *pairingHint);
873 10 : txtFields[numTxtFields++] = txtPairingHint;
874 : }
875 :
876 11 : if (const auto & pairingInstruction = params.GetPairingInstruction(); pairingInstruction.has_value())
877 : {
878 10 : snprintf(txtPairingInstr, sizeof(txtPairingInstr), "PI=%s", *pairingInstruction);
879 10 : txtFields[numTxtFields++] = txtPairingInstr;
880 : }
881 : }
882 : else
883 : {
884 0 : if (params.GetCommissionerPasscodeSupported().value_or(false))
885 : {
886 0 : snprintf(txtCommissionerPasscode, sizeof(txtCommissionerPasscode), "CP=%d", static_cast<int>(1));
887 0 : txtFields[numTxtFields++] = txtCommissionerPasscode;
888 : }
889 : }
890 11 : if (numTxtFields == 0)
891 : {
892 0 : return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1);
893 : }
894 :
895 11 : return allocator->AllocateQNameFromArray(txtFields, numTxtFields);
896 : }
897 :
898 50 : void AdvertiserMinMdns::AdvertiseRecords(BroadcastAdvertiseType type)
899 : {
900 50 : ResponseConfiguration responseConfiguration;
901 50 : if (type == BroadcastAdvertiseType::kRemovingAll)
902 : {
903 : // make a "remove all records now" broadcast
904 18 : responseConfiguration.SetTtlSecondsOverride(0);
905 : }
906 :
907 50 : UniquePtr<ListenIterator> allInterfaces = GetAddressPolicy()->GetListenEndpoints();
908 50 : VerifyOrDieWithMsg(allInterfaces != nullptr, Discovery, "Failed to allocate memory for endpoints.");
909 :
910 50 : chip::Inet::InterfaceId interfaceId;
911 : chip::Inet::IPAddressType addressType;
912 :
913 150 : while (allInterfaces->Next(&interfaceId, &addressType))
914 : {
915 100 : UniquePtr<IpAddressIterator> allIps = GetAddressPolicy()->GetIpAddressesForEndpoint(interfaceId, addressType);
916 100 : VerifyOrDieWithMsg(allIps != nullptr, Discovery, "Failed to allocate memory for ip addresses.");
917 :
918 100 : chip::Inet::IPPacketInfo packetInfo;
919 :
920 100 : packetInfo.Clear();
921 :
922 : // advertising on every interface requires a valid IP address
923 : // since we use "BROADCAST" (unicast is false), we do not actually care about
924 : // the source IP address value, just that it has the right "type"
925 : //
926 : // NOTE: cannot use Broadcast address as the source as they have the type kAny.
927 : //
928 : // TODO: ideally we may want to have a destination that is explicit as "unicast/destIp"
929 : // vs "multicast/addressType". Such a change requires larger code updates.
930 100 : packetInfo.SrcAddress = chip::Inet::IPAddress::Loopback(addressType);
931 100 : packetInfo.DestAddress = BroadcastIpAddresses::Get(addressType);
932 100 : packetInfo.SrcPort = kMdnsPort;
933 100 : packetInfo.DestPort = kMdnsPort;
934 100 : packetInfo.Interface = interfaceId;
935 :
936 : // Advertise all records
937 : //
938 : // TODO: Consider advertising delta changes.
939 : //
940 : // Current advertisement does not have a concept of "delta" to only
941 : // advertise changes. Current implementation is to always
942 : // 1. advertise TTL=0 (clear all caches)
943 : // 2. advertise available records (with longer TTL)
944 : //
945 : // It would be nice if we could selectively advertise what changes, like
946 : // send TTL=0 for anything removed/about to be removed (and only those),
947 : // then only advertise new items added.
948 : //
949 : // This optimization likely will take more logic and state storage, so
950 : // for now it is not done.
951 100 : QueryData queryData(QType::PTR, QClass::IN, false /* unicast */);
952 100 : queryData.SetIsAnnounceBroadcast(true);
953 :
954 156 : for (auto & it : mOperationalResponders)
955 : {
956 56 : it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle();
957 : }
958 100 : mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle();
959 100 : mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle();
960 :
961 100 : CHIP_ERROR err = mResponseSender.Respond(0, queryData, &packetInfo, responseConfiguration);
962 :
963 100 : if (err != CHIP_NO_ERROR)
964 : {
965 30 : ChipLogError(Discovery, "Failed to advertise records: %" CHIP_ERROR_FORMAT, err.Format());
966 : }
967 100 : }
968 :
969 : // Once all automatic broadcasts are done, allow immediate replies once.
970 78 : for (auto & it : mOperationalResponders)
971 : {
972 28 : it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle();
973 : }
974 50 : mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle();
975 50 : mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle();
976 50 : }
977 :
978 : AdvertiserMinMdns gAdvertiser;
979 : } // namespace
980 :
981 : #if CHIP_DNSSD_DEFAULT_MINIMAL
982 :
983 2 : ServiceAdvertiser & GetDefaultAdvertiser()
984 : {
985 2 : return gAdvertiser;
986 : }
987 :
988 : #endif // CHIP_DNSSD_DEFAULT_MINIMAL
989 :
990 : } // namespace Dnssd
991 : } // namespace chip
|