[mqtt] Use stack buffers for discovery message formatting (#13216)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
J. Nick Koston
2026-01-21 18:35:43 -10:00
committed by GitHub
parent 645832a070
commit aa5092bdc2
5 changed files with 49 additions and 18 deletions
@@ -18,7 +18,7 @@ bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t num
} }
bool CustomMQTTDevice::publish(const std::string &topic, int value) { bool CustomMQTTDevice::publish(const std::string &topic, int value) {
char buffer[24]; char buffer[24];
int len = snprintf(buffer, sizeof(buffer), "%d", value); size_t len = buf_append_printf(buffer, sizeof(buffer), 0, "%d", value);
return global_mqtt_client->publish(topic, buffer, len); return global_mqtt_client->publish(topic, buffer, len);
} }
bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) { bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) {
+11 -1
View File
@@ -98,7 +98,17 @@ void MQTTClientComponent::send_device_info_() {
uint8_t index = 0; uint8_t index = 0;
for (auto &ip : network::get_ip_addresses()) { for (auto &ip : network::get_ip_addresses()) {
if (ip.is_set()) { if (ip.is_set()) {
root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str(); char key[8]; // "ip" + up to 3 digits + null
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
if (index == 0) {
key[0] = 'i';
key[1] = 'p';
key[2] = '\0';
} else {
buf_append_printf(key, sizeof(key), 0, "ip%u", index);
}
ip.str_to(ip_buf);
root[key] = ip_buf;
index++; index++;
} }
} }
+35 -14
View File
@@ -190,27 +190,37 @@ bool MQTTComponent::send_discovery_() {
StringRef object_id = this->get_default_object_id_to_(object_id_buf); StringRef object_id = this->get_default_object_id_to_(object_id_buf);
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
char friendly_name_hash[9]; char friendly_name_hash[9];
snprintf(friendly_name_hash, sizeof(friendly_name_hash), "%08" PRIx32, fnv1_hash(this->friendly_name_())); buf_append_printf(friendly_name_hash, sizeof(friendly_name_hash), 0, "%08" PRIx32,
fnv1_hash(this->friendly_name_()));
// Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678") // Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678")
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43 // MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11]; char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
char mac_buf[MAC_ADDRESS_BUFFER_SIZE]; char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
get_mac_address_into_buffer(mac_buf); get_mac_address_into_buffer(mac_buf);
snprintf(unique_id, sizeof(unique_id), "%s-%s-%s", mac_buf, this->component_type(), friendly_name_hash); buf_append_printf(unique_id, sizeof(unique_id), 0, "%s-%s-%s", mac_buf, this->component_type(),
friendly_name_hash);
root[MQTT_UNIQUE_ID] = unique_id; root[MQTT_UNIQUE_ID] = unique_id;
} else { } else {
// default to almost-unique ID. It's a hack but the only way to get that // default to almost-unique ID. It's a hack but the only way to get that
// gorgeous device registry view. // gorgeous device registry view.
root[MQTT_UNIQUE_ID] = "ESP" + std::string(this->component_type()) + object_id.c_str(); // "ESP" (3) + component_type (max 20) + object_id (max 128) + null
char unique_id_buf[3 + MQTT_COMPONENT_TYPE_MAX_LEN + OBJECT_ID_MAX_LEN + 1];
buf_append_printf(unique_id_buf, sizeof(unique_id_buf), 0, "ESP%s%s", this->component_type(),
object_id.c_str());
root[MQTT_UNIQUE_ID] = unique_id_buf;
} }
const std::string &node_name = App.get_name(); const std::string &node_name = App.get_name();
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) {
root[MQTT_OBJECT_ID] = node_name + "_" + object_id.c_str(); // node_name (max 31) + "_" (1) + object_id (max 128) + null
char object_id_full[ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1];
buf_append_printf(object_id_full, sizeof(object_id_full), 0, "%s_%s", node_name.c_str(), object_id.c_str());
root[MQTT_OBJECT_ID] = object_id_full;
}
const std::string &friendly_name_ref = App.get_friendly_name(); const std::string &friendly_name_ref = App.get_friendly_name();
const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref; const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref;
std::string node_area = App.get_area(); const char *node_area = App.get_area();
JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>(); JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
char mac[MAC_ADDRESS_BUFFER_SIZE]; char mac[MAC_ADDRESS_BUFFER_SIZE];
@@ -221,18 +231,29 @@ bool MQTTComponent::send_discovery_() {
device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION " (ESPHome " ESPHOME_VERSION ")"; device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION " (ESPHome " ESPHOME_VERSION ")";
const char *model = std::strchr(ESPHOME_PROJECT_NAME, '.'); const char *model = std::strchr(ESPHOME_PROJECT_NAME, '.');
device_info[MQTT_DEVICE_MODEL] = model == nullptr ? ESPHOME_BOARD : model + 1; device_info[MQTT_DEVICE_MODEL] = model == nullptr ? ESPHOME_BOARD : model + 1;
device_info[MQTT_DEVICE_MANUFACTURER] = if (model == nullptr) {
model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME); device_info[MQTT_DEVICE_MANUFACTURER] = ESPHOME_PROJECT_NAME;
} else {
// Extract manufacturer (part before '.') using stack buffer to avoid heap allocation
// memcpy is used instead of strncpy since we know the exact length and strncpy
// would still require manual null-termination
char manufacturer[sizeof(ESPHOME_PROJECT_NAME)];
size_t len = model - ESPHOME_PROJECT_NAME;
memcpy(manufacturer, ESPHOME_PROJECT_NAME, len);
manufacturer[len] = '\0';
device_info[MQTT_DEVICE_MANUFACTURER] = manufacturer;
}
#else #else
static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")"; static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")";
// Buffer sized for format string expansion: ~4 bytes net growth from format specifier to 8 hex digits, plus
// safety margin
char version_buf[sizeof(ver_fmt) + 8];
#ifdef USE_ESP8266 #ifdef USE_ESP8266
char fmt_buf[sizeof(ver_fmt)]; snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
strcpy_P(fmt_buf, ver_fmt);
const char *fmt = fmt_buf;
#else #else
const char *fmt = ver_fmt; snprintf(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
#endif #endif
device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(fmt, App.get_config_hash()); device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
#if defined(USE_ESP8266) || defined(USE_ESP32) #if defined(USE_ESP8266) || defined(USE_ESP32)
device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif"; device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif";
@@ -246,7 +267,7 @@ bool MQTTComponent::send_discovery_() {
device_info[MQTT_DEVICE_MANUFACTURER] = "Host"; device_info[MQTT_DEVICE_MANUFACTURER] = "Host";
#endif #endif
#endif #endif
if (!node_area.empty()) { if (node_area[0] != '\0') {
device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area; device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
} }
+1 -1
View File
@@ -175,7 +175,7 @@ bool MQTTFanComponent::publish_state() {
auto traits = this->state_->get_traits(); auto traits = this->state_->get_traits();
if (traits.supports_speed()) { if (traits.supports_speed()) {
char buf[12]; char buf[12];
int len = snprintf(buf, sizeof(buf), "%d", this->state_->speed); size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
bool success = this->publish(this->get_speed_level_state_topic(), buf, len); bool success = this->publish(this->get_speed_level_state_topic(), buf, len);
failed = failed || !success; failed = failed || !success;
} }
+1 -1
View File
@@ -75,7 +75,7 @@ bool MQTTNumberComponent::send_initial_state() {
} }
bool MQTTNumberComponent::publish_state(float value) { bool MQTTNumberComponent::publish_state(float value) {
char buffer[64]; char buffer[64];
snprintf(buffer, sizeof(buffer), "%f", value); buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
return this->publish(this->get_state_topic_(), buffer); return this->publish(this->get_state_topic_(), buffer);
} }