blob: 14139083e09a1314c79b714271501c75f46721cb [file] [log] [blame]
/*
*
* Copyright (c) 2020-2022 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 "WiFiDnssdImpl.h"
#include "lib/dnssd/platform/Dnssd.h"
#include <esp_err.h>
#include <esp_netif_net_stack.h>
#include <lwip/ip4_addr.h>
#include <lwip/ip6_addr.h>
#include "platform/CHIPDeviceLayer.h"
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/ESP32/ESP32Utils.h>
namespace {
static constexpr uint32_t kTimeoutMilli = 3000;
static constexpr size_t kMaxResults = 20;
} // namespace
namespace chip {
namespace Dnssd {
struct MdnsQuery
{
GenericContext * ctx;
MdnsQuery * next;
};
static MdnsQuery * sQueryList = nullptr;
static void MdnsQueryNotifier(mdns_search_once_t * queryHandle);
static CHIP_ERROR AddQueryList(GenericContext * ctx)
{
MdnsQuery * ret = static_cast<MdnsQuery *>(chip::Platform::MemoryAlloc(sizeof(MdnsQuery)));
if (ret == nullptr)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for MdnsQuery");
return CHIP_ERROR_NO_MEMORY;
}
ret->ctx = ctx;
ret->next = sQueryList;
sQueryList = ret;
return CHIP_NO_ERROR;
}
static GenericContext * FindMdnsQuery(mdns_search_once_t * queryHandle)
{
MdnsQuery * current = sQueryList;
while (current)
{
if (current->ctx)
{
if (current->ctx->mContextType == ContextType::Browse)
{
BrowseContext * browseCtx = reinterpret_cast<BrowseContext *>(current->ctx);
if (browseCtx->mPtrQueryHandle == queryHandle)
{
return current->ctx;
}
}
else if (current->ctx->mContextType == ContextType::Resolve)
{
ResolveContext * resolveCtx = reinterpret_cast<ResolveContext *>(current->ctx);
if (resolveCtx->mSrvQueryHandle == queryHandle || resolveCtx->mTxtQueryHandle == queryHandle)
{
return current->ctx;
}
}
}
current = current->next;
}
return nullptr;
}
static CHIP_ERROR RemoveMdnsQuery(GenericContext * ctx)
{
MdnsQuery * current = sQueryList;
MdnsQuery * front = nullptr;
VerifyOrReturnError(ctx != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
while (current)
{
if (current->ctx == ctx)
{
break;
}
front = current;
current = current->next;
}
if (!current)
{
return CHIP_ERROR_NOT_FOUND;
}
if (front)
{
front->next = current->next;
}
else
{
sQueryList = current->next;
}
if (current->ctx->mContextType == ContextType::Browse)
{
chip::Platform::Delete(reinterpret_cast<BrowseContext *>(current->ctx));
}
else if (ctx->mContextType == ContextType::Resolve)
{
chip::Platform::Delete(reinterpret_cast<ResolveContext *>(current->ctx));
}
chip::Platform::MemoryFree(current);
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiDnssdInit(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context)
{
CHIP_ERROR error = CHIP_NO_ERROR;
esp_err_t espError;
espError = mdns_init();
VerifyOrExit(espError == ESP_OK, error = CHIP_ERROR_INTERNAL);
exit:
if (espError != ESP_OK)
{
ChipLogError(DeviceLayer, "esp mdns internal error: %s", esp_err_to_name(espError));
}
initCallback(context, error);
return error;
}
static const char * GetProtocolString(DnssdServiceProtocol protocol)
{
return protocol == DnssdServiceProtocol::kDnssdProtocolTcp ? "_tcp" : "_udp";
}
CHIP_ERROR WiFiDnssdPublishService(const DnssdService * service, DnssdPublishCallback callback, void * context)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mdns_txt_item_t * items = nullptr;
esp_err_t espError;
if (strcmp(service->mHostName, "") != 0)
{
VerifyOrExit(mdns_hostname_set(service->mHostName) == ESP_OK, error = CHIP_ERROR_INTERNAL);
}
VerifyOrExit(service->mTextEntrySize <= UINT8_MAX, error = CHIP_ERROR_INVALID_ARGUMENT);
if (service->mTextEntries)
{
items = static_cast<mdns_txt_item_t *>(chip::Platform::MemoryCalloc(service->mTextEntrySize, sizeof(mdns_txt_item_t)));
VerifyOrExit(items != nullptr, error = CHIP_ERROR_NO_MEMORY);
for (size_t i = 0; i < service->mTextEntrySize; i++)
{
items[i].key = service->mTextEntries[i].mKey;
// Unfortunately ESP mdns stack doesn't support arbitrary binary data
items[i].value = reinterpret_cast<const char *>(service->mTextEntries[i].mData);
}
}
espError = mdns_service_add(service->mName, service->mType, GetProtocolString(service->mProtocol), service->mPort, items,
service->mTextEntrySize);
// The mdns_service_add will return error if we try to add an existing service
if (espError != ESP_OK && espError != ESP_ERR_NO_MEM)
{
espError = mdns_service_txt_set(service->mType, GetProtocolString(service->mProtocol), items,
static_cast<uint8_t>(service->mTextEntrySize));
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
for (size_t i = 0; i < service->mSubTypeSize; i++)
{
mdns_service_subtype_add_for_host(service->mName, service->mType, GetProtocolString(service->mProtocol), service->mHostName,
service->mSubTypes[i]);
}
#endif
VerifyOrExit(espError == ESP_OK, error = CHIP_ERROR_INTERNAL);
exit:
if (items != nullptr)
{
chip::Platform::MemoryFree(items);
}
return error;
}
CHIP_ERROR WiFiDnssdRemoveServices()
{
mdns_service_remove("_matter", "_tcp");
mdns_service_remove("_matterc", "_udp");
return CHIP_NO_ERROR;
}
static Inet::IPAddressType MapAddressType(mdns_ip_protocol_t ip_protocol)
{
switch (ip_protocol)
{
#if INET_CONFIG_ENABLE_IPV4
case MDNS_IP_PROTOCOL_V4:
return Inet::IPAddressType::kIPv4;
#endif
case MDNS_IP_PROTOCOL_V6:
return Inet::IPAddressType::kIPv6;
default:
return Inet::IPAddressType::kAny;
}
}
static TextEntry * GetTextEntry(mdns_txt_item_t * txt_array, uint8_t * txt_value_len, size_t txt_count)
{
if (txt_count == 0 || txt_array == NULL)
{
return NULL;
}
TextEntry * ret = static_cast<TextEntry *>(chip::Platform::MemoryCalloc(txt_count, sizeof(TextEntry)));
if (ret)
{
for (size_t TextEntryIndex = 0; TextEntryIndex < txt_count; ++TextEntryIndex)
{
ret[TextEntryIndex].mKey = txt_array[TextEntryIndex].key;
ret[TextEntryIndex].mData = reinterpret_cast<const uint8_t *>(txt_array[TextEntryIndex].value);
ret[TextEntryIndex].mDataSize = txt_value_len[TextEntryIndex];
}
}
return ret;
}
static CHIP_ERROR GetIPAddress(Inet::IPAddress & outIPAddress, mdns_ip_addr_t * mdnsIPAddr)
{
if (!mdnsIPAddr)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (mdnsIPAddr->addr.type == ESP_IPADDR_TYPE_V6)
{
memcpy(outIPAddress.Addr, mdnsIPAddr->addr.u_addr.ip6.addr, sizeof(mdnsIPAddr->addr.u_addr.ip6.addr));
}
else if (mdnsIPAddr->addr.type == ESP_IPADDR_TYPE_V4)
{
outIPAddress.Addr[0] = 0;
outIPAddress.Addr[1] = 0;
outIPAddress.Addr[2] = htonl(0xFFFF);
outIPAddress.Addr[3] = mdnsIPAddr->addr.u_addr.ip4.addr;
}
else
{
outIPAddress = Inet::IPAddress::Any;
}
return CHIP_NO_ERROR;
}
size_t GetResultSize(mdns_result_t * result)
{
size_t ret = 0;
while (result)
{
ret++;
result = result->next;
}
return ret;
}
static CHIP_ERROR OnBrowseDone(BrowseContext * ctx)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mdns_result_t * currentResult = nullptr;
size_t servicesIndex = 0;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
mdns_result_t * delegatedResults = nullptr;
#endif
VerifyOrExit(ctx && ctx->mBrowseCb, error = CHIP_ERROR_INVALID_ARGUMENT);
ctx->mServiceSize = GetResultSize(ctx->mPtrQueryResult);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
mdns_lookup_delegated_service(NULL, ctx->mType, GetProtocolString(ctx->mProtocol), kMaxResults - ctx->mServiceSize,
&delegatedResults);
while (delegatedResults)
{
mdns_result_t * tmp = delegatedResults->next;
delegatedResults->next = ctx->mPtrQueryResult;
ctx->mPtrQueryResult = delegatedResults;
delegatedResults = tmp;
ctx->mServiceSize++;
}
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
if (ctx->mPtrQueryResult && ctx->mServiceSize > 0)
{
if (ctx->mServiceSize > 0)
{
ctx->mService = static_cast<DnssdService *>(chip::Platform::MemoryCalloc(ctx->mServiceSize, sizeof(DnssdService)));
if (!ctx->mService)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for Dnssd services");
ctx->mServiceSize = 0;
error = CHIP_ERROR_NO_MEMORY;
ExitNow();
}
currentResult = ctx->mPtrQueryResult;
servicesIndex = 0;
while (currentResult)
{
Platform::CopyString(ctx->mService[servicesIndex].mName, currentResult->instance_name);
Platform::CopyString(ctx->mService[servicesIndex].mHostName, currentResult->hostname);
Platform::CopyString(ctx->mService[servicesIndex].mType, currentResult->service_type);
ctx->mService[servicesIndex].mProtocol = ctx->mProtocol;
ctx->mService[servicesIndex].mAddressType = MapAddressType(currentResult->ip_protocol);
ctx->mService[servicesIndex].mTransportType = ctx->mAddressType;
ctx->mService[servicesIndex].mPort = currentResult->port;
ctx->mService[servicesIndex].mTextEntries =
GetTextEntry(currentResult->txt, currentResult->txt_value_len, currentResult->txt_count);
ctx->mService[servicesIndex].mTextEntrySize = currentResult->txt_count;
ctx->mService[servicesIndex].mSubTypes = NULL;
ctx->mService[servicesIndex].mSubTypeSize = 0;
if (ctx->mInterfaceId == chip::Inet::InterfaceId::Null())
{
// If the InterfaceId in the context is Null, we will use the Station netif by default.
ctx->mService[servicesIndex].mInterface =
Inet::InterfaceId(DeviceLayer::Internal::ESP32Utils::GetStationNetif());
}
else
{
ctx->mService[servicesIndex].mInterface = ctx->mInterfaceId;
}
if (currentResult->addr)
{
Inet::IPAddress IPAddr;
error = GetIPAddress(IPAddr, currentResult->addr);
SuccessOrExit(error);
ctx->mService[servicesIndex].mAddress.SetValue(IPAddr);
}
currentResult = currentResult->next;
servicesIndex++;
}
}
}
exit:
ctx->mBrowseCb(ctx->mCbContext, ctx->mService, ctx->mServiceSize, true, error);
return RemoveMdnsQuery(reinterpret_cast<GenericContext *>(ctx));
}
size_t GetAddressCount(mdns_ip_addr_t * addr)
{
size_t ret = 0;
while (addr)
{
ret++;
addr = addr->next;
}
return ret;
}
static CHIP_ERROR ParseIPAddresses(ResolveContext * ctx)
{
size_t addressIndex = 0;
if (ctx->mAddrQueryResult && ctx->mAddrQueryResult->addr)
{
ctx->mAddressCount = GetAddressCount(ctx->mAddrQueryResult->addr);
if (ctx->mAddressCount > 0)
{
ctx->mAddresses =
static_cast<Inet::IPAddress *>(chip::Platform::MemoryCalloc(ctx->mAddressCount, sizeof(Inet::IPAddress)));
if (ctx->mAddresses == nullptr)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for addresses");
ctx->mAddressCount = 0;
return CHIP_ERROR_NO_MEMORY;
}
auto * addr = ctx->mAddrQueryResult->addr;
while (addr)
{
GetIPAddress(ctx->mAddresses[addressIndex], addr);
addressIndex++;
addr = addr->next;
}
return CHIP_NO_ERROR;
}
}
return CHIP_ERROR_INVALID_ARGUMENT;
}
static CHIP_ERROR ParseSrvResult(ResolveContext * ctx)
{
if (ctx->mSrvQueryResult)
{
if (!ctx->mService)
{
ctx->mService = static_cast<DnssdService *>(chip::Platform::MemoryAlloc(sizeof(DnssdService)));
}
VerifyOrReturnError(ctx->mService, CHIP_ERROR_NO_MEMORY);
ctx->mServiceSize = 1;
Platform::CopyString(ctx->mService->mName, ctx->mSrvQueryResult->instance_name);
Platform::CopyString(ctx->mService->mHostName, ctx->mSrvQueryResult->hostname);
Platform::CopyString(ctx->mService->mType, ctx->mSrvQueryResult->service_type);
ctx->mService->mProtocol = ctx->mProtocol;
ctx->mService->mAddressType = MapAddressType(ctx->mSrvQueryResult->ip_protocol);
ctx->mService->mTransportType = ctx->mService->mAddressType;
ctx->mService->mPort = ctx->mSrvQueryResult->port;
ctx->mService->mSubTypes = nullptr;
ctx->mService->mSubTypeSize = 0;
if (ctx->mInterfaceId == chip::Inet::InterfaceId::Null())
{
// If the InterfaceId in the context is Null, we will use the Station netif by default.
ctx->mService->mInterface = Inet::InterfaceId(DeviceLayer::Internal::ESP32Utils::GetStationNetif());
}
else
{
ctx->mService->mInterface = ctx->mInterfaceId;
}
return CHIP_NO_ERROR;
}
else
{
ctx->mService = nullptr;
ctx->mServiceSize = 0;
}
return CHIP_ERROR_INVALID_ARGUMENT;
}
// ParseTxtResult should be called after ParseSrvResult
static CHIP_ERROR ParseTxtResult(ResolveContext * ctx)
{
VerifyOrReturnError(ctx->mService, CHIP_ERROR_INCORRECT_STATE);
if (ctx->mTxtQueryResult)
{
ctx->mService->mTextEntries =
GetTextEntry(ctx->mTxtQueryResult->txt, ctx->mTxtQueryResult->txt_value_len, ctx->mTxtQueryResult->txt_count);
ctx->mService->mTextEntrySize = ctx->mTxtQueryResult->txt_count;
}
else
{
ctx->mService->mTextEntries = nullptr;
ctx->mService->mTextEntrySize = 0;
}
return CHIP_NO_ERROR;
}
static CHIP_ERROR OnResolveDone(ResolveContext * ctx)
{
CHIP_ERROR error = CHIP_NO_ERROR;
VerifyOrExit(ctx && ctx->mResolveCb, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(!ctx->mService && ctx->mSrvAddrQueryFinished && ctx->mTxtQueryFinished, error = CHIP_ERROR_INCORRECT_STATE);
error = ParseSrvResult(ctx);
SuccessOrExit(error);
error = ParseIPAddresses(ctx);
SuccessOrExit(error);
error = ParseTxtResult(ctx);
SuccessOrExit(error);
exit:
if (error != CHIP_NO_ERROR)
{
ctx->mResolveCb(ctx->mCbContext, nullptr, Span<Inet::IPAddress>(nullptr, 0), error);
}
else
{
ctx->mResolveCb(ctx->mCbContext, ctx->mService, Span<Inet::IPAddress>(ctx->mAddresses, ctx->mAddressCount), error);
}
RemoveMdnsQuery(reinterpret_cast<GenericContext *>(ctx));
return error;
}
static mdns_result_t * MdnsQueryGetResults(mdns_search_once_t * queryHandle)
{
mdns_result_t * ret = nullptr;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
if (mdns_query_async_get_results(queryHandle, kTimeoutMilli, &ret, NULL))
#else
if (mdns_query_async_get_results(queryHandle, kTimeoutMilli, &ret))
#endif
{
return ret;
}
return nullptr;
}
static void MdnsQueryDone(intptr_t context)
{
if (!context)
{
return;
}
mdns_search_once_t * queryHandle = reinterpret_cast<mdns_search_once_t *>(context);
mdns_result_t * result = MdnsQueryGetResults(queryHandle);
GenericContext * ctx = FindMdnsQuery(queryHandle);
if (!ctx)
{
mdns_query_results_free(result);
mdns_query_async_delete(queryHandle);
return;
}
if (ctx->mContextType == ContextType::Browse)
{
BrowseContext * browseCtx = reinterpret_cast<BrowseContext *>(ctx);
browseCtx->mPtrQueryResult = result;
OnBrowseDone(browseCtx);
}
else if (ctx->mContextType == ContextType::Resolve)
{
ResolveContext * resolveCtx = reinterpret_cast<ResolveContext *>(ctx);
if (resolveCtx->mSrvQueryHandle == queryHandle)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// No result found, look up delegated services.
if (!result)
{
mdns_lookup_delegated_service(resolveCtx->mInstanceName, resolveCtx->mType,
GetProtocolString(resolveCtx->mProtocol), kMaxResults, &result);
}
#endif
if (!result)
{
resolveCtx->mResolveCb(ctx->mCbContext, nullptr, Span<Inet::IPAddress>(nullptr, 0), CHIP_ERROR_INVALID_ARGUMENT);
RemoveMdnsQuery(ctx);
return;
}
// If SRV Query Result is empty, the result is for SRV Query.
if (!resolveCtx->mSrvQueryResult)
{
resolveCtx->mSrvQueryResult = result;
if (result->addr)
{
resolveCtx->mAddrQueryResult = result;
resolveCtx->mSrvAddrQueryFinished = true;
}
else
{
// If there is no A/AAAA records in SRV query response, we will send an AAAA query for the IP addresses.
mdns_query_async_delete(resolveCtx->mSrvQueryHandle);
resolveCtx->mAddrQueryResult = nullptr;
resolveCtx->mSrvQueryHandle = mdns_query_async_new(result->hostname, NULL, NULL, MDNS_TYPE_AAAA, kTimeoutMilli,
kMaxResults, MdnsQueryNotifier);
if (!resolveCtx->mSrvQueryHandle)
{
resolveCtx->mResolveCb(ctx->mCbContext, nullptr, Span<Inet::IPAddress>(nullptr, 0), CHIP_ERROR_NO_MEMORY);
RemoveMdnsQuery(ctx);
return;
}
}
}
else if (!resolveCtx->mAddrQueryResult)
{
resolveCtx->mAddrQueryResult = result;
resolveCtx->mSrvAddrQueryFinished = true;
}
else
{
resolveCtx->mResolveCb(ctx->mCbContext, nullptr, Span<Inet::IPAddress>(nullptr, 0), CHIP_ERROR_INCORRECT_STATE);
RemoveMdnsQuery(ctx);
return;
}
}
else if (resolveCtx->mTxtQueryHandle == queryHandle)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// No result found, look up delegated services.
if (!result)
{
mdns_lookup_delegated_service(resolveCtx->mInstanceName, resolveCtx->mType,
GetProtocolString(resolveCtx->mProtocol), kMaxResults, &result);
}
#endif
resolveCtx->mTxtQueryResult = result;
resolveCtx->mTxtQueryFinished = true;
}
if (resolveCtx->mTxtQueryFinished && resolveCtx->mSrvAddrQueryFinished)
{
OnResolveDone(resolveCtx);
}
}
}
static void MdnsQueryNotifier(mdns_search_once_t * searchHandle)
{
chip::DeviceLayer::PlatformMgr().ScheduleWork(MdnsQueryDone, reinterpret_cast<intptr_t>(searchHandle));
}
CHIP_ERROR WiFiDnssdBrowse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType,
chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context,
intptr_t * browseIdentifier)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mdns_search_once_t * queryHandle =
mdns_query_async_new(NULL, type, GetProtocolString(protocol), MDNS_TYPE_PTR, kTimeoutMilli, kMaxResults, MdnsQueryNotifier);
VerifyOrReturnError(queryHandle, CHIP_ERROR_NO_MEMORY);
BrowseContext * ctx =
chip::Platform::New<BrowseContext>(type, protocol, interface, queryHandle, addressType, callback, context);
if (!ctx)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for browse context");
mdns_query_async_delete(queryHandle);
return CHIP_ERROR_NO_MEMORY;
}
error = AddQueryList(reinterpret_cast<GenericContext *>(ctx));
if (error != CHIP_NO_ERROR)
{
chip::Platform::Delete(ctx);
}
else
{
*browseIdentifier = reinterpret_cast<intptr_t>(nullptr);
}
return error;
}
CHIP_ERROR WiFiDnssdResolve(DnssdService * service, chip::Inet::InterfaceId interface, DnssdResolveCallback callback,
void * context)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mdns_search_once_t * querySrv = mdns_query_async_new(service->mName, service->mType, GetProtocolString(service->mProtocol),
MDNS_TYPE_SRV, kTimeoutMilli, kMaxResults, MdnsQueryNotifier);
VerifyOrReturnError(querySrv, CHIP_ERROR_NO_MEMORY);
mdns_search_once_t * queryTxt = mdns_query_async_new(service->mName, service->mType, GetProtocolString(service->mProtocol),
MDNS_TYPE_TXT, kTimeoutMilli, kMaxResults, MdnsQueryNotifier);
if (!queryTxt)
{
mdns_query_async_delete(querySrv);
return CHIP_ERROR_NO_MEMORY;
}
ResolveContext * ctx = chip::Platform::New<ResolveContext>(service, interface, querySrv, queryTxt, callback, context);
if (!ctx)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for resolve context");
mdns_query_async_delete(querySrv);
mdns_query_async_delete(queryTxt);
return CHIP_ERROR_NO_MEMORY;
}
error = AddQueryList(reinterpret_cast<GenericContext *>(ctx));
if (error != CHIP_NO_ERROR)
{
chip::Platform::Delete(ctx);
}
return error;
}
} // namespace Dnssd
} // namespace chip