blob: cef2b07aaf818df9401debb1d0c240b6b36bdd55 [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 "DnssdImpl.h"
#include "lib/dnssd/platform/Dnssd.h"
#include <esp_err.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>
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;
void MdnsQueryNotifier(mdns_search_once_t * searchHandle);
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;
}
GenericContext * FindMdnsQuery(mdns_search_once_t * searchHandle)
{
MdnsQuery * current = sQueryList;
while (current)
{
if (current->ctx && current->ctx->mSearchHandle == searchHandle)
{
return current->ctx;
}
current = current->next;
}
return nullptr;
}
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 ChipDnssdInit(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;
}
void ChipDnssdShutdown() {}
static const char * GetProtocolString(DnssdServiceProtocol protocol)
{
return protocol == DnssdServiceProtocol::kDnssdProtocolTcp ? "_tcp" : "_udp";
}
CHIP_ERROR ChipDnssdPublishService(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));
}
VerifyOrExit(espError == ESP_OK, error = CHIP_ERROR_INTERNAL);
exit:
if (items != nullptr)
{
chip::Platform::MemoryFree(items);
}
return error;
}
CHIP_ERROR ChipDnssdRemoveServices()
{
mdns_service_remove("_matter", "_tcp");
mdns_service_remove("_matterc", "_udp");
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipDnssdFinalizeServiceUpdate()
{
return CHIP_NO_ERROR;
}
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;
}
}
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;
}
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;
}
CHIP_ERROR OnBrowseDone(BrowseContext * ctx)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mdns_result_t * currentResult = nullptr;
size_t servicesIndex = 0;
VerifyOrExit(ctx && ctx->mBrowseCb, error = CHIP_ERROR_INVALID_ARGUMENT);
if (ctx->mResult)
{
ctx->mServiceSize = GetResultSize(ctx->mResult);
if (ctx->mServiceSize > 0)
{
ctx->mServices = static_cast<DnssdService *>(chip::Platform::MemoryCalloc(ctx->mServiceSize, sizeof(DnssdService)));
if (!ctx->mServices)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for Dnssd services");
ctx->mServiceSize = 0;
error = CHIP_ERROR_NO_MEMORY;
ExitNow();
}
currentResult = ctx->mResult;
servicesIndex = 0;
while (currentResult)
{
strncpy(ctx->mServices[servicesIndex].mName, currentResult->instance_name,
strnlen(currentResult->instance_name, Common::kInstanceNameMaxLength));
strncpy(ctx->mServices[servicesIndex].mHostName, currentResult->hostname,
strnlen(currentResult->hostname, kHostNameMaxLength));
strncpy(ctx->mServices[servicesIndex].mType, currentResult->service_type,
strnlen(currentResult->service_type, kDnssdTypeMaxSize));
ctx->mServices[servicesIndex].mProtocol = ctx->mProtocol;
ctx->mServices[servicesIndex].mAddressType = MapAddressType(currentResult->ip_protocol);
ctx->mServices[servicesIndex].mTransportType = ctx->mAddressType;
ctx->mServices[servicesIndex].mPort = currentResult->port;
ctx->mServices[servicesIndex].mInterface = ctx->mInterfaceId;
ctx->mServices[servicesIndex].mTextEntries =
GetTextEntry(currentResult->txt, currentResult->txt_value_len, currentResult->txt_count);
ctx->mServices[servicesIndex].mTextEntrySize = currentResult->txt_count;
ctx->mServices[servicesIndex].mSubTypes = NULL;
ctx->mServices[servicesIndex].mSubTypeSize = 0;
if (currentResult->addr)
{
Inet::IPAddress IPAddr;
error = GetIPAddress(IPAddr, currentResult->addr);
SuccessOrExit(error);
ctx->mServices[servicesIndex].mAddress.SetValue(IPAddr);
}
currentResult = currentResult->next;
servicesIndex++;
}
}
}
exit:
ctx->mBrowseCb(ctx->mCbContext, ctx->mServices, ctx->mServiceSize, 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;
}
CHIP_ERROR OnResolveQuerySrvDone(ResolveContext * ctx)
{
CHIP_ERROR error = CHIP_NO_ERROR;
size_t addressIndex = 0;
VerifyOrExit(ctx && ctx->mResolveCb, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(ctx->mService == nullptr && ctx->mResolveState == ResolveContext::ResolveState::QuerySrv,
error = CHIP_ERROR_INCORRECT_STATE);
if (ctx->mResult)
{
ctx->mService = static_cast<DnssdService *>(chip::Platform::MemoryAlloc(sizeof(DnssdService)));
VerifyOrExit(ctx->mService != nullptr, error = CHIP_ERROR_NO_MEMORY);
strncpy(ctx->mService->mName, ctx->mResult->instance_name,
strnlen(ctx->mResult->instance_name, Common::kInstanceNameMaxLength));
strncpy(ctx->mService->mHostName, ctx->mResult->hostname, strnlen(ctx->mResult->hostname, kHostNameMaxLength));
strncpy(ctx->mService->mType, ctx->mResult->service_type, strnlen(ctx->mResult->service_type, kDnssdTypeMaxSize));
ctx->mService->mProtocol = ctx->mProtocol;
ctx->mService->mAddressType = MapAddressType(ctx->mResult->ip_protocol);
ctx->mService->mTransportType = ctx->mService->mAddressType;
ctx->mService->mPort = ctx->mResult->port;
ctx->mService->mInterface = ctx->mInterfaceId;
ctx->mService->mSubTypes = nullptr;
ctx->mService->mSubTypeSize = 0;
if (ctx->mResult->addr)
{
ctx->mAddressCount = GetAddressCount(ctx->mResult->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");
error = CHIP_ERROR_NO_MEMORY;
ctx->mAddressCount = 0;
ExitNow();
}
auto * addr = ctx->mResult->addr;
while (addr)
{
GetIPAddress(ctx->mAddresses[addressIndex], addr);
addressIndex++;
addr = addr->next;
}
}
else
{
ctx->mAddresses = nullptr;
ctx->mAddressCount = 0;
}
}
}
exit:
if (error != CHIP_NO_ERROR)
{
ctx->mResolveCb(ctx->mCbContext, nullptr, Span<Inet::IPAddress>(nullptr, 0), error);
RemoveMdnsQuery(reinterpret_cast<GenericContext *>(ctx));
return error;
}
mdns_query_results_free(ctx->mResult);
mdns_query_async_delete(ctx->mSearchHandle);
ctx->mResult = nullptr;
ctx->mResolveState = ResolveContext::ResolveState::QueryTxt;
// then query the text entries
ctx->mSearchHandle = mdns_query_async_new(ctx->mInstanceName, ctx->mType, GetProtocolString(ctx->mProtocol), MDNS_TYPE_TXT,
kTimeoutMilli, kMaxResults, MdnsQueryNotifier);
return CHIP_NO_ERROR;
}
CHIP_ERROR OnResolveQueryTxtDone(ResolveContext * ctx)
{
CHIP_ERROR error = CHIP_NO_ERROR;
VerifyOrExit(ctx && ctx->mResolveCb, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(ctx->mService && ctx->mResolveState == ResolveContext::ResolveState::QueryTxt, error = CHIP_ERROR_INCORRECT_STATE);
if (ctx->mResult)
{
ctx->mService->mTextEntries = GetTextEntry(ctx->mResult->txt, ctx->mResult->txt_value_len, ctx->mResult->txt_count);
ctx->mService->mTextEntrySize = ctx->mResult->txt_count;
}
else
{
ctx->mService->mTextEntries = nullptr;
ctx->mService->mTextEntrySize = 0;
}
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;
}
void MdnsQueryDone(intptr_t context)
{
if (!context)
{
return;
}
mdns_search_once_t * searchHandle = reinterpret_cast<mdns_search_once_t *>(context);
GenericContext * ctx = FindMdnsQuery(searchHandle);
if (mdns_query_async_get_results(searchHandle, kTimeoutMilli, &(ctx->mResult)))
{
if (ctx->mContextType == ContextType::Browse)
{
OnBrowseDone(reinterpret_cast<BrowseContext *>(ctx));
}
else if (ctx->mContextType == ContextType::Resolve)
{
ResolveContext * resolveCtx = reinterpret_cast<ResolveContext *>(ctx);
if (resolveCtx->mResolveState == ResolveContext::ResolveState::QuerySrv)
{
OnResolveQuerySrvDone(resolveCtx);
}
else if (resolveCtx->mResolveState == ResolveContext::ResolveState::QueryTxt)
{
OnResolveQueryTxtDone(resolveCtx);
}
}
}
}
void MdnsQueryNotifier(mdns_search_once_t * searchHandle)
{
chip::DeviceLayer::PlatformMgr().ScheduleWork(MdnsQueryDone, reinterpret_cast<intptr_t>(searchHandle));
}
CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType,
chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mdns_search_once_t * searchHandle =
mdns_query_async_new(NULL, type, GetProtocolString(protocol), MDNS_TYPE_PTR, kTimeoutMilli, kMaxResults, MdnsQueryNotifier);
BrowseContext * ctx =
chip::Platform::New<BrowseContext>(type, protocol, interface, searchHandle, addressType, callback, context);
if (!ctx)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for browse context");
mdns_query_async_delete(searchHandle);
return CHIP_ERROR_NO_MEMORY;
}
error = AddQueryList(reinterpret_cast<GenericContext *>(ctx));
if (error != CHIP_NO_ERROR)
{
chip::Platform::Delete(ctx);
}
return error;
}
CHIP_ERROR ChipDnssdResolve(DnssdService * service, chip::Inet::InterfaceId interface, DnssdResolveCallback callback,
void * context)
{
CHIP_ERROR error = CHIP_NO_ERROR;
mdns_search_once_t * searchHandle = mdns_query_async_new(service->mName, service->mType, GetProtocolString(service->mProtocol),
MDNS_TYPE_SRV, kTimeoutMilli, kMaxResults, MdnsQueryNotifier);
ResolveContext * ctx = chip::Platform::New<ResolveContext>(service, interface, searchHandle, callback, context);
if (!ctx)
{
ChipLogError(DeviceLayer, "Failed to alloc memory for resolve context");
mdns_query_async_delete(searchHandle);
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