WIP: System Events

This commit is contained in:
Joe Kearney 2025-11-22 14:18:35 -06:00
parent 1c2f281579
commit c979c38fd7
46 changed files with 647 additions and 470 deletions

View file

@ -1 +1 @@
3ec0af5f6bce310512e90f482388d21cc7c0e99668172d2f895356165fc6f7c5
29e47564b1a7ee778135e17fbbf2a2773f71c97ebabfe626c8eda7c958a7ad16

View file

@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(mdns): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py mdns
tag_format: mdns-v$version
version: 1.8.2
version: 1.9.1
version_files:
- idf_component.yml

View file

@ -1,5 +1,32 @@
# Changelog
## [1.9.1](https://github.com/espressif/esp-protocols/commits/mdns-v1.9.1)
### Bug Fixes
- Fix to use tagged AFL image + minor format fix ([2b2f009a](https://github.com/espressif/esp-protocols/commit/2b2f009a))
- Fix unused variable `dcst` warning for wifi-remote chips ([081eef88](https://github.com/espressif/esp-protocols/commit/081eef88))
## [1.9.0](https://github.com/espressif/esp-protocols/commits/mdns-v1.9.0)
### Features
- support null value for boolean txt records ([fa96de3b](https://github.com/espressif/esp-protocols/commit/fa96de3b))
### Bug Fixes
- Add test case for bool/NULL txt handling ([5068f221](https://github.com/espressif/esp-protocols/commit/5068f221))
- Temporary fix for build issues on IDF master ([0197c994](https://github.com/espressif/esp-protocols/commit/0197c994))
- Add tests for delegated answers ([487a746d](https://github.com/espressif/esp-protocols/commit/487a746d))
- Add fuzzing into mdns CI ([af6bb1b5](https://github.com/espressif/esp-protocols/commit/af6bb1b5))
- Host test to use hw_support include dir ([8bba3a97](https://github.com/espressif/esp-protocols/commit/8bba3a97))
- Fixes case where we create our own malloc/free allocators, therefore we need to call mdns_mem_free and not free ([63bf7091](https://github.com/espressif/esp-protocols/commit/63bf7091))
- put srv/txt records in additional section for ptr queries ([b7b8c5db](https://github.com/espressif/esp-protocols/commit/b7b8c5db))
### Updated
- ci(common): Update test component dir for IDFv6.0 ([18418c83](https://github.com/espressif/esp-protocols/commit/18418c83))
## [1.8.2](https://github.com/espressif/esp-protocols/commits/mdns-v1.8.2)
### Bug Fixes

View file

@ -1,8 +1,6 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/mdns:
version: "^1.0.0"
override_path: "../../../"
version: ^1.0.0
idf: '>=5.0'
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View file

@ -12,6 +12,7 @@ CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_PHY_GENERIC=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5

View file

@ -7,7 +7,7 @@ documentation: https://docs.espressif.com/projects/esp-protocols/mdns/docs/lates
issues: https://github.com/espressif/esp-protocols/issues
repository: git://github.com/espressif/esp-protocols.git
repository_info:
commit_sha: e9d7350219dfb5e39eb56e5ef60c094190888c55
commit_sha: 3bfa00389de6f0d6d40efda8bea808380899a43d
path: components/mdns
url: https://github.com/espressif/esp-protocols/tree/master/components/mdns
version: 1.8.2
version: 1.9.1

View file

@ -33,7 +33,7 @@ static void _mdns_browse_send(mdns_browse_t *browse, mdns_if_t interface);
#if ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(5, 1, 0)
#define MDNS_ESP_WIFI_ENABLED CONFIG_SOC_WIFI_SUPPORTED
#else
#define MDNS_ESP_WIFI_ENABLED CONFIG_ESP_WIFI_ENABLED
#define MDNS_ESP_WIFI_ENABLED (CONFIG_ESP_WIFI_ENABLED || CONFIG_ESP_WIFI_REMOTE_ENABLED)
#endif
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
@ -1795,8 +1795,8 @@ static bool _mdns_create_answer_from_service(mdns_tx_packet_t *packet, mdns_serv
// According to RFC6763-section12.1, for DNS-SD, SRV, TXT and all address records
// should be included in additional records.
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service, NULL, false, false) ||
!_mdns_alloc_answer(is_delegated ? &packet->additional : &packet->answers, MDNS_TYPE_SRV, service, NULL, send_flush, false) ||
!_mdns_alloc_answer(is_delegated ? &packet->additional : &packet->answers, MDNS_TYPE_TXT, service, NULL, send_flush, false) ||
!_mdns_alloc_answer(&packet->additional, MDNS_TYPE_SRV, service, NULL, send_flush, false) ||
!_mdns_alloc_answer(&packet->additional, MDNS_TYPE_TXT, service, NULL, send_flush, false) ||
!_mdns_alloc_answer((shared || is_delegated) ? &packet->additional : &packet->answers, MDNS_TYPE_A, service, host, send_flush,
false) ||
!_mdns_alloc_answer((shared || is_delegated) ? &packet->additional : &packet->answers, MDNS_TYPE_AAAA, service, host,
@ -2656,13 +2656,18 @@ static mdns_txt_linked_item_t *_mdns_allocate_txt(size_t num_items, mdns_txt_ite
mdns_mem_free(new_item);
break;
}
new_item->value = mdns_mem_strdup(txt[i].value);
if (!new_item->value) {
mdns_mem_free((char *)new_item->key);
mdns_mem_free(new_item);
break;
if (txt[i].value) {
new_item->value = mdns_mem_strdup(txt[i].value);
if (!new_item->value) {
mdns_mem_free((char *)new_item->key);
mdns_mem_free(new_item);
break;
}
new_item->value_len = strlen(new_item->value);
} else {
new_item->value = NULL;
new_item->value_len = 0;
}
new_item->value_len = strlen(new_item->value);
new_item->next = new_txt;
new_txt = new_item;
}
@ -4488,9 +4493,9 @@ void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
return;
}
esp_netif_dhcp_status_t dcst;
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
if (event_base == WIFI_EVENT) {
esp_netif_dhcp_status_t dcst;
switch (event_id) {
case WIFI_EVENT_STA_CONNECTED:
if (!esp_netif_dhcpc_get_status(esp_netif_from_preset_if(MDNS_IF_STA), &dcst)) {
@ -4517,6 +4522,7 @@ void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
#endif
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
if (event_base == ETH_EVENT) {
esp_netif_dhcp_status_t dcst;
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
if (!esp_netif_dhcpc_get_status(esp_netif_from_preset_if(MDNS_IF_ETH), &dcst)) {

View file

@ -154,7 +154,7 @@
} \
}
#define queueFree(type, queue) while (queue) { type * _q = queue; queue = queue->next; free(_q); }
#define queueFree(type, queue) while (queue) { type * _q = queue; queue = queue->next; mdns_mem_free(_q); }
#define PCB_STATE_IS_PROBING(s) (s->state > PCB_OFF && s->state < PCB_ANNOUNCE_1)
#define PCB_STATE_IS_ANNOUNCING(s) (s->state > PCB_PROBE_3 && s->state < PCB_RUNNING)

View file

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import re
@ -92,10 +92,58 @@ class DnsPythonWrapper:
if expect is None:
expect = name
if expected:
assert any(expect in answer for answer in answers), f"Expected record '{expect}' not found in answer section"
assert any(expect in answer for answer in answers), f"Expected record '{expect}' not in answer section"
else:
assert not any(expect in answer for answer in answers), f"Unexpected record '{expect}' found in answer section"
def parse_section(self, response, section: str, rdtype_text: str):
"""Parse a specific response section (answer, authority, additional) for given rdtype.
Returns list of textual records for that rdtype.
"""
out = []
if not response:
return out
rrsets = []
if section == 'answer':
rrsets = response.answer
elif section == 'authority':
rrsets = response.authority
elif section == 'additional':
rrsets = response.additional
else:
raise ValueError('invalid section')
for rr in rrsets:
if dns.rdatatype.to_text(rr.rdtype) != rdtype_text:
continue
for item in rr.items:
full = (
f'{rr.name} {rr.ttl} '
f'{dns.rdataclass.to_text(rr.rdclass)} '
f'{dns.rdatatype.to_text(rr.rdtype)} '
f'{item.to_text()}'
)
out.append(full)
return out
def check_additional(self, response, rdtype_text: str, owner_contains: str, expected: bool = True, expect_substr: str | None = None):
"""Check Additional section for an RR of type rdtype_text whose owner includes owner_contains.
If expect_substr is provided, also require it to appear in the textual RR.
"""
records = self.parse_section(response, 'additional', rdtype_text)
logger.info(f'additional({rdtype_text}): {records}')
def _matches(line: str) -> bool:
in_owner = owner_contains in line
has_val = (expect_substr in line) if expect_substr else True
return in_owner and has_val
found = any(_matches(r) for r in records)
if expected:
assert found, f"Expected {rdtype_text} for {owner_contains} in Additional not found"
else:
assert not found, f"Unexpected {rdtype_text} for {owner_contains} found in Additional"
if __name__ == '__main__':
if len(sys.argv) < 3:

View file

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
@ -65,6 +65,17 @@ def test_add_service(mdns_console, dig_app):
dig_app.check_record('_http._tcp.local', query_type='PTR', expected=True)
def test_ptr_additional_records_for_service(dig_app):
# Query PTR for the service type and ensure SRV/TXT are in Additional (RFC 6763 §12.1)
resp = dig_app.run_query('_http._tcp.local', query_type='PTR')
# Answer section should have at least one PTR to the instance
answers = dig_app.parse_answer_section(resp, 'PTR')
assert any('test_service._http._tcp.local' in a for a in answers)
# Additional section should include SRV and TXT for the same instance
dig_app.check_additional(resp, 'SRV', 'test_service._http._tcp.local', expected=True)
dig_app.check_additional(resp, 'TXT', 'test_service._http._tcp.local', expected=True)
def test_remove_service(mdns_console, dig_app):
mdns_console.send_input('mdns_service_remove _http _tcp')
mdns_console.send_input('mdns_service_lookup _http _tcp')

View file

@ -1,7 +1,9 @@
#INSTR=off
TEST_NAME=test
FUZZ=afl-fuzz
COMPONENTS_DIR=$(IDF_PATH)/components
COMPILER_ICLUDE_DIR=$(shell echo `which xtensa-esp32-elf-gcc | xargs dirname | xargs dirname`/xtensa-esp32-elf)
# Use ESP32 toolchain include path if available, otherwise fall back to system includes for host-based compilation
COMPILER_INCLUDE_DIR=$(shell if command -v xtensa-esp32-elf-gcc >/dev/null 2>&1; then echo `which xtensa-esp32-elf-gcc | xargs dirname | xargs dirname`/xtensa-esp32-elf; else echo /usr; fi)
CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversion -Wno-macro-redefined -Wno-int-to-void-pointer-cast -DHOOK_MALLOC_FAILED -DESP_EVENT_H_ -D__ESP_LOG_H__ \
-I. -I../.. -I../../include -I../../private_include -I ./build/config \
@ -34,7 +36,8 @@ CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversi
-I$(COMPONENTS_DIR)/soc/src/esp32/include \
-I$(COMPONENTS_DIR)/xtensa/include \
-I$(COMPONENTS_DIR)/xtensa/esp32/include \
-I$(COMPILER_ICLUDE_DIR)/include
-I$(COMPONENTS_DIR)/esp_hw_support/etm/include \
-I$(COMPILER_INCLUDE_DIR)/include
MDNS_C_DEPENDENCY_INJECTION=-include mdns_di.h
@ -76,7 +79,18 @@ $(TEST_NAME): $(OBJECTS)
@$(LD) $(OBJECTS) -o $@ $(LDLIBS)
fuzz: $(TEST_NAME)
@$(FUZZ) -i "in" -o "out" -- ./$(TEST_NAME)
# timeout returns 124 if time limit is reached, original return code otherwise
# pass only if: fuzzing was running smoothly until timeout AND no crash found
@timeout 10m $(FUZZ) -i "in" -o "out" -- ./$(TEST_NAME) || \
if [ $$? -eq 124 ]; then \
if [ -n "$$(find out/default/crashes -type f 2>/dev/null)" ]; then \
echo "Crashes found!"; \
tar -czf out/default/crashes.tar.gz -C out/default crashes; \
exit 1; \
fi \
else \
exit 1; \
fi
clean:
@rm -rf *.o *.SYM $(TEST_NAME) out

View file

@ -55,8 +55,7 @@
#define pdMS_TO_TICKS(a) a
#define xSemaphoreTake(s,d) true
#define xTaskDelete(a)
#define vTaskDelete(a) free(a)
#define vTaskDelete(a) free(NULL)
#define xSemaphoreGive(s)
#define xQueueCreateMutex(s)
#define _mdns_pcb_init(a,b) true
@ -66,7 +65,7 @@
#define vSemaphoreDelete(s) free(s)
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U
#define xTaskCreatePinnedToCore(a,b,c,d,e,f,g) *(f) = malloc(1)
#define xTaskCreateStaticPinnedToCore(a,b,c,d,e,f,g,h) true
#define xTaskCreateStaticPinnedToCore(a,b,c,d,e,f,g,h) ((void*)1)
#define vTaskDelay(m) usleep((m)*0)
#define esp_random() (rand()%UINT32_MAX)
@ -139,4 +138,8 @@ TaskHandle_t xTaskGetCurrentTaskHandle(void);
void xTaskNotifyGive(TaskHandle_t task);
BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time);
static inline void xTaskGetStaticBuffers(void *pvTaskBuffer, void *pvStackBuffer, void *pvTaskTCB)
{
}
#endif //_ESP32_COMPAT_H_

View file

@ -78,30 +78,20 @@ static int mdns_test_service_txt_set(const char *service, const char *proto, ui
static int mdns_test_sub_service_add(const char *sub_name, const char *service_name, const char *proto, uint32_t port)
{
if (mdns_service_add(NULL, service_name, proto, port, NULL, 0)) {
// This is expected failure as the service thread is not running
return ESP_FAIL;
}
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
if (mdns_test_mdns_get_service_item(service_name, proto) == NULL) {
return ESP_FAIL;
}
int ret = mdns_service_subtype_add_for_host(NULL, service_name, proto, NULL, sub_name);
a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return ret;
return mdns_service_subtype_add_for_host(NULL, service_name, proto, NULL, sub_name);
}
static int mdns_test_service_add(const char *service_name, const char *proto, uint32_t port)
{
if (mdns_service_add(NULL, service_name, proto, port, NULL, 0)) {
// This is expected failure as the service thread is not running
return ESP_FAIL;
}
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
if (mdns_test_mdns_get_service_item(service_name, proto) == NULL) {
return ESP_FAIL;
@ -266,9 +256,6 @@ int main(int argc, char **argv)
}
#ifndef MDNS_NO_SERVICES
mdns_service_remove_all();
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
#endif
ForceTaskDelete();
mdns_free();

View file

@ -1,7 +1,15 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS ../.. "$ENV{IDF_PATH}/tools/unit-test-app/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0")
set(test_component_dir $ENV{IDF_PATH}/tools/test_apps/components)
else()
set(test_component_dir $ENV{IDF_PATH}/tools/unit-test-app/components)
endif()
set(EXTRA_COMPONENT_DIRS ../..
${test_component_dir})
project(mdns_test)

View file

@ -61,6 +61,45 @@ TEST(mdns, init_deinit)
esp_event_loop_delete_default();
}
TEST(mdns, boolean_txt_null_value)
{
mdns_result_t *results = NULL;
test_case_uses_tcpip();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
TEST_ASSERT_EQUAL(ESP_OK, mdns_init());
TEST_ASSERT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
mdns_txt_item_t txt_data[] = {
{"bool", NULL},
{"key", "value"},
};
const size_t txt_data_count = sizeof(txt_data) / sizeof(txt_data[0]);
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_txt_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, txt_data, txt_data_count));
yield_to_all_priorities();
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_selfhosted_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_NOT_EQUAL(NULL, results->txt);
TEST_ASSERT_EQUAL(txt_data_count, results->txt_count);
bool found_bool = false;
for (size_t i = 0; i < results->txt_count; ++i) {
if (strcmp(results->txt[i].key, "bool") == 0) {
TEST_ASSERT_NOT_EQUAL(NULL, results->txt_value_len);
TEST_ASSERT_EQUAL_UINT8(0, results->txt_value_len[i]);
found_bool = true;
}
}
TEST_ASSERT_TRUE(found_bool);
mdns_query_results_free(results);
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_remove(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO));
mdns_free();
esp_event_loop_delete_default();
}
TEST(mdns, api_fails_with_expected_err)
{
mdns_txt_item_t serviceTxtData[CONFIG_MDNS_MAX_SERVICES] = { {NULL, NULL},
@ -290,6 +329,7 @@ TEST_GROUP_RUNNER(mdns)
RUN_TEST_CASE(mdns, init_deinit)
RUN_TEST_CASE(mdns, add_remove_service)
RUN_TEST_CASE(mdns, add_remove_deleg_service)
RUN_TEST_CASE(mdns, boolean_txt_null_value)
}