mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-06-25 09:21:32 +02:00
feat(mdns): add a new mdns query mode browse
This commit is contained in:
@ -26,6 +26,11 @@ extern "C" {
|
||||
*/
|
||||
typedef struct mdns_search_once_s mdns_search_once_t;
|
||||
|
||||
/**
|
||||
* @brief Daemon query handle
|
||||
*/
|
||||
typedef struct mdns_browse_s mdns_browse_t;
|
||||
|
||||
typedef enum {
|
||||
MDNS_EVENT_ENABLE_IP4 = 1 << 1,
|
||||
MDNS_EVENT_ENABLE_IP6 = 1 << 2,
|
||||
@ -97,6 +102,7 @@ typedef struct mdns_result_s {
|
||||
} mdns_result_t;
|
||||
|
||||
typedef void (*mdns_query_notify_t)(mdns_search_once_t *search);
|
||||
typedef void (*mdns_browse_notify_t)(mdns_result_t *result);
|
||||
|
||||
/**
|
||||
* @brief Initialize mDNS on given interface
|
||||
@ -830,6 +836,28 @@ esp_err_t mdns_unregister_netif(esp_netif_t *esp_netif);
|
||||
*/
|
||||
esp_err_t mdns_netif_action(esp_netif_t *esp_netif, mdns_event_actions_t event_action);
|
||||
|
||||
/**
|
||||
* @brief Browse mDNS for a service `_service._proto`.
|
||||
*
|
||||
* @param service Pointer to the `_service` which will be browsed.
|
||||
* @param proto Pointer to the `_proto` which will be browsed.
|
||||
* @param notifier The callback which will be called when the browsing service changed.
|
||||
* @return mdns_browse_t pointer to new browse object if initiated successfully.
|
||||
* NULL otherwise.
|
||||
*/
|
||||
mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_browse_notify_t notifier);
|
||||
|
||||
/**
|
||||
* @brief Stop the `_service._proto` browse.
|
||||
* @param service Pointer to the `_service` which will be browsed.
|
||||
* @param proto Pointer to the `_proto` which will be browsed.
|
||||
* @return
|
||||
* - ESP_OK success.
|
||||
* - ESP_ERR_FAIL mDNS is not running or the browsing of `_service._proto` is never started.
|
||||
* - ESP_ERR_NO_MEM memory error.
|
||||
*/
|
||||
esp_err_t mdns_browse_delete(const char *service, const char *proto);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -18,6 +18,14 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
|
||||
static void _mdns_browse_item_free(mdns_browse_t *browse);
|
||||
static esp_err_t _mdns_send_browse_action(mdns_action_type_t type, mdns_browse_t *browse);
|
||||
static esp_err_t _mdns_sync_browse_action(mdns_action_type_t type, mdns_browse_sync_t *browse_sync);
|
||||
static void _mdns_browse_sync(mdns_browse_sync_t *browse_sync);
|
||||
static void _mdns_browse_finish(mdns_browse_t *browse);
|
||||
static void _mdns_browse_add(mdns_browse_t *browse);
|
||||
static void _mdns_browse_send(mdns_browse_t *browse);
|
||||
|
||||
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
#include "esp_eth.h"
|
||||
#endif
|
||||
@ -57,6 +65,18 @@ static SemaphoreHandle_t _mdns_service_semaphore = NULL;
|
||||
|
||||
static void _mdns_search_finish_done(void);
|
||||
static mdns_search_once_t *_mdns_search_find_from(mdns_search_once_t *search, mdns_name_t *name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
|
||||
static mdns_browse_t *_mdns_browse_find_from(mdns_browse_t *b, mdns_name_t *name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
|
||||
static void _mdns_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);
|
||||
static void _mdns_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);
|
||||
static void _mdns_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);
|
||||
#ifdef MDNS_ENABLE_DEBUG
|
||||
static void debug_printf_browse_result(mdns_result_t *r_t, mdns_browse_t *b_t);
|
||||
static void debug_printf_browse_result_all(mdns_result_t *r_t);
|
||||
#endif // MDNS_ENABLE_DEBUG
|
||||
static void _mdns_search_result_add_ip(mdns_search_once_t *search, const char *hostname, esp_ip_addr_t *ip,
|
||||
mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl);
|
||||
static void _mdns_search_result_add_srv(mdns_search_once_t *search, const char *hostname, uint16_t port,
|
||||
@ -1494,11 +1514,16 @@ static void _mdns_dispatch_tx_packet(mdns_tx_packet_t *p)
|
||||
|
||||
#ifdef MDNS_ENABLE_DEBUG
|
||||
_mdns_dbg_printf("\nTX[%lu][%lu]: ", (unsigned long)p->tcpip_if, (unsigned long)p->ip_protocol);
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (p->dst.type == ESP_IPADDR_TYPE_V4) {
|
||||
_mdns_dbg_printf("To: " IPSTR ":%u, ", IP2STR(&p->dst.u_addr.ip4), p->port);
|
||||
} else {
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (p->dst.type == ESP_IPADDR_TYPE_V6) {
|
||||
_mdns_dbg_printf("To: " IPV6STR ":%u, ", IPV62STR(p->dst.u_addr.ip6), p->port);
|
||||
}
|
||||
#endif
|
||||
mdns_debug_packet(packet, index);
|
||||
#endif
|
||||
|
||||
@ -3511,14 +3536,24 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
const uint8_t *content = data + MDNS_HEAD_LEN;
|
||||
bool do_not_reply = false;
|
||||
mdns_search_once_t *search_result = NULL;
|
||||
mdns_browse_t *browse_result = NULL;
|
||||
char *browse_result_instance = NULL;
|
||||
char *browse_result_service = NULL;
|
||||
char *browse_result_proto = NULL;
|
||||
mdns_browse_sync_t *out_sync_browse = NULL;
|
||||
|
||||
#ifdef MDNS_ENABLE_DEBUG
|
||||
_mdns_dbg_printf("\nRX[%lu][%lu]: ", (unsigned long)packet->tcpip_if, (unsigned long)packet->ip_protocol);
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (packet->src.type == ESP_IPADDR_TYPE_V4) {
|
||||
_mdns_dbg_printf("From: " IPSTR ":%u, To: " IPSTR ", ", IP2STR(&packet->src.u_addr.ip4), packet->src_port, IP2STR(&packet->dest.u_addr.ip4));
|
||||
} else {
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (packet->src.type == ESP_IPADDR_TYPE_V6) {
|
||||
_mdns_dbg_printf("From: " IPV6STR ":%u, To: " IPV6STR ", ", IPV62STR(packet->src.u_addr.ip6), packet->src_port, IPV62STR(packet->dest.u_addr.ip6));
|
||||
}
|
||||
#endif
|
||||
mdns_debug_packet(data, len);
|
||||
#endif
|
||||
|
||||
@ -3722,6 +3757,45 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
continue;
|
||||
}
|
||||
search_result = _mdns_search_find_from(_mdns_server->search_once, name, type, packet->tcpip_if, packet->ip_protocol);
|
||||
browse_result = _mdns_browse_find_from(_mdns_server->browse, name, type, packet->tcpip_if, packet->ip_protocol);
|
||||
if (browse_result) {
|
||||
if (!out_sync_browse) {
|
||||
// will be freed in function `_mdns_browse_sync`
|
||||
out_sync_browse = (mdns_browse_sync_t *)malloc(sizeof(mdns_browse_sync_t));
|
||||
if (!out_sync_browse) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
goto clear_rx_packet;
|
||||
}
|
||||
out_sync_browse->browse = browse_result;
|
||||
out_sync_browse->sync_result = NULL;
|
||||
}
|
||||
if (!browse_result_service) {
|
||||
browse_result_service = (char *)malloc(MDNS_NAME_BUF_LEN);
|
||||
if (!browse_result_service) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
goto clear_rx_packet;
|
||||
}
|
||||
}
|
||||
memcpy(browse_result_service, browse_result->service, MDNS_NAME_BUF_LEN);
|
||||
if (!browse_result_proto) {
|
||||
browse_result_proto = (char *)malloc(MDNS_NAME_BUF_LEN);
|
||||
if (!browse_result_proto) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
goto clear_rx_packet;
|
||||
}
|
||||
}
|
||||
memcpy(browse_result_proto, browse_result->proto, MDNS_NAME_BUF_LEN);
|
||||
if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) {
|
||||
if (!browse_result_instance) {
|
||||
browse_result_instance = (char *)malloc(MDNS_NAME_BUF_LEN);
|
||||
if (!browse_result_instance) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
goto clear_rx_packet;
|
||||
}
|
||||
}
|
||||
memcpy(browse_result_instance, name->host, MDNS_NAME_BUF_LEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == MDNS_TYPE_PTR) {
|
||||
@ -3774,6 +3848,10 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
uint16_t weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET);
|
||||
uint16_t port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET);
|
||||
|
||||
if (browse_result) {
|
||||
_mdns_browse_result_add_srv(browse_result, name->host, browse_result_instance, browse_result_service,
|
||||
browse_result_proto, port, packet->tcpip_if, packet->ip_protocol, ttl, out_sync_browse);
|
||||
}
|
||||
if (search_result) {
|
||||
if (search_result->type == MDNS_TYPE_PTR) {
|
||||
if (!result->hostname) { // assign host/port for this entry only if not previously set
|
||||
@ -3842,12 +3920,17 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
}
|
||||
}
|
||||
} else if (type == MDNS_TYPE_TXT) {
|
||||
if (search_result) {
|
||||
mdns_txt_item_t *txt = NULL;
|
||||
uint8_t *txt_value_len = NULL;
|
||||
size_t txt_count = 0;
|
||||
mdns_txt_item_t *txt = NULL;
|
||||
uint8_t *txt_value_len = NULL;
|
||||
size_t txt_count = 0;
|
||||
|
||||
mdns_result_t *result = NULL;
|
||||
mdns_result_t *result = NULL;
|
||||
if (browse_result) {
|
||||
_mdns_result_txt_create(data_ptr, data_len, &txt, &txt_value_len, &txt_count);
|
||||
_mdns_browse_result_add_txt(browse_result, browse_result_instance, browse_result_service, browse_result_proto,
|
||||
txt, txt_value_len, txt_count, packet->tcpip_if, packet->ip_protocol, ttl, out_sync_browse);
|
||||
}
|
||||
if (search_result) {
|
||||
if (search_result->type == MDNS_TYPE_PTR) {
|
||||
result = search_result->result;
|
||||
while (result) {
|
||||
@ -3910,6 +3993,9 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
esp_ip_addr_t ip6;
|
||||
ip6.type = ESP_IPADDR_TYPE_V6;
|
||||
memcpy(ip6.u_addr.ip6.addr, data_ptr, MDNS_ANSWER_AAAA_SIZE);
|
||||
if (browse_result) {
|
||||
_mdns_browse_result_add_ip(browse_result, name->host, &ip6, packet->tcpip_if, packet->ip_protocol, ttl, out_sync_browse);
|
||||
}
|
||||
if (search_result) {
|
||||
//check for more applicable searches (PTR & A/AAAA at the same time)
|
||||
while (search_result) {
|
||||
@ -3964,6 +4050,9 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
esp_ip_addr_t ip;
|
||||
ip.type = ESP_IPADDR_TYPE_V4;
|
||||
memcpy(&(ip.u_addr.ip4.addr), data_ptr, 4);
|
||||
if (browse_result) {
|
||||
_mdns_browse_result_add_ip(browse_result, name->host, &ip, packet->tcpip_if, packet->ip_protocol, ttl, out_sync_browse);
|
||||
}
|
||||
if (search_result) {
|
||||
//check for more applicable searches (PTR & A/AAAA at the same time)
|
||||
while (search_result) {
|
||||
@ -4023,7 +4112,21 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
if (!do_not_reply && _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].state > PCB_PROBE_3 && (parsed_packet->questions || parsed_packet->discovery)) {
|
||||
_mdns_create_answer_from_parsed_packet(parsed_packet);
|
||||
}
|
||||
|
||||
if (out_sync_browse) {
|
||||
#ifdef MDNS_ENABLE_DEBUG
|
||||
_mdns_dbg_printf("Browse %s%s total result:", out_sync_browse->browse->service, out_sync_browse->browse->proto);
|
||||
debug_printf_browse_result_all(out_sync_browse->browse->result);
|
||||
#endif // MDNS_ENABLE_DEBUG
|
||||
if (out_sync_browse->sync_result) {
|
||||
#ifdef MDNS_ENABLE_DEBUG
|
||||
_mdns_dbg_printf("Changed result:");
|
||||
debug_printf_browse_result_all(out_sync_browse->sync_result->result);
|
||||
#endif // MDNS_ENABLE_DEBUG
|
||||
_mdns_sync_browse_action(ACTION_BROWSE_SYNC, out_sync_browse);
|
||||
} else {
|
||||
free(out_sync_browse);
|
||||
}
|
||||
}
|
||||
|
||||
clear_rx_packet:
|
||||
while (parsed_packet->questions) {
|
||||
@ -4044,6 +4147,15 @@ clear_rx_packet:
|
||||
free(question);
|
||||
}
|
||||
free(parsed_packet);
|
||||
if (browse_result_instance) {
|
||||
free(browse_result_instance);
|
||||
}
|
||||
if (browse_result_service) {
|
||||
free(browse_result_service);
|
||||
}
|
||||
if (browse_result_proto) {
|
||||
free(browse_result_proto);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4402,12 +4514,16 @@ static void _mdns_result_add_ip(mdns_result_t *r, esp_ip_addr_t *ip)
|
||||
mdns_ip_addr_t *a = r->addr;
|
||||
while (a) {
|
||||
if (a->addr.type == ip->type) {
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (a->addr.type == ESP_IPADDR_TYPE_V4 && a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (a->addr.type == ESP_IPADDR_TYPE_V6 && !memcmp(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
@ -4608,6 +4724,7 @@ free_txt:
|
||||
free((char *)(txt[i].value));
|
||||
}
|
||||
free(txt);
|
||||
free(r->txt_value_len);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4850,6 +4967,18 @@ static void _mdns_remap_self_service_hostname(const char *old_hostname, const ch
|
||||
}
|
||||
}
|
||||
|
||||
static void _mdns_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;
|
||||
free(need_free);
|
||||
}
|
||||
free(browse_sync);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free action data
|
||||
*/
|
||||
@ -4889,6 +5018,14 @@ static void _mdns_free_action(mdns_action_t *action)
|
||||
case ACTION_SEARCH_END:
|
||||
_mdns_search_free(action->data.search_add.search);
|
||||
break;
|
||||
case ACTION_BROWSE_ADD:
|
||||
//fallthrough
|
||||
case ACTION_BROWSE_END:
|
||||
_mdns_browse_item_free(action->data.browse_add.browse);
|
||||
break;
|
||||
case ACTION_BROWSE_SYNC:
|
||||
_mdns_sync_browse_result_link_free(action->data.browse_sync.browse_sync);
|
||||
break;
|
||||
case ACTION_TX_HANDLE:
|
||||
_mdns_free_tx_packet(action->data.tx_handle.packet);
|
||||
break;
|
||||
@ -5093,6 +5230,18 @@ static void _mdns_execute_action(mdns_action_t *action)
|
||||
case ACTION_SEARCH_END:
|
||||
_mdns_search_finish(action->data.search_add.search);
|
||||
break;
|
||||
|
||||
case ACTION_BROWSE_ADD:
|
||||
_mdns_browse_add(action->data.browse_add.browse);
|
||||
break;
|
||||
case ACTION_BROWSE_SYNC:
|
||||
_mdns_browse_sync(action->data.browse_sync.browse_sync);
|
||||
_mdns_sync_browse_result_link_free(action->data.browse_sync.browse_sync);
|
||||
break;
|
||||
case ACTION_BROWSE_END:
|
||||
_mdns_browse_finish(action->data.browse_add.browse);
|
||||
break;
|
||||
|
||||
case ACTION_TX_HANDLE: {
|
||||
mdns_tx_packet_t *p = _mdns_server->tx_queue_head;
|
||||
// packet to be handled should be at tx head, but must be consistent with the one pushed to action queue
|
||||
@ -5590,6 +5739,12 @@ void mdns_free(void)
|
||||
}
|
||||
free(h);
|
||||
}
|
||||
while (_mdns_server->browse) {
|
||||
mdns_browse_t *b = _mdns_server->browse;
|
||||
_mdns_server->browse = _mdns_server->browse->next;
|
||||
_mdns_browse_item_free(b);
|
||||
|
||||
}
|
||||
vSemaphoreDelete(_mdns_server->action_sema);
|
||||
free(_mdns_server);
|
||||
_mdns_server = NULL;
|
||||
@ -6797,4 +6952,593 @@ void mdns_debug_packet(const uint8_t *data, size_t len)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* MDNS_ENABLE_DEBUG */
|
||||
|
||||
/**
|
||||
* @brief Browse sync result action
|
||||
*/
|
||||
static esp_err_t _mdns_sync_browse_action(mdns_action_type_t type, mdns_browse_sync_t *browse_sync)
|
||||
{
|
||||
mdns_action_t *action = NULL;
|
||||
|
||||
action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
|
||||
if (!action) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
action->type = type;
|
||||
action->data.browse_sync.browse_sync = browse_sync;
|
||||
if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
|
||||
free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Browse action
|
||||
*/
|
||||
static esp_err_t _mdns_send_browse_action(mdns_action_type_t type, mdns_browse_t *browse)
|
||||
{
|
||||
mdns_action_t *action = NULL;
|
||||
|
||||
action = (mdns_action_t *)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 (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
|
||||
free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free a browse item (Not free the list).
|
||||
*/
|
||||
static void _mdns_browse_item_free(mdns_browse_t *browse)
|
||||
{
|
||||
free(browse->service);
|
||||
free(browse->proto);
|
||||
if (browse->result) {
|
||||
mdns_query_results_free(browse->result);
|
||||
}
|
||||
free(browse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate new browse structure
|
||||
*/
|
||||
static mdns_browse_t *_mdns_browse_init(const char *service, const char *proto, mdns_browse_notify_t notifier)
|
||||
{
|
||||
mdns_browse_t *browse = (mdns_browse_t *)malloc(sizeof(mdns_browse_t));
|
||||
|
||||
if (!browse) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
memset(browse, 0, sizeof(mdns_browse_t));
|
||||
|
||||
browse->state = BROWSE_INIT;
|
||||
if (!_str_null_or_empty(service)) {
|
||||
browse->service = strndup(service, MDNS_NAME_BUF_LEN - 1);
|
||||
if (!browse->service) {
|
||||
_mdns_browse_item_free(browse);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_str_null_or_empty(proto)) {
|
||||
browse->proto = strndup(proto, MDNS_NAME_BUF_LEN - 1);
|
||||
if (!browse->proto) {
|
||||
_mdns_browse_item_free(browse);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
browse->notifier = notifier;
|
||||
return browse;
|
||||
}
|
||||
|
||||
mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_browse_notify_t notifier)
|
||||
{
|
||||
mdns_browse_t *browse = NULL;
|
||||
|
||||
if (!_mdns_server || _str_null_or_empty(service) || _str_null_or_empty(proto)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
browse = _mdns_browse_init(service, proto, notifier);
|
||||
if (!browse) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (_mdns_send_browse_action(ACTION_BROWSE_ADD, browse)) {
|
||||
_mdns_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_server || _str_null_or_empty(service) || _str_null_or_empty(proto)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
browse = _mdns_browse_init(service, proto, NULL);
|
||||
if (!browse) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (_mdns_send_browse_action(ACTION_BROWSE_END, browse)) {
|
||||
_mdns_browse_item_free(browse);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark browse as finished, remove and free it from browse chain
|
||||
*/
|
||||
static void _mdns_browse_finish(mdns_browse_t *browse)
|
||||
{
|
||||
browse->state = BROWSE_OFF;
|
||||
mdns_browse_t *b = _mdns_server->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, _mdns_server->browse, target_free);
|
||||
_mdns_browse_item_free(target_free);
|
||||
} else {
|
||||
b = b->next;
|
||||
}
|
||||
}
|
||||
_mdns_browse_item_free(browse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add new browse to the browse chain
|
||||
*/
|
||||
static void _mdns_browse_add(mdns_browse_t *browse)
|
||||
{
|
||||
browse->state = BROWSE_RUNNING;
|
||||
mdns_browse_t *queue = _mdns_server->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 = _mdns_server->browse;
|
||||
_mdns_server->browse = browse;
|
||||
}
|
||||
_mdns_browse_send(browse);
|
||||
if (found) {
|
||||
_mdns_browse_item_free(browse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send PTR query packet to all available interfaces for browsing.
|
||||
*/
|
||||
static void _mdns_browse_send(mdns_browse_t *browse)
|
||||
{
|
||||
// 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;
|
||||
|
||||
uint8_t i, j;
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
for (j = 0; j < MDNS_IP_PROTOCOL_MAX; j++) {
|
||||
_mdns_search_send_pcb(&search, (mdns_if_t)i, (mdns_ip_protocol_t)j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add result to browse, only add when the result is a new one.
|
||||
*/
|
||||
static esp_err_t _mdns_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 *)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
|
||||
*/
|
||||
static void _mdns_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_get_esp_netif(tcpip_if) && !_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_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_result_update_ttl(r, ttl);
|
||||
}
|
||||
}
|
||||
if (_mdns_add_browse_result(out_sync_browse, r) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from packet parser to find matching running search
|
||||
*/
|
||||
static mdns_browse_t *_mdns_browse_find_from(mdns_browse_t *b, mdns_name_t *name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
// 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_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(name->host, r->hostname)) {
|
||||
return b;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
b = b->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add TXT data to search result
|
||||
*/
|
||||
static void _mdns_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_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol &&
|
||||
!_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name) &&
|
||||
!_str_null_or_empty(r->service_type) && !strcasecmp(service, r->service_type) &&
|
||||
!_str_null_or_empty(r->proto) && !strcasecmp(proto, r->proto)) {
|
||||
if (r->txt) {
|
||||
// If the result has a previous txt entry, we delete it and re-add.
|
||||
// TODO: we need to check if txt changes.
|
||||
for (size_t i = 0; i < r->txt_count; i++) {
|
||||
free((char *)(r->txt[i].key));
|
||||
free((char *)(r->txt[i].value));
|
||||
}
|
||||
free(r->txt);
|
||||
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_result_update_ttl(r, ttl);
|
||||
}
|
||||
if (previous_ttl != r->ttl) {
|
||||
if (_mdns_add_browse_result(out_sync_browse, r) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
r = (mdns_result_t *)malloc(sizeof(mdns_result_t));
|
||||
if (!r) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
goto free_txt;
|
||||
}
|
||||
memset(r, 0, sizeof(mdns_result_t));
|
||||
r->instance_name = strdup(instance);
|
||||
r->service_type = strdup(service);
|
||||
r->proto = strdup(proto);
|
||||
if (!r->instance_name || !r->service_type || !r->proto) {
|
||||
free(r->instance_name);
|
||||
free(r->service_type);
|
||||
free(r->proto);
|
||||
free(r);
|
||||
return;
|
||||
}
|
||||
r->txt = txt;
|
||||
r->txt_value_len = txt_value_len;
|
||||
r->txt_count = txt_count;
|
||||
r->esp_netif = _mdns_get_esp_netif(tcpip_if);
|
||||
r->ip_protocol = ip_protocol;
|
||||
r->ttl = ttl;
|
||||
r->next = browse->result;
|
||||
browse->result = r;
|
||||
_mdns_add_browse_result(out_sync_browse, r);
|
||||
return;
|
||||
|
||||
free_txt:
|
||||
for (size_t i = 0; i < txt_count; i++) {
|
||||
free((char *)(txt[i].key));
|
||||
free((char *)(txt[i].value));
|
||||
}
|
||||
free(txt);
|
||||
free(r->txt_value_len);
|
||||
return;
|
||||
}
|
||||
|
||||
static esp_err_t _mdns_copy_address_in_previous_result(mdns_result_t *result_list, mdns_result_t *r)
|
||||
{
|
||||
while (result_list) {
|
||||
if (!_str_null_or_empty(result_list->hostname) && !_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 = 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
|
||||
*/
|
||||
static void _mdns_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_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol &&
|
||||
!_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name) &&
|
||||
!_str_null_or_empty(r->service_type) && !strcasecmp(service, r->service_type) &&
|
||||
!_str_null_or_empty(r->proto) && !strcasecmp(proto, r->proto)) {
|
||||
if (_str_null_or_empty(r->hostname) || strcasecmp(hostname, r->hostname)) {
|
||||
r->hostname = strdup(hostname);
|
||||
r->port = port;
|
||||
if (!r->hostname) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return;
|
||||
}
|
||||
if (!r->addr) {
|
||||
esp_err_t err = _mdns_copy_address_in_previous_result(browse->result, r);
|
||||
if (err == ESP_ERR_NO_MEM) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_mdns_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_result_update_ttl(r, ttl);
|
||||
}
|
||||
if (previous_ttl != r->ttl) {
|
||||
if (_mdns_add_browse_result(out_sync_browse, r) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
r = (mdns_result_t *)malloc(sizeof(mdns_result_t));
|
||||
if (!r) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
memset(r, 0, sizeof(mdns_result_t));
|
||||
r->hostname = strdup(hostname);
|
||||
r->instance_name = strdup(instance);
|
||||
r->service_type = strdup(service);
|
||||
r->proto = strdup(proto);
|
||||
if (!r->hostname || !r->instance_name || !r->service_type || !r->proto) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
free(r->hostname);
|
||||
free(r->instance_name);
|
||||
free(r->service_type);
|
||||
free(r->proto);
|
||||
free(r);
|
||||
return;
|
||||
}
|
||||
r->port = port;
|
||||
r->esp_netif = _mdns_get_esp_netif(tcpip_if);
|
||||
r->ip_protocol = ip_protocol;
|
||||
r->ttl = ttl;
|
||||
r->next = browse->result;
|
||||
browse->result = r;
|
||||
_mdns_add_browse_result(out_sync_browse, r);
|
||||
return;
|
||||
}
|
||||
|
||||
static void _mdns_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;
|
||||
#ifdef MDNS_ENABLE_DEBUG
|
||||
debug_printf_browse_result(result, browse_sync->browse);
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MDNS_ENABLE_DEBUG
|
||||
void _debug_printf_result(mdns_result_t *r_t)
|
||||
{
|
||||
mdns_ip_addr_t *r_a = NULL;
|
||||
int addr_count = 0;
|
||||
_mdns_dbg_printf("result esp_netif: %p\n", r_t->esp_netif);
|
||||
_mdns_dbg_printf("result ip_protocol: %d\n", r_t->ip_protocol);
|
||||
_mdns_dbg_printf("result hostname: %s\n", _str_null_or_empty(r_t->hostname) ? "NULL" : r_t->hostname);
|
||||
_mdns_dbg_printf("result instance_name: %s\n", _str_null_or_empty(r_t->instance_name) ? "NULL" : r_t->instance_name);
|
||||
_mdns_dbg_printf("result service_type: %s\n", _str_null_or_empty(r_t->service_type) ? "NULL" : r_t->service_type);
|
||||
_mdns_dbg_printf("result proto: %s\n", _str_null_or_empty(r_t->proto) ? "NULL" : r_t->proto);
|
||||
_mdns_dbg_printf("result port: %d\n", r_t->port);
|
||||
_mdns_dbg_printf("result ttl: %" PRIu32 "\n", r_t->ttl);
|
||||
for (int i = 0; i < r_t->txt_count; i++) {
|
||||
_mdns_dbg_printf("result txt item%d, key: %s, value: %s\n", i, r_t->txt[i].key, r_t->txt[i].value);
|
||||
}
|
||||
r_a = r_t->addr;
|
||||
while (r_a) {
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (r_a->addr.type == ESP_IPADDR_TYPE_V4) {
|
||||
_mdns_dbg_printf("Addr%d: " IPSTR "\n", addr_count++, IP2STR(&r_a->addr.u_addr.ip4));
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (r_a->addr.type == ESP_IPADDR_TYPE_V6) {
|
||||
_mdns_dbg_printf("Addr%d: " IPV6STR "\n", addr_count++, IPV62STR(r_a->addr.u_addr.ip6));
|
||||
}
|
||||
#endif
|
||||
r_a = r_a->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void debug_printf_browse_result(mdns_result_t *r_t, mdns_browse_t *b_t)
|
||||
{
|
||||
_mdns_dbg_printf("----------------sync browse %s.%s result---------------\n", b_t->service, b_t->proto);
|
||||
_mdns_dbg_printf("browse pointer: %p\n", b_t);
|
||||
_debug_printf_result(r_t);
|
||||
}
|
||||
|
||||
static void debug_printf_browse_result_all(mdns_result_t *r_t)
|
||||
{
|
||||
int count = 0;
|
||||
while (r_t) {
|
||||
_mdns_dbg_printf("----------------result %d---------------\n", count++);
|
||||
_debug_printf_result(r_t);
|
||||
r_t = r_t->next;
|
||||
}
|
||||
}
|
||||
#endif // MDNS_ENABLE_DEBUG
|
||||
|
@ -199,6 +199,9 @@ typedef enum {
|
||||
ACTION_SEARCH_ADD,
|
||||
ACTION_SEARCH_SEND,
|
||||
ACTION_SEARCH_END,
|
||||
ACTION_BROWSE_ADD,
|
||||
ACTION_BROWSE_SYNC,
|
||||
ACTION_BROWSE_END,
|
||||
ACTION_TX_HANDLE,
|
||||
ACTION_RX_HANDLE,
|
||||
ACTION_TASK_STOP,
|
||||
@ -370,6 +373,13 @@ typedef enum {
|
||||
SEARCH_MAX
|
||||
} mdns_search_once_state_t;
|
||||
|
||||
typedef enum {
|
||||
BROWSE_OFF,
|
||||
BROWSE_INIT,
|
||||
BROWSE_RUNNING,
|
||||
BROWSE_MAX
|
||||
} mdns_browse_state_t;
|
||||
|
||||
typedef struct mdns_search_once_s {
|
||||
struct mdns_search_once_s *next;
|
||||
|
||||
@ -389,6 +399,27 @@ typedef struct mdns_search_once_s {
|
||||
mdns_result_t *result;
|
||||
} mdns_search_once_t;
|
||||
|
||||
typedef struct mdns_browse_s {
|
||||
struct mdns_browse_s *next;
|
||||
|
||||
mdns_browse_state_t state;
|
||||
mdns_browse_notify_t notifier;
|
||||
|
||||
char *service;
|
||||
char *proto;
|
||||
mdns_result_t *result;
|
||||
} mdns_browse_t;
|
||||
|
||||
typedef struct mdns_browse_result_sync_t {
|
||||
mdns_result_t *result;
|
||||
struct mdns_browse_result_sync_t *next;
|
||||
} mdns_browse_result_sync_t;
|
||||
|
||||
typedef struct mdns_browse_sync {
|
||||
mdns_browse_t *browse;
|
||||
mdns_browse_result_sync_t *sync_result;
|
||||
} mdns_browse_sync_t;
|
||||
|
||||
typedef struct mdns_server_s {
|
||||
struct {
|
||||
mdns_pcb_t pcbs[MDNS_IP_PROTOCOL_MAX];
|
||||
@ -401,6 +432,7 @@ typedef struct mdns_server_s {
|
||||
mdns_tx_packet_t *tx_queue_head;
|
||||
mdns_search_once_t *search_once;
|
||||
esp_timer_handle_t timer_handle;
|
||||
mdns_browse_t *browse;
|
||||
} mdns_server_t;
|
||||
|
||||
typedef struct {
|
||||
@ -459,6 +491,12 @@ typedef struct {
|
||||
const char *hostname;
|
||||
mdns_ip_addr_t *address_list;
|
||||
} delegate_hostname;
|
||||
struct {
|
||||
mdns_browse_t *browse;
|
||||
} browse_add;
|
||||
struct {
|
||||
mdns_browse_sync_t *browse_sync;
|
||||
} browse_sync;
|
||||
} data;
|
||||
} mdns_action_t;
|
||||
|
||||
|
Reference in New Issue
Block a user