Files
esp-protocols/components/mdns/mdns_browser.c
David Cermak bed116d98b feat(mdns): Refactor mdns library (stage #1)
The MDNS component has been refactored from a single monolithic file mdns.c
into a set of focused modules with clear responsibilities.
This restructuring maintains the same functionality while improving code organization,
maintainability, and testability.
In the stage#2 we will focus on module based tests
In the stage#3 we will focus on small scale refators and optimizations
2025-11-25 17:30:27 +01:00

657 lines
21 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "sdkconfig.h"
#include "mdns_private.h"
#include "mdns_browser.h"
#include "mdns_mem_caps.h"
#include "mdns_debug.h"
#include "mdns_utils.h"
#include "mdns_querier.h"
#include "mdns_responder.h"
#include "mdns_netif.h"
#include "mdns_service.h"
#include "esp_log.h"
static const char *TAG = "mdns_browser";
static mdns_browse_t *s_browse;
/**
* @brief Browse action
*/
static esp_err_t send_browse_action(mdns_action_type_t type, mdns_browse_t *browse)
{
mdns_action_t *action = NULL;
action = (mdns_action_t *)mdns_mem_malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = type;
action->data.browse_add.browse = browse;
if (!mdns_priv_queue_action(action)) {
mdns_mem_free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
/**
* @brief Free a browse item (Not free the list).
*/
static void browse_item_free(mdns_browse_t *browse)
{
mdns_mem_free(browse->service);
mdns_mem_free(browse->proto);
if (browse->result) {
mdns_priv_query_results_free(browse->result);
}
mdns_mem_free(browse);
}
static void browse_sync(mdns_browse_sync_t *browse_sync)
{
mdns_browse_t *browse = browse_sync->browse;
mdns_browse_result_sync_t *sync_result = browse_sync->sync_result;
while (sync_result) {
mdns_result_t *result = sync_result->result;
DBG_BROWSE_RESULTS(result, browse_sync->browse);
browse->notifier(result);
if (result->ttl == 0) {
queueDetach(mdns_result_t, browse->result, result);
// Just free current result
result->next = NULL;
mdns_query_results_free(result);
}
sync_result = sync_result->next;
}
}
/**
* @brief Send PTR query packet to all available interfaces for browsing.
*/
static void browse_send(mdns_browse_t *browse, mdns_if_t interface)
{
// Using search once for sending the PTR query
mdns_search_once_t search = {0};
search.instance = NULL;
search.service = browse->service;
search.proto = browse->proto;
search.type = MDNS_TYPE_PTR;
search.unicast = false;
search.result = NULL;
search.next = NULL;
for (uint8_t protocol_idx = 0; protocol_idx < MDNS_IP_PROTOCOL_MAX; protocol_idx++) {
mdns_priv_query_send(&search, interface, (mdns_ip_protocol_t) protocol_idx);
}
}
void mdns_priv_browse_send_all(mdns_if_t mdns_if)
{
mdns_browse_t *browse = s_browse;
while (browse) {
browse_send(browse, mdns_if);
browse = browse->next;
}
}
void mdns_priv_browse_free(void)
{
while (s_browse) {
mdns_browse_t *b = s_browse;
s_browse = s_browse->next;
browse_item_free(b);
}
}
/**
* @brief Mark browse as finished, remove and free it from browse chain
*/
static void browse_finish(mdns_browse_t *browse)
{
browse->state = BROWSE_OFF;
mdns_browse_t *b = s_browse;
mdns_browse_t *target_free = NULL;
while (b) {
if (strlen(b->service) == strlen(browse->service) && memcmp(b->service, browse->service, strlen(b->service)) == 0 &&
strlen(b->proto) == strlen(browse->proto) && memcmp(b->proto, browse->proto, strlen(b->proto)) == 0) {
target_free = b;
b = b->next;
queueDetach(mdns_browse_t, s_browse, target_free);
browse_item_free(target_free);
} else {
b = b->next;
}
}
browse_item_free(browse);
}
/**
* @brief Allocate new browse structure
*/
static mdns_browse_t *browse_init(const char *service, const char *proto, mdns_browse_notify_t notifier)
{
mdns_browse_t *browse = (mdns_browse_t *)mdns_mem_malloc(sizeof(mdns_browse_t));
if (!browse) {
HOOK_MALLOC_FAILED;
return NULL;
}
memset(browse, 0, sizeof(mdns_browse_t));
browse->state = BROWSE_INIT;
if (!mdns_utils_str_null_or_empty(service)) {
browse->service = mdns_mem_strndup(service, MDNS_NAME_BUF_LEN - 1);
if (!browse->service) {
browse_item_free(browse);
HOOK_MALLOC_FAILED;
return NULL;
}
}
if (!mdns_utils_str_null_or_empty(proto)) {
browse->proto = mdns_mem_strndup(proto, MDNS_NAME_BUF_LEN - 1);
if (!browse->proto) {
browse_item_free(browse);
return NULL;
}
}
browse->notifier = notifier;
return browse;
}
/**
* @brief Add new browse to the browse chain
*/
static void browse_add(mdns_browse_t *browse)
{
browse->state = BROWSE_RUNNING;
mdns_browse_t *queue = s_browse;
bool found = false;
// looking for this browse in active browses
while (queue) {
if (strlen(queue->service) == strlen(browse->service) && memcmp(queue->service, browse->service, strlen(queue->service)) == 0 &&
strlen(queue->proto) == strlen(browse->proto) && memcmp(queue->proto, browse->proto, strlen(queue->proto)) == 0) {
found = true;
break;
}
queue = queue->next;
}
if (!found) {
browse->next = s_browse;
s_browse = browse;
}
for (uint8_t interface_idx = 0; interface_idx < MDNS_MAX_INTERFACES; interface_idx++) {
browse_send(browse, (mdns_if_t) interface_idx);
}
if (found) {
browse_item_free(browse);
}
}
/**
* @brief Called from packet parser to find matching running search
*/
mdns_browse_t *mdns_priv_browse_find(mdns_name_t *name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_browse_t *b = s_browse;
// For browse, we only care about the SRV, TXT, A and AAAA
if (type != MDNS_TYPE_SRV && type != MDNS_TYPE_A && type != MDNS_TYPE_AAAA && type != MDNS_TYPE_TXT) {
return NULL;
}
mdns_result_t *r = NULL;
while (b) {
if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) {
if (strcasecmp(name->service, b->service)
|| strcasecmp(name->proto, b->proto)) {
b = b->next;
continue;
}
return b;
} else if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) {
r = b->result;
while (r) {
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol && !mdns_utils_str_null_or_empty(r->hostname) && !strcasecmp(name->host, r->hostname)) {
return b;
}
r = r->next;
}
b = b->next;
continue;
}
}
return b;
}
static void sync_browse_result_link_free(mdns_browse_sync_t *browse_sync)
{
mdns_browse_result_sync_t *current = browse_sync->sync_result;
mdns_browse_result_sync_t *need_free;
while (current) {
need_free = current;
current = current->next;
mdns_mem_free(need_free);
}
mdns_mem_free(browse_sync);
}
void mdns_priv_browse_action(mdns_action_t *action, mdns_action_subtype_t type)
{
if (type == ACTION_RUN) {
switch (action->type) {
case ACTION_BROWSE_ADD:
browse_add(action->data.browse_add.browse);
break;
case ACTION_BROWSE_SYNC:
browse_sync(action->data.browse_sync.browse_sync);
sync_browse_result_link_free(action->data.browse_sync.browse_sync);
break;
case ACTION_BROWSE_END:
browse_finish(action->data.browse_add.browse);
break;
default:
abort();
}
return;
}
if (type == ACTION_CLEANUP) {
switch (action->type) {
case ACTION_BROWSE_ADD:
//fallthrough
case ACTION_BROWSE_END:
browse_item_free(action->data.browse_add.browse);
break;
case ACTION_BROWSE_SYNC:
sync_browse_result_link_free(action->data.browse_sync.browse_sync);
break;
default:
abort();
}
return;
}
}
/**
* @brief Add result to browse, only add when the result is a new one.
*/
static esp_err_t add_browse_result(mdns_browse_sync_t *sync_browse, mdns_result_t *r)
{
mdns_browse_result_sync_t *sync_r = sync_browse->sync_result;
while (sync_r) {
if (sync_r->result == r) {
break;
}
sync_r = sync_r->next;
}
if (!sync_r) {
// Do not find, need to add the result to the list
mdns_browse_result_sync_t *new = (mdns_browse_result_sync_t *)mdns_mem_malloc(sizeof(mdns_browse_result_sync_t));
if (!new) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
new->result = r;
new->next = sync_browse->sync_result;
sync_browse->sync_result = new;
}
return ESP_OK;
}
/**
* @brief Called from parser to add A/AAAA data to search result
*/
void mdns_priv_browse_result_add_ip(mdns_browse_t *browse, const char *hostname, esp_ip_addr_t *ip,
mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl, mdns_browse_sync_t *out_sync_browse)
{
if (out_sync_browse->browse == NULL) {
return;
} else {
if (out_sync_browse->browse != browse) {
return;
}
}
mdns_result_t *r = NULL;
mdns_ip_addr_t *r_a = NULL;
if (browse) {
r = browse->result;
while (r) {
if (r->ip_protocol == ip_protocol) {
// Find the target result in browse result.
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && !mdns_utils_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) {
r_a = r->addr;
// Check if the address has already added in result.
while (r_a) {
#ifdef CONFIG_LWIP_IPV4
if (r_a->addr.type == ip->type && r_a->addr.type == ESP_IPADDR_TYPE_V4 && r_a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) {
break;
}
#endif
#ifdef CONFIG_LWIP_IPV6
if (r_a->addr.type == ip->type && r_a->addr.type == ESP_IPADDR_TYPE_V6 && !memcmp(r_a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16)) {
break;
}
#endif
r_a = r_a->next;
}
if (!r_a) {
// The current IP is a new one, add it to the link list.
mdns_ip_addr_t *a = NULL;
a = mdns_priv_result_addr_create_ip(ip);
if (!a) {
return;
}
a->next = r->addr;
r->addr = a;
if (r->ttl != ttl) {
if (r->ttl == 0) {
r->ttl = ttl;
} else {
mdns_priv_query_update_result_ttl(r, ttl);
}
}
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
return;
}
break;
}
}
}
r = r->next;
}
}
}
static bool is_txt_item_in_list(mdns_txt_item_t txt, uint8_t txt_value_len, mdns_txt_item_t *txt_list, uint8_t *txt_value_len_list, size_t txt_count)
{
for (size_t i = 0; i < txt_count; i++) {
if (strcmp(txt.key, txt_list[i].key) == 0) {
if (txt_value_len == txt_value_len_list[i] && memcmp(txt.value, txt_list[i].value, txt_value_len) == 0) {
return true;
} else {
// The key value is unique, so there is no need to continue searching.
return false;
}
}
}
return false;
}
/**
* @brief Called from parser to add TXT data to search result
*/
void mdns_priv_browse_result_add_txt(mdns_browse_t *browse, const char *instance, const char *service, const char *proto,
mdns_txt_item_t *txt, uint8_t *txt_value_len, size_t txt_count, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol,
uint32_t ttl, mdns_browse_sync_t *out_sync_browse)
{
if (out_sync_browse->browse == NULL) {
return;
} else {
if (out_sync_browse->browse != browse) {
return;
}
}
mdns_result_t *r = browse->result;
while (r) {
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol &&
!mdns_utils_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name) &&
!mdns_utils_str_null_or_empty(r->service_type) && !strcasecmp(service, r->service_type) &&
!mdns_utils_str_null_or_empty(r->proto) && !strcasecmp(proto, r->proto)) {
bool should_update = false;
if (r->txt) {
// Check if txt changed
if (txt_count != r->txt_count) {
should_update = true;
} else {
for (size_t txt_index = 0; txt_index < txt_count; txt_index++) {
if (!is_txt_item_in_list(txt[txt_index], txt_value_len[txt_index], r->txt, r->txt_value_len, r->txt_count)) {
should_update = true;
break;
}
}
}
// If the result has a previous txt entry, we delete it and re-add.
for (size_t i = 0; i < r->txt_count; i++) {
mdns_mem_free((char *)(r->txt[i].key));
mdns_mem_free((char *)(r->txt[i].value));
}
mdns_mem_free(r->txt);
mdns_mem_free(r->txt_value_len);
}
r->txt = txt;
r->txt_value_len = txt_value_len;
r->txt_count = txt_count;
if (r->ttl != ttl) {
uint32_t previous_ttl = r->ttl;
if (r->ttl == 0) {
r->ttl = ttl;
} else {
mdns_priv_query_update_result_ttl(r, ttl);
}
if (previous_ttl != r->ttl) {
should_update = true;
}
}
if (should_update) {
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
return;
}
}
return;
}
r = r->next;
}
r = (mdns_result_t *)mdns_mem_malloc(sizeof(mdns_result_t));
if (!r) {
HOOK_MALLOC_FAILED;
goto free_txt;
}
memset(r, 0, sizeof(mdns_result_t));
r->instance_name = mdns_mem_strdup(instance);
r->service_type = mdns_mem_strdup(service);
r->proto = mdns_mem_strdup(proto);
if (!r->instance_name || !r->service_type || !r->proto) {
mdns_mem_free(r->instance_name);
mdns_mem_free(r->service_type);
mdns_mem_free(r->proto);
mdns_mem_free(r);
return;
}
r->txt = txt;
r->txt_value_len = txt_value_len;
r->txt_count = txt_count;
r->esp_netif = mdns_priv_get_esp_netif(tcpip_if);
r->ip_protocol = ip_protocol;
r->ttl = ttl;
r->next = browse->result;
browse->result = r;
add_browse_result(out_sync_browse, r);
return;
free_txt:
for (size_t i = 0; i < txt_count; i++) {
mdns_mem_free((char *)(txt[i].key));
mdns_mem_free((char *)(txt[i].value));
}
mdns_mem_free(txt);
mdns_mem_free(txt_value_len);
return;
}
static esp_err_t copy_address_in_previous_result(mdns_result_t *result_list, mdns_result_t *r)
{
while (result_list) {
if (!mdns_utils_str_null_or_empty(result_list->hostname) && !mdns_utils_str_null_or_empty(r->hostname) && !strcasecmp(result_list->hostname, r->hostname) &&
result_list->ip_protocol == r->ip_protocol && result_list->addr && !r->addr) {
// If there is a same hostname in previous result, we need to copy the address here.
r->addr = mdns_utils_copy_address_list(result_list->addr);
if (!r->addr) {
return ESP_ERR_NO_MEM;
}
break;
} else {
result_list = result_list->next;
}
}
return ESP_OK;
}
/**
* @brief Called from parser to add SRV data to search result
*/
void mdns_priv_browse_result_add_srv(mdns_browse_t *browse, const char *hostname, const char *instance, const char *service, const char *proto,
uint16_t port, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl, mdns_browse_sync_t *out_sync_browse)
{
if (out_sync_browse->browse == NULL) {
return;
} else {
if (out_sync_browse->browse != browse) {
return;
}
}
mdns_result_t *r = browse->result;
while (r) {
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol &&
!mdns_utils_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name) &&
!mdns_utils_str_null_or_empty(r->service_type) && !strcasecmp(service, r->service_type) &&
!mdns_utils_str_null_or_empty(r->proto) && !strcasecmp(proto, r->proto)) {
if (mdns_utils_str_null_or_empty(r->hostname) || strcasecmp(hostname, r->hostname)) {
r->hostname = mdns_mem_strdup(hostname);
r->port = port;
if (!r->hostname) {
HOOK_MALLOC_FAILED;
return;
}
if (!r->addr) {
esp_err_t err = copy_address_in_previous_result(browse->result, r);
if (err == ESP_ERR_NO_MEM) {
return;
}
}
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
return;
}
}
if (r->ttl != ttl) {
uint32_t previous_ttl = r->ttl;
if (r->ttl == 0) {
r->ttl = ttl;
} else {
mdns_priv_query_update_result_ttl(r, ttl);
}
if (previous_ttl != r->ttl) {
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
return;
}
}
}
return;
}
r = r->next;
}
r = (mdns_result_t *)mdns_mem_malloc(sizeof(mdns_result_t));
if (!r) {
HOOK_MALLOC_FAILED;
return;
}
memset(r, 0, sizeof(mdns_result_t));
r->hostname = mdns_mem_strdup(hostname);
r->instance_name = mdns_mem_strdup(instance);
r->service_type = mdns_mem_strdup(service);
r->proto = mdns_mem_strdup(proto);
if (!r->hostname || !r->instance_name || !r->service_type || !r->proto) {
HOOK_MALLOC_FAILED;
mdns_mem_free(r->hostname);
mdns_mem_free(r->instance_name);
mdns_mem_free(r->service_type);
mdns_mem_free(r->proto);
mdns_mem_free(r);
return;
}
r->port = port;
r->esp_netif = mdns_priv_get_esp_netif(tcpip_if);
r->ip_protocol = ip_protocol;
r->ttl = ttl;
r->next = browse->result;
browse->result = r;
add_browse_result(out_sync_browse, r);
}
/**
* @brief Browse sync result
*/
esp_err_t mdns_priv_browse_sync(mdns_browse_sync_t *browse_sync)
{
mdns_action_t *action = NULL;
action = (mdns_action_t *)mdns_mem_malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_BROWSE_SYNC;
action->data.browse_sync.browse_sync = browse_sync;
if (!mdns_priv_queue_action(action)) {
mdns_mem_free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
/**
* @defgroup MDNS_PUBCLIC_API
*/
mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_browse_notify_t notifier)
{
mdns_browse_t *browse = NULL;
if (mdns_priv_is_server_init() || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
return NULL;
}
browse = browse_init(service, proto, notifier);
if (!browse) {
return NULL;
}
if (send_browse_action(ACTION_BROWSE_ADD, browse)) {
browse_item_free(browse);
return NULL;
}
return browse;
}
esp_err_t mdns_browse_delete(const char *service, const char *proto)
{
mdns_browse_t *browse = NULL;
if (!mdns_priv_is_server_init() || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
return ESP_FAIL;
}
browse = browse_init(service, proto, NULL);
if (!browse) {
return ESP_ERR_NO_MEM;
}
if (send_browse_action(ACTION_BROWSE_END, browse)) {
browse_item_free(browse);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}