blob: 3f9d5a433a08e6f13e4f744b6059b76265b79be7 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Advertiser.h"
#include <inttypes.h>
#include <stdio.h>
#include "MinimalMdnsServer.h"
#include "ServiceNaming.h"
#include <crypto/RandUtils.h>
#include <lib/dnssd/Advertiser_ImplMinimalMdnsAllocator.h>
#include <lib/dnssd/minimal_mdns/ResponseSender.h>
#include <lib/dnssd/minimal_mdns/Server.h>
#include <lib/dnssd/minimal_mdns/core/FlatAllocatedQName.h>
#include <lib/dnssd/minimal_mdns/responders/IP.h>
#include <lib/dnssd/minimal_mdns/responders/Ptr.h>
#include <lib/dnssd/minimal_mdns/responders/QueryResponder.h>
#include <lib/dnssd/minimal_mdns/responders/Srv.h>
#include <lib/dnssd/minimal_mdns/responders/Txt.h>
#include <lib/support/BytesToHex.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/IntrusiveList.h>
#include <lib/support/StringBuilder.h>
// Enable detailed mDNS logging for received queries
#undef DETAIL_LOGGING
// #define DETAIL_LOGGING
namespace chip {
namespace Dnssd {
namespace {
using namespace mdns::Minimal;
#ifdef DETAIL_LOGGING
const char * ToString(QClass qClass)
{
switch (qClass)
{
case QClass::IN:
return "IN";
default:
return "???";
}
}
const char * ToString(QType qType)
{
switch (qType)
{
case QType::ANY:
return "ANY";
case QType::A:
return "A";
case QType::AAAA:
return "AAAA";
case QType::TXT:
return "TXT";
case QType::SRV:
return "SRV";
case QType::PTR:
return "PTR";
default:
return "???";
}
}
void LogQuery(const QueryData & data)
{
StringBuilder<128> logString;
logString.Add("QUERY ").Add(ToString(data.GetClass())).Add("/").Add(ToString(data.GetType())).Add(": ");
SerializedQNameIterator name = data.GetName();
while (name.Next())
{
logString.Add(name.Value()).Add(".");
}
ChipLogDetail(Discovery, "%s", logString.c_str());
}
#else
void LogQuery(const QueryData & data) {}
#endif
// Max number of records for operational = PTR, SRV, TXT, A, AAAA, I subtype.
constexpr size_t kMaxOperationalRecords = 6;
/// Represents an allocated operational responder.
///
/// Wraps a QueryResponderAllocator.
class OperationalQueryAllocator : public chip::IntrusiveListNodeBase
{
public:
using Allocator = QueryResponderAllocator<kMaxOperationalRecords>;
/// Prefer to use `::New` for allocations instead of this direct call
explicit OperationalQueryAllocator(Allocator * allocator) : mAllocator(allocator) {}
~OperationalQueryAllocator()
{
chip::Platform::Delete(mAllocator);
mAllocator = nullptr;
}
Allocator * GetAllocator() { return mAllocator; }
const Allocator * GetAllocator() const { return mAllocator; }
/// Allocate a new entry for this type.
///
/// May return null on allocation failures.
static OperationalQueryAllocator * New()
{
Allocator * allocator = chip::Platform::New<Allocator>();
if (allocator == nullptr)
{
return nullptr;
}
OperationalQueryAllocator * result = chip::Platform::New<OperationalQueryAllocator>(allocator);
if (result == nullptr)
{
chip::Platform::Delete(allocator);
return nullptr;
}
return result;
}
private:
Allocator * mAllocator = nullptr;
};
class AdvertiserMinMdns : public ServiceAdvertiser,
public MdnsPacketDelegate, // receive query packets
public ParserDelegate // parses queries
{
public:
AdvertiserMinMdns() : mResponseSender(&GlobalMinimalMdnsServer::Server())
{
GlobalMinimalMdnsServer::Instance().SetQueryDelegate(this);
CHIP_ERROR err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissionable.GetQueryResponder());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to set up commissionable responder: %" CHIP_ERROR_FORMAT, err.Format());
}
err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissioner.GetQueryResponder());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to set up commissioner responder: %" CHIP_ERROR_FORMAT, err.Format());
}
}
~AdvertiserMinMdns() override { RemoveServices(); }
// Service advertiser
CHIP_ERROR Init(chip::Inet::EndPointManager<chip::Inet::UDPEndPoint> * udpEndPointManager) override;
void Shutdown() override;
CHIP_ERROR RemoveServices() override;
CHIP_ERROR Advertise(const OperationalAdvertisingParameters & params) override;
CHIP_ERROR Advertise(const CommissionAdvertisingParameters & params) override;
CHIP_ERROR FinalizeServiceUpdate() override { return CHIP_NO_ERROR; }
CHIP_ERROR GetCommissionableInstanceName(char * instanceName, size_t maxLength) override;
// MdnsPacketDelegate
void OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info) override;
// ParserDelegate
void OnHeader(ConstHeaderRef & header) override { mMessageId = header.GetMessageId(); }
void OnResource(ResourceType type, const ResourceData & data) override {}
void OnQuery(const QueryData & data) override;
private:
/// Advertise available records configured within the server
///
/// Usable as boot-time advertisement of available SRV records.
void AdvertiseRecords();
/// Determine if advertisement on the specified interface/address is ok given the
/// interfaces on which the mDNS server is listening
bool ShouldAdvertiseOn(const chip::Inet::InterfaceId id, const chip::Inet::IPAddress & addr);
FullQName GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params);
FullQName GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator,
const OperationalAdvertisingParameters & params);
struct CommonTxtEntryStorage
{
// +2 for all to account for '=' and terminating nullchar
char mrpRetryIntervalIdleBuf[KeySize(TxtFieldKey::kMrpRetryIntervalIdle) + ValSize(TxtFieldKey::kMrpRetryIntervalIdle) + 2];
char mrpRetryIntervalActiveBuf[KeySize(TxtFieldKey::kMrpRetryIntervalActive) +
ValSize(TxtFieldKey::kMrpRetryIntervalActive) + 2];
char tcpSupportedBuf[KeySize(TxtFieldKey::kTcpSupported) + ValSize(TxtFieldKey::kTcpSupported) + 2];
};
template <class Derived>
CHIP_ERROR AddCommonTxtEntries(const BaseAdvertisingParams<Derived> & params, CommonTxtEntryStorage & storage,
char ** txtFields, size_t & numTxtFields)
{
auto optionalMrp = params.GetMRPConfig();
if (optionalMrp.HasValue())
{
auto mrp = optionalMrp.Value();
{
if (mrp.mIdleRetransTimeout > kMaxRetryInterval)
{
ChipLogProgress(Discovery,
"MRP retry interval idle value exceeds allowed range of 1 hour, using maximum available");
mrp.mIdleRetransTimeout = kMaxRetryInterval;
}
size_t writtenCharactersNumber = snprintf(storage.mrpRetryIntervalIdleBuf, sizeof(storage.mrpRetryIntervalIdleBuf),
"CRI=%" PRIu32, mrp.mIdleRetransTimeout.count());
VerifyOrReturnError((writtenCharactersNumber > 0) &&
(writtenCharactersNumber < sizeof(storage.mrpRetryIntervalIdleBuf)),
CHIP_ERROR_INVALID_STRING_LENGTH);
txtFields[numTxtFields++] = storage.mrpRetryIntervalIdleBuf;
}
{
if (mrp.mActiveRetransTimeout > kMaxRetryInterval)
{
ChipLogProgress(Discovery,
"MRP retry interval active value exceeds allowed range of 1 hour, using maximum available");
mrp.mActiveRetransTimeout = kMaxRetryInterval;
}
size_t writtenCharactersNumber =
snprintf(storage.mrpRetryIntervalActiveBuf, sizeof(storage.mrpRetryIntervalActiveBuf), "CRA=%" PRIu32,
mrp.mActiveRetransTimeout.count());
VerifyOrReturnError((writtenCharactersNumber > 0) &&
(writtenCharactersNumber < sizeof(storage.mrpRetryIntervalActiveBuf)),
CHIP_ERROR_INVALID_STRING_LENGTH);
txtFields[numTxtFields++] = storage.mrpRetryIntervalActiveBuf;
}
}
if (params.GetTcpSupported().HasValue())
{
size_t writtenCharactersNumber =
snprintf(storage.tcpSupportedBuf, sizeof(storage.tcpSupportedBuf), "T=%d", params.GetTcpSupported().Value());
VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.tcpSupportedBuf)),
CHIP_ERROR_INVALID_STRING_LENGTH);
txtFields[numTxtFields++] = storage.tcpSupportedBuf;
}
return CHIP_NO_ERROR;
}
IntrusiveList<OperationalQueryAllocator> mOperationalResponders;
// Max number of records for commissionable = 7 x PTR (base + 6 sub types - _S, _L, _D, _T, _C, _A), SRV, TXT, A, AAAA
static constexpr size_t kMaxCommissionRecords = 11;
QueryResponderAllocator<kMaxCommissionRecords> mQueryResponderAllocatorCommissionable;
QueryResponderAllocator<kMaxCommissionRecords> mQueryResponderAllocatorCommissioner;
OperationalQueryAllocator::Allocator * FindOperationalAllocator(const FullQName & qname);
OperationalQueryAllocator::Allocator * FindEmptyOperationalAllocator();
ResponseSender mResponseSender;
uint8_t mCommissionableInstanceName[sizeof(uint64_t)];
// current request handling
const chip::Inet::IPPacketInfo * mCurrentSource = nullptr;
uint32_t mMessageId = 0;
const char * mEmptyTextEntries[1] = {
"=",
};
};
void AdvertiserMinMdns::OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info)
{
#ifdef DETAIL_LOGGING
char srcAddressString[chip::Inet::IPAddress::kMaxStringLength];
VerifyOrDie(info->SrcAddress.ToString(srcAddressString) != nullptr);
ChipLogDetail(Discovery, "Received an mDNS query from %s", srcAddressString);
#endif
mCurrentSource = info;
if (!ParsePacket(data, this))
{
ChipLogError(Discovery, "Failed to parse mDNS query");
}
mCurrentSource = nullptr;
}
void AdvertiserMinMdns::OnQuery(const QueryData & data)
{
if (mCurrentSource == nullptr)
{
ChipLogError(Discovery, "INTERNAL CONSISTENCY ERROR: missing query source");
return;
}
LogQuery(data);
CHIP_ERROR err = mResponseSender.Respond(mMessageId, data, mCurrentSource);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to reply to query: %s", ErrorStr(err));
}
}
CHIP_ERROR AdvertiserMinMdns::Init(chip::Inet::EndPointManager<chip::Inet::UDPEndPoint> * udpEndPointManager)
{
GlobalMinimalMdnsServer::Server().Shutdown();
uint64_t random_instance_name = chip::Crypto::GetRandU64();
memcpy(&mCommissionableInstanceName[0], &random_instance_name, sizeof(mCommissionableInstanceName));
// Re-set the server in the response sender in case this has been swapped in the
// GlobalMinimalMdnsServer (used for testing).
mResponseSender.SetServer(&GlobalMinimalMdnsServer::Server());
ReturnErrorOnFailure(GlobalMinimalMdnsServer::Instance().StartServer(udpEndPointManager, kMdnsPort));
ChipLogProgress(Discovery, "CHIP minimal mDNS started advertising.");
AdvertiseRecords();
return CHIP_NO_ERROR;
}
void AdvertiserMinMdns::Shutdown()
{
GlobalMinimalMdnsServer::Server().Shutdown();
}
CHIP_ERROR AdvertiserMinMdns::RemoveServices()
{
while (mOperationalResponders.begin() != mOperationalResponders.end())
{
auto it = mOperationalResponders.begin();
// Need to free the memory once it is out of the list
OperationalQueryAllocator * ptr = &*it;
// Mark as unused
ptr->GetAllocator()->Clear();
CHIP_ERROR err = mResponseSender.RemoveQueryResponder(ptr->GetAllocator()->GetQueryResponder());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to remove query responder: %" CHIP_ERROR_FORMAT, err.Format());
}
mOperationalResponders.Remove(ptr);
// Finally release the memory
chip::Platform::Delete(ptr);
}
mQueryResponderAllocatorCommissionable.Clear();
mQueryResponderAllocatorCommissioner.Clear();
return CHIP_NO_ERROR;
}
OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindOperationalAllocator(const FullQName & qname)
{
for (auto & it : mOperationalResponders)
{
if (it.GetAllocator()->GetResponder(QType::SRV, qname) != nullptr)
{
return it.GetAllocator();
}
}
return nullptr;
}
OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindEmptyOperationalAllocator()
{
OperationalQueryAllocator * result = OperationalQueryAllocator::New();
if (result == nullptr)
{
return nullptr;
}
CHIP_ERROR err = mResponseSender.AddQueryResponder(result->GetAllocator()->GetQueryResponder());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to register query responder: %" CHIP_ERROR_FORMAT, err.Format());
Platform::Delete(result);
return nullptr;
}
mOperationalResponders.PushBack(result);
return result->GetAllocator();
}
CHIP_ERROR AdvertiserMinMdns::Advertise(const OperationalAdvertisingParameters & params)
{
char nameBuffer[Operational::kInstanceNameMaxLength + 1] = "";
/// need to set server name
ReturnErrorOnFailure(MakeInstanceName(nameBuffer, sizeof(nameBuffer), params.GetPeerId()));
QNamePart nameCheckParts[] = { nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain };
FullQName nameCheck = FullQName(nameCheckParts);
auto * operationalAllocator = FindOperationalAllocator(nameCheck);
if (operationalAllocator != nullptr)
{
operationalAllocator->Clear();
}
else
{
operationalAllocator = FindEmptyOperationalAllocator();
if (operationalAllocator == nullptr)
{
ChipLogError(Discovery, "Failed to find an open operational allocator");
return CHIP_ERROR_NO_MEMORY;
}
}
FullQName serviceName = operationalAllocator->AllocateQName(kOperationalServiceName, kOperationalProtocol, kLocalDomain);
FullQName instanceName =
operationalAllocator->AllocateQName(nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain);
ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac()));
FullQName hostName = operationalAllocator->AllocateQName(nameBuffer, kLocalDomain);
if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0))
{
ChipLogError(Discovery, "Failed to allocate QNames.");
return CHIP_ERROR_NO_MEMORY;
}
if (!operationalAllocator->AddResponder<PtrResponder>(serviceName, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add service PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (!operationalAllocator->AddResponder<SrvResponder>(SrvResourceRecord(instanceName, hostName, params.GetPort()))
.SetReportAdditional(hostName)
.IsValid())
{
ChipLogError(Discovery, "Failed to add SRV record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (!operationalAllocator
->AddResponder<TxtResponder>(TxtResourceRecord(instanceName, GetOperationalTxtEntries(operationalAllocator, params)))
.SetReportAdditional(hostName)
.IsValid())
{
ChipLogError(Discovery, "Failed to add TXT record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (!operationalAllocator->AddResponder<IPv6Responder>(hostName).IsValid())
{
ChipLogError(Discovery, "Failed to add IPv6 mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (params.IsIPv4Enabled())
{
if (!operationalAllocator->AddResponder<IPv4Responder>(hostName).IsValid())
{
ChipLogError(Discovery, "Failed to add IPv4 mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
}
MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
DiscoveryFilter(DiscoveryFilterType::kCompressedFabricId, params.GetPeerId().GetCompressedFabricId()));
FullQName compressedFabricIdSubtype = operationalAllocator->AllocateQName(
nameBuffer, kSubtypeServiceNamePart, kOperationalServiceName, kOperationalProtocol, kLocalDomain);
ReturnErrorCodeIf(compressedFabricIdSubtype.nameCount == 0, CHIP_ERROR_NO_MEMORY);
if (!operationalAllocator->AddResponder<PtrResponder>(compressedFabricIdSubtype, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Operational device'.");
// Advertise the records we just added as required by RFC 6762.
// TODO - Don't announce records that haven't been updated.
AdvertiseRecords();
ChipLogProgress(Discovery, "mDNS service published: %s.%s", instanceName.names[1], instanceName.names[2]);
return CHIP_NO_ERROR;
}
CHIP_ERROR AdvertiserMinMdns::GetCommissionableInstanceName(char * instanceName, size_t maxLength)
{
if (maxLength < (Commission::kInstanceNameMaxLength + 1))
{
return CHIP_ERROR_NO_MEMORY;
}
return chip::Encoding::BytesToUppercaseHexString(&mCommissionableInstanceName[0], sizeof(mCommissionableInstanceName),
instanceName, maxLength);
}
CHIP_ERROR AdvertiserMinMdns::Advertise(const CommissionAdvertisingParameters & params)
{
if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
{
mQueryResponderAllocatorCommissionable.Clear();
}
else
{
mQueryResponderAllocatorCommissioner.Clear();
}
// TODO: need to detect colisions here
char nameBuffer[64] = "";
ReturnErrorOnFailure(GetCommissionableInstanceName(nameBuffer, sizeof(nameBuffer)));
QueryResponderAllocator<kMaxCommissionRecords> * allocator =
params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable
: &mQueryResponderAllocatorCommissioner;
const char * serviceType = params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode
? kCommissionableServiceName
: kCommissionerServiceName;
FullQName serviceName = allocator->AllocateQName(serviceType, kCommissionProtocol, kLocalDomain);
FullQName instanceName = allocator->AllocateQName(nameBuffer, serviceType, kCommissionProtocol, kLocalDomain);
ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac()));
FullQName hostName = allocator->AllocateQName(nameBuffer, kLocalDomain);
if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0))
{
ChipLogError(Discovery, "Failed to allocate QNames.");
return CHIP_ERROR_NO_MEMORY;
}
if (!allocator->AddResponder<PtrResponder>(serviceName, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add service PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (!allocator->AddResponder<SrvResponder>(SrvResourceRecord(instanceName, hostName, params.GetPort()))
.SetReportAdditional(hostName)
.IsValid())
{
ChipLogError(Discovery, "Failed to add SRV record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (!allocator->AddResponder<IPv6Responder>(hostName).IsValid())
{
ChipLogError(Discovery, "Failed to add IPv6 mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (params.IsIPv4Enabled())
{
if (!allocator->AddResponder<IPv4Responder>(hostName).IsValid())
{
ChipLogError(Discovery, "Failed to add IPv4 mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
}
if (params.GetVendorId().HasValue())
{
MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
DiscoveryFilter(DiscoveryFilterType::kVendorId, params.GetVendorId().Value()));
FullQName vendorServiceName =
allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
ReturnErrorCodeIf(vendorServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
if (!allocator->AddResponder<PtrResponder>(vendorServiceName, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add vendor PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
}
if (params.GetDeviceType().HasValue())
{
MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
DiscoveryFilter(DiscoveryFilterType::kDeviceType, params.GetDeviceType().Value()));
FullQName vendorServiceName =
allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
ReturnErrorCodeIf(vendorServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
if (!allocator->AddResponder<PtrResponder>(vendorServiceName, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
}
// the following sub types only apply to commissionable node advertisements
if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
{
{
MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
DiscoveryFilter(DiscoveryFilterType::kShortDiscriminator, params.GetShortDiscriminator()));
FullQName shortServiceName =
allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
ReturnErrorCodeIf(shortServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
if (!allocator->AddResponder<PtrResponder>(shortServiceName, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add short discriminator PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
}
{
MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
DiscoveryFilter(DiscoveryFilterType::kLongDiscriminator, params.GetLongDiscriminator()));
FullQName longServiceName =
allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
ReturnErrorCodeIf(longServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
if (!allocator->AddResponder<PtrResponder>(longServiceName, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add long discriminator PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
}
if (params.GetCommissioningMode() != CommissioningMode::kDisabled)
{
MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kCommissioningMode));
FullQName longServiceName =
allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
ReturnErrorCodeIf(longServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
if (!allocator->AddResponder<PtrResponder>(longServiceName, instanceName)
.SetReportAdditional(instanceName)
.SetReportInServiceListing(true)
.IsValid())
{
ChipLogError(Discovery, "Failed to add commissioning mode PTR record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
}
}
if (!allocator->AddResponder<TxtResponder>(TxtResourceRecord(instanceName, GetCommissioningTxtEntries(params)))
.SetReportAdditional(hostName)
.IsValid())
{
ChipLogError(Discovery, "Failed to add TXT record mDNS responder");
return CHIP_ERROR_NO_MEMORY;
}
if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
{
ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissionable node device'.");
}
else
{
ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissioner device'.");
}
// Advertise the records we just added as required by RFC 6762.
// TODO - Don't announce records that haven't been updated.
AdvertiseRecords();
ChipLogProgress(Discovery, "mDNS service published: %s.%s", instanceName.names[1], instanceName.names[2]);
return CHIP_NO_ERROR;
}
FullQName AdvertiserMinMdns::GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator,
const OperationalAdvertisingParameters & params)
{
char * txtFields[OperationalAdvertisingParameters::kTxtMaxNumber];
size_t numTxtFields = 0;
struct CommonTxtEntryStorage commonStorage;
AddCommonTxtEntries<OperationalAdvertisingParameters>(params, commonStorage, txtFields, numTxtFields);
if (numTxtFields == 0)
{
return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1);
}
return allocator->AllocateQNameFromArray(txtFields, numTxtFields);
}
FullQName AdvertiserMinMdns::GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params)
{
char * txtFields[CommissionAdvertisingParameters::kTxtMaxNumber];
size_t numTxtFields = 0;
QueryResponderAllocator<kMaxCommissionRecords> * allocator =
params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable
: &mQueryResponderAllocatorCommissioner;
char txtVidPid[chip::Dnssd::kKeyVendorProductMaxLength + 4];
if (params.GetProductId().HasValue() && params.GetVendorId().HasValue())
{
snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d+%d", params.GetVendorId().Value(), params.GetProductId().Value());
txtFields[numTxtFields++] = txtVidPid;
}
else if (params.GetVendorId().HasValue())
{
snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d", params.GetVendorId().Value());
txtFields[numTxtFields++] = txtVidPid;
}
char txtDeviceType[chip::Dnssd::kKeyDeviceTypeMaxLength + 4];
if (params.GetDeviceType().HasValue())
{
snprintf(txtDeviceType, sizeof(txtDeviceType), "DT=%" PRIu32, params.GetDeviceType().Value());
txtFields[numTxtFields++] = txtDeviceType;
}
char txtDeviceName[chip::Dnssd::kKeyDeviceNameMaxLength + 4];
if (params.GetDeviceName().HasValue())
{
snprintf(txtDeviceName, sizeof(txtDeviceName), "DN=%s", params.GetDeviceName().Value());
txtFields[numTxtFields++] = txtDeviceName;
}
CommonTxtEntryStorage commonStorage;
AddCommonTxtEntries<CommissionAdvertisingParameters>(params, commonStorage, txtFields, numTxtFields);
// the following sub types only apply to commissionable node advertisements
char txtDiscriminator[chip::Dnssd::kKeyLongDiscriminatorMaxLength + 3];
char txtCommissioningMode[chip::Dnssd::kKeyCommissioningModeMaxLength + 4];
char txtRotatingDeviceId[chip::Dnssd::kKeyRotatingDeviceIdMaxLength + 4];
char txtPairingHint[chip::Dnssd::kKeyPairingInstructionMaxLength + 4];
char txtPairingInstr[chip::Dnssd::kKeyPairingInstructionMaxLength + 4];
if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
{
// a discriminator always exists
snprintf(txtDiscriminator, sizeof(txtDiscriminator), "D=%d", params.GetLongDiscriminator());
txtFields[numTxtFields++] = txtDiscriminator;
snprintf(txtCommissioningMode, sizeof(txtCommissioningMode), "CM=%d", static_cast<int>(params.GetCommissioningMode()));
txtFields[numTxtFields++] = txtCommissioningMode;
if (params.GetRotatingDeviceId().HasValue())
{
snprintf(txtRotatingDeviceId, sizeof(txtRotatingDeviceId), "RI=%s", params.GetRotatingDeviceId().Value());
txtFields[numTxtFields++] = txtRotatingDeviceId;
}
if (params.GetPairingHint().HasValue())
{
snprintf(txtPairingHint, sizeof(txtPairingHint), "PH=%d", params.GetPairingHint().Value());
txtFields[numTxtFields++] = txtPairingHint;
}
if (params.GetPairingInstruction().HasValue())
{
snprintf(txtPairingInstr, sizeof(txtPairingInstr), "PI=%s", params.GetPairingInstruction().Value());
txtFields[numTxtFields++] = txtPairingInstr;
}
}
if (numTxtFields == 0)
{
return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1);
}
return allocator->AllocateQNameFromArray(txtFields, numTxtFields);
}
bool AdvertiserMinMdns::ShouldAdvertiseOn(const chip::Inet::InterfaceId id, const chip::Inet::IPAddress & addr)
{
auto & server = GlobalMinimalMdnsServer::Server();
bool result = false;
server.ForEachEndPoints([&](auto * info) {
if (info->mListenUdp == nullptr)
{
return chip::Loop::Continue;
}
if (info->mInterfaceId != id)
{
return chip::Loop::Continue;
}
if (info->mAddressType != addr.Type())
{
return chip::Loop::Continue;
}
result = true;
return chip::Loop::Break;
});
return result;
}
void AdvertiserMinMdns::AdvertiseRecords()
{
chip::Inet::InterfaceAddressIterator interfaceAddress;
if (!interfaceAddress.Next())
{
return;
}
for (; interfaceAddress.HasCurrent(); interfaceAddress.Next())
{
if (!Internal::IsCurrentInterfaceUsable(interfaceAddress))
{
continue;
}
Inet::IPAddress ipAddress;
if (interfaceAddress.GetAddress(ipAddress) != CHIP_NO_ERROR)
{
continue;
}
if (!ShouldAdvertiseOn(interfaceAddress.GetInterfaceId(), ipAddress))
{
continue;
}
chip::Inet::IPPacketInfo packetInfo;
packetInfo.Clear();
packetInfo.SrcAddress = ipAddress;
if (ipAddress.IsIPv4())
{
BroadcastIpAddresses::GetIpv4Into(packetInfo.DestAddress);
}
else
{
BroadcastIpAddresses::GetIpv6Into(packetInfo.DestAddress);
}
packetInfo.SrcPort = kMdnsPort;
packetInfo.DestPort = kMdnsPort;
packetInfo.Interface = interfaceAddress.GetInterfaceId();
QueryData queryData(QType::PTR, QClass::IN, false /* unicast */);
queryData.SetIsBootAdvertising(true);
for (auto & it : mOperationalResponders)
{
it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle();
}
mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle();
mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle();
CHIP_ERROR err = mResponseSender.Respond(0, queryData, &packetInfo);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to advertise records: %s", ErrorStr(err));
}
}
// Once all automatic broadcasts are done, allow immediate replies once.
for (auto & it : mOperationalResponders)
{
it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle();
}
mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle();
mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle();
}
AdvertiserMinMdns gAdvertiser;
} // namespace
ServiceAdvertiser & ServiceAdvertiser::Instance()
{
return gAdvertiser;
}
} // namespace Dnssd
} // namespace chip