From 0fd71ca2111da9030248a80b9e8c6b2df8c801ed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 Oct 2025 15:30:17 -0500 Subject: [PATCH] [mdns][openthread] Use StaticVector for services storage with compile-time capacity (#10976) --- esphome/components/mdns/__init__.py | 17 +++++++- esphome/components/mdns/mdns_component.cpp | 43 ++++---------------- esphome/components/mdns/mdns_component.h | 14 +++---- esphome/components/openthread/openthread.cpp | 9 ++-- esphome/components/openthread/openthread.h | 1 - esphome/core/defines.h | 1 + esphome/core/helpers.h | 10 +++++ 7 files changed, 44 insertions(+), 51 deletions(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index a84fe5a249..ce0241677d 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -17,6 +17,11 @@ from esphome.coroutine import CoroPriority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +# Components that create mDNS services at runtime +# IMPORTANT: If you add a new component here, you must also update the corresponding +# #ifdef blocks in mdns_component.cpp compile_records_() method +COMPONENTS_WITH_MDNS_SERVICES = ("api", "prometheus", "web_server") + mdns_ns = cg.esphome_ns.namespace("mdns") MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component) MDNSTXTRecord = mdns_ns.struct("MDNSTXTRecord") @@ -91,12 +96,20 @@ async def to_code(config): cg.add_define("USE_MDNS") - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) + # Calculate compile-time service count + service_count = sum( + 1 for key in COMPONENTS_WITH_MDNS_SERVICES if key in CORE.config + ) + len(config[CONF_SERVICES]) if config[CONF_SERVICES]: cg.add_define("USE_MDNS_EXTRA_SERVICES") + # Ensure at least 1 service (fallback service) + cg.add_define("MDNS_SERVICE_COUNT", max(1, service_count)) + + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + for service in config[CONF_SERVICES]: txt = [ cg.StructInitializer( diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 5d9788198f..eed2516c6a 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -74,32 +74,12 @@ MDNS_STATIC_CONST_CHAR(NETWORK_THREAD, "thread"); void MDNSComponent::compile_records_() { this->hostname_ = App.get_name(); - // Calculate exact capacity needed for services vector - size_t services_count = 0; -#ifdef USE_API - if (api::global_api_server != nullptr) { - services_count++; - } -#endif -#ifdef USE_PROMETHEUS - services_count++; -#endif -#ifdef USE_WEBSERVER - services_count++; -#endif -#ifdef USE_MDNS_EXTRA_SERVICES - services_count += this->services_extra_.size(); -#endif - // Reserve for fallback service if needed - if (services_count == 0) { - services_count = 1; - } - this->services_.reserve(services_count); + // IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES + // in mdns/__init__.py. If you add a new service here, update both locations. #ifdef USE_API if (api::global_api_server != nullptr) { - this->services_.emplace_back(); - auto &service = this->services_.back(); + auto &service = this->services_.emplace_next(); service.service_type = MDNS_STR(SERVICE_ESPHOMELIB); service.proto = MDNS_STR(SERVICE_TCP); service.port = api::global_api_server->get_port(); @@ -178,30 +158,23 @@ void MDNSComponent::compile_records_() { #endif // USE_API #ifdef USE_PROMETHEUS - this->services_.emplace_back(); - auto &prom_service = this->services_.back(); + auto &prom_service = this->services_.emplace_next(); prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS); prom_service.proto = MDNS_STR(SERVICE_TCP); prom_service.port = USE_WEBSERVER_PORT; #endif #ifdef USE_WEBSERVER - this->services_.emplace_back(); - auto &web_service = this->services_.back(); + auto &web_service = this->services_.emplace_next(); web_service.service_type = MDNS_STR(SERVICE_HTTP); web_service.proto = MDNS_STR(SERVICE_TCP); web_service.port = USE_WEBSERVER_PORT; #endif -#ifdef USE_MDNS_EXTRA_SERVICES - this->services_.insert(this->services_.end(), this->services_extra_.begin(), this->services_extra_.end()); -#endif - #if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES) // Publish "http" service if not using native API or any other services // This is just to have *some* mDNS service so that .local resolution works - this->services_.emplace_back(); - auto &fallback_service = this->services_.back(); + auto &fallback_service = this->services_.emplace_next(); fallback_service.service_type = "_http"; fallback_service.proto = "_tcp"; fallback_service.port = USE_WEBSERVER_PORT; @@ -214,7 +187,7 @@ void MDNSComponent::dump_config() { "mDNS:\n" " Hostname: %s", this->hostname_.c_str()); -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGV(TAG, " Services:"); for (const auto &service : this->services_) { ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), @@ -227,8 +200,6 @@ void MDNSComponent::dump_config() { #endif } -std::vector MDNSComponent::get_services() { return this->services_; } - } // namespace mdns } // namespace esphome #endif diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index f87ef08bcd..e0e268c914 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -2,13 +2,16 @@ #include "esphome/core/defines.h" #ifdef USE_MDNS #include -#include #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/helpers.h" namespace esphome { namespace mdns { +// Service count is calculated at compile time by Python codegen +// MDNS_SERVICE_COUNT will always be defined + struct MDNSTXTRecord { std::string key; TemplatableValue value; @@ -36,18 +39,15 @@ class MDNSComponent : public Component { float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } #ifdef USE_MDNS_EXTRA_SERVICES - void add_extra_service(MDNSService service) { services_extra_.push_back(std::move(service)); } + void add_extra_service(MDNSService service) { this->services_.emplace_next() = std::move(service); } #endif - std::vector get_services(); + const StaticVector &get_services() const { return this->services_; } void on_shutdown() override; protected: -#ifdef USE_MDNS_EXTRA_SERVICES - std::vector services_extra_{}; -#endif - std::vector services_{}; + StaticVector services_{}; std::string hostname_; void compile_records_(); }; diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index 5b5c113f83..57b972d195 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -143,11 +143,10 @@ void OpenThreadSrpComponent::setup() { return; } - // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this - // component - this->mdns_services_ = this->mdns_->get_services(); - ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size()); - for (const auto &service : this->mdns_services_) { + // Get mdns services and copy their data (strings are copied with strdup below) + const auto &mdns_services = this->mdns_->get_services(); + ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", mdns_services.size()); + for (const auto &service : mdns_services) { otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); if (!entry) { ESP_LOGW(TAG, "Failed to allocate service entry"); diff --git a/esphome/components/openthread/openthread.h b/esphome/components/openthread/openthread.h index a9aff78e56..5d139c633d 100644 --- a/esphome/components/openthread/openthread.h +++ b/esphome/components/openthread/openthread.h @@ -57,7 +57,6 @@ class OpenThreadSrpComponent : public Component { protected: esphome::mdns::MDNSComponent *mdns_{nullptr}; - std::vector mdns_services_; std::vector> memory_pool_; void *pool_alloc_(size_t size); }; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 5516b06040..554e1ee13c 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -82,6 +82,7 @@ #define USE_LVGL_TILEVIEW #define USE_LVGL_TOUCHSCREEN #define USE_MDNS +#define MDNS_SERVICE_COUNT 3 #define USE_MEDIA_PLAYER #define USE_NEXTION_TFT_UPLOAD #define USE_NUMBER diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a28718de5a..53e82abfef 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -127,6 +127,16 @@ template class StaticVector { } } + // Return reference to next element and increment count (with bounds checking) + T &emplace_next() { + if (count_ >= N) { + // Should never happen with proper size calculation + // Return reference to last element to avoid crash + return data_[N - 1]; + } + return data_[count_++]; + } + size_t size() const { return count_; } bool empty() const { return count_ == 0; }