blob: 17116f21fc277f99c0491aef169a4b5b3666f848 [file] [log] [blame]
/*
*
* Copyright (c) 2023 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 "lib/dnssd/platform/Dnssd.h"
#include <lib/support/CodeUtils.h>
#include <lib/support/FixedBufferAllocator.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.h>
#include <platform/OpenThread/OpenThreadUtils.h>
#include <platform/nxp/common/ConnectivityManagerImpl.h>
#include <openthread/mdns_server.h>
using namespace ::chip::DeviceLayer;
using namespace chip::DeviceLayer::Internal;
namespace chip {
namespace Dnssd {
#define LOCAL_DOMAIN_STRING_SIZE 7
#define ARPA_DOMAIN_STRING_SIZE 22
#define MATTER_DNS_TXT_SIZE 128
// Support both operational and commissionable discovery, so buffers sizes must be worst case.
static constexpr uint8_t kMaxMdnsServiceTxtEntriesNumber =
std::max(Dnssd::CommissionAdvertisingParameters::kTxtMaxNumber, Dnssd::OperationalAdvertisingParameters::kTxtMaxNumber);
static constexpr size_t kTotalMdnsServiceTxtValueSize = std::max(Dnssd::CommissionAdvertisingParameters::kTxtTotalValueSize,
Dnssd::OperationalAdvertisingParameters::kTxtTotalValueSize);
static constexpr size_t kTotalMdnsServiceTxtKeySize =
std::max(Dnssd::CommissionAdvertisingParameters::kTxtTotalKeySize, Dnssd::OperationalAdvertisingParameters::kTxtTotalKeySize);
static constexpr size_t kTotalMdnsServiceTxtBufferSize =
kTotalMdnsServiceTxtKeySize + kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtValueSize;
static const char * GetProtocolString(DnssdServiceProtocol protocol)
{
return protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? "_udp" : "_tcp";
}
struct DnsServiceTxtEntries
{
uint8_t mBuffer[kTotalMdnsServiceTxtBufferSize];
Dnssd::TextEntry mTxtEntries[kMaxMdnsServiceTxtEntriesNumber];
};
struct mDnsQueryCtx
{
void * matterCtx;
chip::Dnssd::DnssdService mMdnsService;
DnsServiceTxtEntries mServiceTxtEntry;
char mServiceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 1];
CHIP_ERROR error;
mDnsQueryCtx(void * context, CHIP_ERROR aError)
{
matterCtx = context;
error = aError;
}
};
static const char * GetProtocolString(DnssdServiceProtocol protocol);
static void OtBrowseCallback(otError aError, const otDnsBrowseResponse * aResponse, void * aContext);
static void OtServiceCallback(otError aError, const otDnsServiceResponse * aResponse, void * aContext);
static void DispatchBrowseEmpty(intptr_t context);
static void DispatchBrowse(intptr_t context);
static void DispatchBrowseNoMemory(intptr_t context);
void DispatchAddressResolve(intptr_t context);
void DispatchResolve(intptr_t context);
void DispatchResolveNoMemory(intptr_t context);
static DnsBrowseCallback mDnsBrowseCallback;
static DnsResolveCallback mDnsResolveCallback;
CHIP_ERROR ResolveBySrp(DnssdService * mdnsReq, otInstance * thrInstancePtr, char * instanceName, void * context);
CHIP_ERROR BrowseBySrp(otInstance * thrInstancePtr, char * serviceName, void * context);
CHIP_ERROR FromSrpCacheToMdnsData(const otSrpServerService * service, const otSrpServerHost * host,
const DnssdService * mdnsQueryReq, chip::Dnssd::DnssdService & mdnsService,
DnsServiceTxtEntries & serviceTxtEntries);
static bool bBrowseInProgress = false;
CHIP_ERROR ChipDnssdInit(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context)
{
CHIP_ERROR error = CHIP_NO_ERROR;
otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance();
uint8_t macBuffer[ConfigurationManager::kPrimaryMACAddressLength];
MutableByteSpan mac(macBuffer);
char hostname[kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 1] = "";
ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetPrimaryMACAddress(mac));
MakeHostName(hostname, sizeof(hostname), mac);
snprintf(hostname + strlen(hostname), sizeof(hostname), ".local.");
error = MapOpenThreadError(otMdnsServerSetHostName(thrInstancePtr, hostname));
initCallback(context, error);
return error;
}
void ChipDnssdShutdown()
{
otMdnsServerStop(ThreadStackMgrImpl().OTInstance());
}
CHIP_ERROR ChipDnssdRemoveServices()
{
otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance();
otMdnsServerMarkServiceForRemoval(thrInstancePtr, nullptr, "_matter._tcp.local.");
otMdnsServerMarkServiceForRemoval(thrInstancePtr, nullptr, "_matterc._udp.local.");
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipDnssdPublishService(const DnssdService * service, DnssdPublishCallback callback, void * context)
{
ReturnErrorCodeIf(service == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance();
otError otErr;
otDnsTxtEntry aTxtEntry;
uint32_t txtBufferOffset = 0;
char fullInstName[Common::kInstanceNameMaxLength + chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 1] =
"";
char serviceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 1] = "";
// secure space for the raw TXT data in the worst-case scenario relevant for Matter:
// each entry consists of txt_entry_size (1B) + txt_entry_key + "=" + txt_entry_data
uint8_t txtBuffer[kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtBufferSize] = { 0 };
if ((strcmp(service->mHostName, "") != 0) && (nullptr == otMdnsServerGetHostName(thrInstancePtr)))
{
char hostname[kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 1] = "";
snprintf(hostname, sizeof(hostname), "%s.local.", service->mHostName);
otMdnsServerSetHostName(thrInstancePtr, hostname);
}
snprintf(serviceType, sizeof(serviceType), "%s.%s.local.", service->mType, GetProtocolString(service->mProtocol));
snprintf(fullInstName, sizeof(fullInstName), "%s.%s", service->mName, serviceType);
for (uint32_t i = 0; i < service->mTextEntrySize; i++)
{
uint32_t keySize = strlen(service->mTextEntries[i].mKey);
// add TXT entry len, + 1 is for '='
*(txtBuffer + txtBufferOffset++) = keySize + service->mTextEntries[i].mDataSize + 1;
// add TXT entry key
memcpy(txtBuffer + txtBufferOffset, service->mTextEntries[i].mKey, keySize);
txtBufferOffset += keySize;
// add TXT entry value if pointer is not null, if pointer is null it means we have bool value
if (service->mTextEntries[i].mData)
{
*(txtBuffer + txtBufferOffset++) = '=';
memcpy(txtBuffer + txtBufferOffset, service->mTextEntries[i].mData, service->mTextEntries[i].mDataSize);
txtBufferOffset += service->mTextEntries[i].mDataSize;
}
}
aTxtEntry.mKey = nullptr;
aTxtEntry.mValue = txtBuffer;
aTxtEntry.mValueLength = txtBufferOffset;
otErr = otMdnsServerAddService(thrInstancePtr, fullInstName, serviceType, service->mSubTypes, service->mSubTypeSize,
service->mPort, &aTxtEntry, 1);
// Ignore duplicate error and threat it as error none
if (otErr == OT_ERROR_DUPLICATED)
otErr = OT_ERROR_NONE;
return MapOpenThreadError(otErr);
}
CHIP_ERROR ChipDnssdFinalizeServiceUpdate()
{
otMdnsServerRemoveMarkedServices(ThreadStackMgrImpl().OTInstance());
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, Inet::IPAddressType addressType,
Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, intptr_t * browseIdentifier)
{
*browseIdentifier = reinterpret_cast<intptr_t>(nullptr);
CHIP_ERROR error = CHIP_NO_ERROR;
CHIP_ERROR srpBrowseError = CHIP_NO_ERROR;
char serviceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + ARPA_DOMAIN_STRING_SIZE + 1] = ""; // +1 for null-terminator
if (type == nullptr || callback == nullptr)
return CHIP_ERROR_INVALID_ARGUMENT;
otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance();
mDnsBrowseCallback = callback;
mDnsQueryCtx * browseContext = Platform::New<mDnsQueryCtx>(context, CHIP_NO_ERROR);
VerifyOrReturnError(browseContext != nullptr, CHIP_ERROR_NO_MEMORY);
// First try to browse the service in the SRP cache, use default.service.arpa as domain name
snprintf(serviceType, sizeof(serviceType), "%s.%s.default.service.arpa.", type, GetProtocolString(protocol));
// After browsing in the SRP cache we will continue with regular mDNS browse
srpBrowseError = BrowseBySrp(thrInstancePtr, serviceType, context);
// Proceed to generate a mDNS query
snprintf(browseContext->mServiceType, sizeof(browseContext->mServiceType), "%s.%s.local.", type, GetProtocolString(protocol));
error = MapOpenThreadError(otMdnsServerBrowse(thrInstancePtr, browseContext->mServiceType, OtBrowseCallback, browseContext));
if (CHIP_NO_ERROR == error)
{
bBrowseInProgress = true;
*browseIdentifier = reinterpret_cast<intptr_t>(browseContext);
}
else
{
if (srpBrowseError == CHIP_NO_ERROR)
{
// In this case, we need to send a final browse indication to signal the Matter App that there are no more
// browse results coming
browseContext->error = error;
DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowseEmpty, reinterpret_cast<intptr_t>(browseContext));
}
else
{
Platform::Delete<mDnsQueryCtx>(browseContext);
}
}
return error;
}
CHIP_ERROR ChipDnssdStopBrowse(intptr_t browseIdentifier)
{
mDnsQueryCtx * browseContext = reinterpret_cast<mDnsQueryCtx *>(browseIdentifier);
otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance();
otError error = OT_ERROR_INVALID_ARGS;
// browseContext is only valid when bBrowseInProgress is true. The Matter stack can call this function even with a browseContext
// that has been freed in DispatchBrowseEmpty before.
if ((true == bBrowseInProgress) && (browseContext))
{
browseContext->error = MapOpenThreadError(otMdnsServerStopQuery(thrInstancePtr, browseContext->mServiceType));
// browse context will be freed in DispatchBrowseEmpty
DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowseEmpty, reinterpret_cast<intptr_t>(browseContext));
}
return MapOpenThreadError(error);
}
CHIP_ERROR ChipDnssdResolve(DnssdService * browseResult, Inet::InterfaceId interface, DnssdResolveCallback callback, void * context)
{
ChipError error;
if (browseResult == nullptr || callback == nullptr)
return CHIP_ERROR_INVALID_ARGUMENT;
otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance();
mDnsResolveCallback = callback;
char serviceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + ARPA_DOMAIN_STRING_SIZE + 1] = ""; // +1 for null-terminator
char fullInstName[Common::kInstanceNameMaxLength + chip::Dnssd::kDnssdTypeAndProtocolMaxSize + ARPA_DOMAIN_STRING_SIZE + 1] =
"";
// First try to find the service in the SRP cache, use default.service.arpa as domain name
snprintf(serviceType, sizeof(serviceType), "%s.%s.default.service.arpa.", browseResult->mType,
GetProtocolString(browseResult->mProtocol));
snprintf(fullInstName, sizeof(fullInstName), "%s.%s", browseResult->mName, serviceType);
error = ResolveBySrp(browseResult, thrInstancePtr, fullInstName, context);
if (CHIP_ERROR_NOT_FOUND == error)
{
// If the SRP cache returns not found, proceed to generate a MDNS query
memset(serviceType, 0, sizeof(serviceType));
memset(fullInstName, 0, sizeof(fullInstName));
snprintf(serviceType, sizeof(serviceType), "%s.%s.local.", browseResult->mType, GetProtocolString(browseResult->mProtocol));
snprintf(fullInstName, sizeof(fullInstName), "%s.%s", browseResult->mName, serviceType);
return MapOpenThreadError(otMdnsServerResolveService(thrInstancePtr, fullInstName, OtServiceCallback, context));
}
else
{
return error;
}
}
CHIP_ERROR BrowseBySrp(otInstance * thrInstancePtr, char * serviceName, void * context)
{
const otSrpServerHost * host = nullptr;
const otSrpServerService * service = nullptr;
CHIP_ERROR error = CHIP_ERROR_NOT_FOUND;
while ((host = otSrpServerGetNextHost(thrInstancePtr, host)) != nullptr)
{
service = otSrpServerHostFindNextService(host, service, OT_SRP_SERVER_FLAGS_ANY_TYPE_ACTIVE_SERVICE, serviceName, nullptr);
if (service != nullptr)
{
mDnsQueryCtx * serviceContext;
serviceContext = Platform::New<mDnsQueryCtx>(context, CHIP_NO_ERROR);
if (serviceContext != nullptr)
{
if (CHIP_NO_ERROR ==
FromSrpCacheToMdnsData(service, host, nullptr, serviceContext->mMdnsService, serviceContext->mServiceTxtEntry))
{
// Set error to CHIP_NO_ERROR to signal that there was at least one service found in the cache
error = CHIP_NO_ERROR;
DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowse, reinterpret_cast<intptr_t>(serviceContext));
}
else
{
Platform::Delete<mDnsQueryCtx>(serviceContext);
}
}
}
}
return error;
}
CHIP_ERROR ResolveBySrp(DnssdService * mdnsReq, otInstance * thrInstancePtr, char * instanceName, void * context)
{
const otSrpServerHost * host = nullptr;
const otSrpServerService * service = nullptr;
CHIP_ERROR error = CHIP_ERROR_NOT_FOUND;
while ((host = otSrpServerGetNextHost(thrInstancePtr, host)) != nullptr)
{
service = otSrpServerHostFindNextService(
host, service, (OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE | OT_SRP_SERVER_SERVICE_FLAG_ACTIVE), nullptr, instanceName);
if (service != nullptr)
{
error = CHIP_NO_ERROR;
mDnsQueryCtx * serviceContext;
serviceContext = Platform::New<mDnsQueryCtx>(context, CHIP_NO_ERROR);
if (serviceContext != nullptr)
{
error =
FromSrpCacheToMdnsData(service, host, mdnsReq, serviceContext->mMdnsService, serviceContext->mServiceTxtEntry);
if (error == CHIP_NO_ERROR)
{
DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolve, reinterpret_cast<intptr_t>(serviceContext));
}
else
{
Platform::Delete<mDnsQueryCtx>(serviceContext);
}
}
else
{
error = CHIP_ERROR_NO_MEMORY;
}
break;
}
}
return error;
}
CHIP_ERROR FromSrpCacheToMdnsData(const otSrpServerService * service, const otSrpServerHost * host,
const DnssdService * mdnsQueryReq, chip::Dnssd::DnssdService & mdnsService,
DnsServiceTxtEntries & serviceTxtEntries)
{
const char * tmpName;
const uint8_t * txtStringPtr;
size_t substringSize;
uint8_t addrNum = 0;
uint16_t txtDataLen;
const otIp6Address * ip6AddrPtr = otSrpServerHostGetAddresses(host, &addrNum);
if (mdnsQueryReq != nullptr)
{
Platform::CopyString(mdnsService.mName, sizeof(mdnsService.mName), mdnsQueryReq->mName);
Platform::CopyString(mdnsService.mType, sizeof(mdnsService.mType), mdnsQueryReq->mType);
mdnsService.mProtocol = mdnsQueryReq->mProtocol;
}
else
{
tmpName = otSrpServerServiceGetInstanceName(service);
// Extract from the <instance>.<type>.<protocol>.<domain-name>. the <instance> part
size_t substringSize = strchr(tmpName, '.') - tmpName;
if (substringSize >= ArraySize(mdnsService.mName))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
Platform::CopyString(mdnsService.mName, substringSize + 1, tmpName);
// Extract from the <instance>.<type>.<protocol>.<domain-name>. the <type> part.
tmpName = tmpName + substringSize + 1;
substringSize = strchr(tmpName, '.') - tmpName;
if (substringSize >= ArraySize(mdnsService.mType))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
Platform::CopyString(mdnsService.mType, substringSize + 1, tmpName);
// Extract from the <instance>.<type>.<protocol>.<domain-name>. the <type> part.
tmpName = tmpName + substringSize + 1;
substringSize = strchr(tmpName, '.') - tmpName;
if (substringSize >= (chip::Dnssd::kDnssdProtocolTextMaxSize + 1))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (strncmp(tmpName, "_udp", substringSize) == 0)
{
mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUdp;
}
else if (strncmp(tmpName, "_tcp", substringSize) == 0)
{
mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolTcp;
}
else
{
mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUnknown;
}
}
// Extract from the <hostname>.<domain-name>. the <hostname> part.
tmpName = otSrpServerHostGetFullName(host);
substringSize = strchr(tmpName, '.') - tmpName;
if (substringSize >= ArraySize(mdnsService.mHostName))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
Platform::CopyString(mdnsService.mHostName, substringSize + 1, tmpName);
mdnsService.mPort = otSrpServerServiceGetPort(service);
// All SRP cache hits come from the Thread Netif
mdnsService.mInterface = ConnectivityManagerImpl().GetThreadInterface();
mdnsService.mAddressType = Inet::IPAddressType::kIPv6;
mdnsService.mAddress = MakeOptional(ToIPAddress(*ip6AddrPtr));
// Extract TXT record SRP service
txtStringPtr = otSrpServerServiceGetTxtData(service, &txtDataLen);
if (txtDataLen != 0)
{
otDnsTxtEntryIterator iterator;
otDnsInitTxtEntryIterator(&iterator, txtStringPtr, txtDataLen);
otDnsTxtEntry txtEntry;
chip::FixedBufferAllocator alloc(serviceTxtEntries.mBuffer);
uint8_t entryIndex = 0;
while ((otDnsGetNextTxtEntry(&iterator, &txtEntry) == OT_ERROR_NONE) && entryIndex < 64)
{
if (txtEntry.mKey == nullptr || txtEntry.mValue == nullptr)
continue;
serviceTxtEntries.mTxtEntries[entryIndex].mKey = alloc.Clone(txtEntry.mKey);
serviceTxtEntries.mTxtEntries[entryIndex].mData = alloc.Clone(txtEntry.mValue, txtEntry.mValueLength);
serviceTxtEntries.mTxtEntries[entryIndex].mDataSize = txtEntry.mValueLength;
entryIndex++;
}
ReturnErrorCodeIf(alloc.AnyAllocFailed(), CHIP_ERROR_BUFFER_TOO_SMALL);
mdnsService.mTextEntries = serviceTxtEntries.mTxtEntries;
mdnsService.mTextEntrySize = entryIndex;
}
else
{
mdnsService.mTextEntrySize = 0;
}
mdnsService.mSubTypes = nullptr;
mdnsService.mSubTypeSize = 0;
return CHIP_NO_ERROR;
}
CHIP_ERROR FromOtDnsResponseToMdnsData(otDnsServiceInfo & serviceInfo, const char * serviceType,
chip::Dnssd::DnssdService & mdnsService, DnsServiceTxtEntries & serviceTxtEntries,
otError error)
{
char protocol[chip::Dnssd::kDnssdProtocolTextMaxSize + 1];
if (strchr(serviceType, '.') == nullptr)
return CHIP_ERROR_INVALID_ARGUMENT;
// Extract from the <type>.<protocol>.<domain-name>. the <type> part.
size_t substringSize = strchr(serviceType, '.') - serviceType;
if (substringSize >= ArraySize(mdnsService.mType))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
Platform::CopyString(mdnsService.mType, substringSize + 1, serviceType);
// Extract from the <type>.<protocol>.<domain-name>. the <protocol> part.
const char * protocolSubstringStart = serviceType + substringSize + 1;
if (strchr(protocolSubstringStart, '.') == nullptr)
return CHIP_ERROR_INVALID_ARGUMENT;
substringSize = strchr(protocolSubstringStart, '.') - protocolSubstringStart;
if (substringSize >= ArraySize(protocol))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
Platform::CopyString(protocol, substringSize + 1, protocolSubstringStart);
if (strncmp(protocol, "_udp", chip::Dnssd::kDnssdProtocolTextMaxSize) == 0)
{
mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUdp;
}
else if (strncmp(protocol, "_tcp", chip::Dnssd::kDnssdProtocolTextMaxSize) == 0)
{
mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolTcp;
}
else
{
mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUnknown;
}
// Check if SRV record was included in DNS response.
if (error != OT_ERROR_NOT_FOUND)
{
if (strchr(serviceInfo.mHostNameBuffer, '.') == nullptr)
return CHIP_ERROR_INVALID_ARGUMENT;
// Extract from the <hostname>.<domain-name>. the <hostname> part.
substringSize = strchr(serviceInfo.mHostNameBuffer, '.') - serviceInfo.mHostNameBuffer;
if (substringSize >= ArraySize(mdnsService.mHostName))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
Platform::CopyString(mdnsService.mHostName, substringSize + 1, serviceInfo.mHostNameBuffer);
mdnsService.mPort = serviceInfo.mPort;
}
// All mDNS replies come from the External Netif
mdnsService.mInterface = ConnectivityManagerImpl().GetExternalInterface();
// Check if AAAA record was included in DNS response.
if (!otIp6IsAddressUnspecified(&serviceInfo.mHostAddress))
{
mdnsService.mAddressType = Inet::IPAddressType::kIPv6;
mdnsService.mAddress = MakeOptional(ToIPAddress(serviceInfo.mHostAddress));
}
// Check if TXT record was included in DNS response.
if (serviceInfo.mTxtDataSize != 0)
{
otDnsTxtEntryIterator iterator;
otDnsInitTxtEntryIterator(&iterator, serviceInfo.mTxtData, serviceInfo.mTxtDataSize);
otDnsTxtEntry txtEntry;
chip::FixedBufferAllocator alloc(serviceTxtEntries.mBuffer);
uint8_t entryIndex = 0;
while ((otDnsGetNextTxtEntry(&iterator, &txtEntry) == OT_ERROR_NONE) && entryIndex < 64)
{
if (txtEntry.mKey == nullptr || txtEntry.mValue == nullptr)
continue;
serviceTxtEntries.mTxtEntries[entryIndex].mKey = alloc.Clone(txtEntry.mKey);
serviceTxtEntries.mTxtEntries[entryIndex].mData = alloc.Clone(txtEntry.mValue, txtEntry.mValueLength);
serviceTxtEntries.mTxtEntries[entryIndex].mDataSize = txtEntry.mValueLength;
entryIndex++;
}
ReturnErrorCodeIf(alloc.AnyAllocFailed(), CHIP_ERROR_BUFFER_TOO_SMALL);
mdnsService.mTextEntries = serviceTxtEntries.mTxtEntries;
mdnsService.mTextEntrySize = entryIndex;
}
else
{
mdnsService.mTextEntrySize = 0;
}
return CHIP_NO_ERROR;
}
void ChipDnssdResolveNoLongerNeeded(const char * instanceName) {}
CHIP_ERROR ChipDnssdReconfirmRecord(const char * hostname, chip::Inet::IPAddress address, chip::Inet::InterfaceId interface)
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
static void OtBrowseCallback(otError aError, const otDnsBrowseResponse * aResponse, void * aContext)
{
CHIP_ERROR error;
// type buffer size is kDnssdTypeAndProtocolMaxSize + . + kMaxDomainNameSize + . + termination character
char type[Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 3];
// hostname buffer size is kHostNameMaxLength + . + kMaxDomainNameSize + . + termination character
char hostname[Dnssd::kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 3];
// secure space for the raw TXT data in the worst-case scenario relevant for Matter:
// each entry consists of txt_entry_size (1B) + txt_entry_key + "=" + txt_entry_data
uint8_t txtBuffer[kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtBufferSize];
mDnsQueryCtx * browseContext = reinterpret_cast<mDnsQueryCtx *>(aContext);
otDnsServiceInfo serviceInfo;
uint16_t index = 0;
/// TODO: check this code, might be remvoed, or if not free browseContext
if (mDnsBrowseCallback == nullptr)
{
ChipLogError(DeviceLayer, "Invalid dns browse callback");
return;
}
VerifyOrExit(aError == OT_ERROR_NONE, error = MapOpenThreadError(aError));
error = MapOpenThreadError(otDnsBrowseResponseGetServiceName(aResponse, type, sizeof(type)));
VerifyOrExit(error == CHIP_NO_ERROR, );
char serviceName[Dnssd::Common::kInstanceNameMaxLength + 1];
while (otDnsBrowseResponseGetServiceInstance(aResponse, index, serviceName, sizeof(serviceName)) == OT_ERROR_NONE)
{
serviceInfo.mHostNameBuffer = hostname;
serviceInfo.mHostNameBufferSize = sizeof(hostname);
serviceInfo.mTxtData = txtBuffer;
serviceInfo.mTxtDataSize = sizeof(txtBuffer);
otError err = otDnsBrowseResponseGetServiceInfo(aResponse, serviceName, &serviceInfo);
error = MapOpenThreadError(err);
VerifyOrExit(err == OT_ERROR_NOT_FOUND || err == OT_ERROR_NONE, );
mDnsQueryCtx * tmpContext = Platform::New<mDnsQueryCtx>(browseContext->matterCtx, CHIP_NO_ERROR);
VerifyOrExit(tmpContext != nullptr, error = CHIP_ERROR_NO_MEMORY);
error = FromOtDnsResponseToMdnsData(serviceInfo, type, tmpContext->mMdnsService, tmpContext->mServiceTxtEntry, err);
if (CHIP_NO_ERROR == error)
{
// Invoke callback for every service one by one instead of for the whole
// list due to large memory size needed to allocate on stack.
static_assert(ArraySize(tmpContext->mMdnsService.mName) >= ArraySize(serviceName),
"The target buffer must be big enough");
Platform::CopyString(tmpContext->mMdnsService.mName, serviceName);
DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowse, reinterpret_cast<intptr_t>(tmpContext));
}
else
{
Platform::Delete<mDnsQueryCtx>(tmpContext);
}
index++;
}
exit:
// Invoke callback to notify about end-of-browse when OT_ERROR_RESPONSE_TIMEOUT is received, otherwise ignore errors
if (aError == OT_ERROR_RESPONSE_TIMEOUT)
{
DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowseEmpty, reinterpret_cast<intptr_t>(browseContext));
}
}
static void OtServiceCallback(otError aError, const otDnsServiceResponse * aResponse, void * aContext)
{
CHIP_ERROR error;
otError otErr;
otDnsServiceInfo serviceInfo;
mDnsQueryCtx * serviceContext;
bool bStopQuery = false;
// If error is timeout we don't need to inform the Matter app and we can just exit
VerifyOrReturn(aError != OT_ERROR_RESPONSE_TIMEOUT, );
bStopQuery = true;
serviceContext = Platform::New<mDnsQueryCtx>(aContext, MapOpenThreadError(aError));
VerifyOrExit(serviceContext != nullptr, error = CHIP_ERROR_NO_MEMORY);
// type buffer size is kDnssdTypeAndProtocolMaxSize + . + kMaxDomainNameSize + . + termination character
char type[Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 3];
// hostname buffer size is kHostNameMaxLength + . + kMaxDomainNameSize + . + termination character
char hostname[Dnssd::kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 3];
// secure space for the raw TXT data in the worst-case scenario relevant for Matter:
// each entry consists of txt_entry_size (1B) + txt_entry_key + "=" + txt_entry_data
uint8_t txtBuffer[kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtBufferSize];
if (mDnsResolveCallback == nullptr)
{
ChipLogError(DeviceLayer, "Invalid dns resolve callback");
return;
}
VerifyOrExit(aError == OT_ERROR_NONE, error = MapOpenThreadError(aError));
error = MapOpenThreadError(otDnsServiceResponseGetServiceName(aResponse, serviceContext->mMdnsService.mName,
sizeof(serviceContext->mMdnsService.mName), type, sizeof(type)));
VerifyOrExit(error == CHIP_NO_ERROR, );
serviceInfo.mHostNameBuffer = hostname;
serviceInfo.mHostNameBufferSize = sizeof(hostname);
serviceInfo.mTxtData = txtBuffer;
serviceInfo.mTxtDataSize = sizeof(txtBuffer);
otErr = otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo);
error = MapOpenThreadError(otErr);
VerifyOrExit(error == CHIP_NO_ERROR, );
error = FromOtDnsResponseToMdnsData(serviceInfo, type, serviceContext->mMdnsService, serviceContext->mServiceTxtEntry, otErr);
exit:
if (serviceContext == nullptr)
{
DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolveNoMemory, reinterpret_cast<intptr_t>(aContext));
return;
}
serviceContext->error = error;
// If IPv6 address in unspecified (AAAA record not present), send additional DNS query to obtain IPv6 address.
if (otIp6IsAddressUnspecified(&serviceInfo.mHostAddress))
{
DeviceLayer::PlatformMgr().ScheduleWork(DispatchAddressResolve, reinterpret_cast<intptr_t>(serviceContext));
}
else
{
DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolve, reinterpret_cast<intptr_t>(serviceContext));
}
if (bStopQuery)
{
char fullInstName[Common::kInstanceNameMaxLength + chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE +
1] = "";
snprintf(fullInstName, sizeof(fullInstName), "%s.%s", serviceContext->mMdnsService.mName, type);
otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance();
otMdnsServerStopQuery(thrInstancePtr, fullInstName);
}
}
void DispatchBrowseEmpty(intptr_t context)
{
auto * browseContext = reinterpret_cast<mDnsQueryCtx *>(context);
mDnsBrowseCallback(browseContext->matterCtx, nullptr, 0, true, browseContext->error);
Platform::Delete<mDnsQueryCtx>(browseContext);
bBrowseInProgress = false;
}
void DispatchBrowse(intptr_t context)
{
auto * browseContext = reinterpret_cast<mDnsQueryCtx *>(context);
mDnsBrowseCallback(browseContext->matterCtx, &browseContext->mMdnsService, 1, false, browseContext->error);
Platform::Delete<mDnsQueryCtx>(browseContext);
}
void DispatchBrowseNoMemory(intptr_t context)
{
mDnsBrowseCallback(reinterpret_cast<void *>(context), nullptr, 0, true, CHIP_ERROR_NO_MEMORY);
}
void DispatchAddressResolve(intptr_t context)
{
CHIP_ERROR error = CHIP_ERROR_NO_MEMORY; // ResolveAddress(context, OnDnsAddressResolveResult);
// In case of address resolve failure, fill the error code field and dispatch method to end resolve process.
if (error != CHIP_NO_ERROR)
{
mDnsQueryCtx * resolveContext = reinterpret_cast<mDnsQueryCtx *>(context);
resolveContext->error = error;
DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolve, reinterpret_cast<intptr_t>(resolveContext));
}
}
void DispatchResolve(intptr_t context)
{
mDnsQueryCtx * resolveContext = reinterpret_cast<mDnsQueryCtx *>(context);
Dnssd::DnssdService & service = resolveContext->mMdnsService;
Span<Inet::IPAddress> ipAddrs;
if (service.mAddress.has_value())
{
ipAddrs = Span<Inet::IPAddress>(&*service.mAddress, 1);
}
mDnsResolveCallback(resolveContext->matterCtx, &service, ipAddrs, resolveContext->error);
Platform::Delete<mDnsQueryCtx>(resolveContext);
}
void DispatchResolveNoMemory(intptr_t context)
{
Span<Inet::IPAddress> ipAddrs;
mDnsResolveCallback(reinterpret_cast<void *>(context), nullptr, ipAddrs, CHIP_ERROR_NO_MEMORY);
}
} // namespace Dnssd
} // namespace chip