Add a flash safe fnv1_hash_object_id_P()

calc_object_id_() hashes the name in place instead of copying it into a
stack buffer first.
This commit is contained in:
J. Nick Koston
2026-10-08 18:11:36 -10:00
parent 93aecdd435
commit b4289aee59
3 changed files with 22 additions and 5 deletions
+1 -3
View File
@@ -119,9 +119,7 @@ const char *EntityBase::get_icon_to([[maybe_unused]] std::span<char, MAX_ICON_LE
// Calculate Object ID Hash directly from name using snake_case + sanitize
void EntityBase::calc_object_id_() {
char buf[ENTITY_NAME_BUF_SIZE];
StringRef name = this->get_name_to(buf);
this->object_id_hash_ = fnv1_hash_object_id(name.c_str(), name.size());
this->object_id_hash_ = fnv1_hash_object_id_P(this->name_.progmem_ptr(), this->name_.size());
}
size_t EntityBase::write_object_id_to(char *buf, size_t buf_size) const {
+13 -2
View File
@@ -1140,15 +1140,26 @@ template<size_t N> inline char *str_sanitize_to(char (&buffer)[N], const char *s
/// This computes object_id hashes directly from names without creating an intermediate buffer.
/// IMPORTANT: Must match Python fnv1_hash_object_id() in esphome/helpers.py.
/// If you modify this function, update the Python version and tests in both places.
inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
template<typename ReadByte> inline uint32_t fnv1_hash_object_id_with(const char *str, size_t len, ReadByte read) {
uint32_t hash = FNV1_OFFSET_BASIS;
for (size_t i = 0; i < len; i++) {
hash *= FNV1_PRIME;
// Apply snake_case (space->underscore, uppercase->lowercase) then sanitize
hash ^= static_cast<uint8_t>(to_sanitized_char(to_snake_case_char(str[i])));
hash ^= static_cast<uint8_t>(to_sanitized_char(to_snake_case_char(read(str + i))));
}
return hash;
}
inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
return fnv1_hash_object_id_with(str, len, [](const char *p) { return *p; });
}
/// fnv1_hash_object_id() for a string that may be in flash (PROGMEM).
inline uint32_t fnv1_hash_object_id_P(const char *str, size_t len) {
#ifdef USE_ESP8266
return fnv1_hash_object_id_with(str, len, [](const char *p) { return static_cast<char>(pgm_read_byte(p)); });
#else
return fnv1_hash_object_id(str, len);
#endif
}
// str_snprintf, str_sprintf moved to alloc_helpers.h - remove this comment before 2026.11.0
+8
View File
@@ -440,4 +440,12 @@ TEST(RAMAllocatorMakeUnique, ArrayFormAllocatesElements) {
EXPECT_EQ(buf[255], 0xA5);
}
TEST(Fnv1HashObjectId, ProgmemVariantMatches) {
// Values match Python fnv1_hash_object_id(); the _P variant must hash identically
static const char NAME[] = "Living Room Temp";
const size_t len = sizeof(NAME) - 1;
EXPECT_EQ(fnv1_hash_object_id_P(NAME, len), fnv1_hash_object_id(NAME, len));
EXPECT_EQ(fnv1_hash_object_id_P("", 0), fnv1_hash_object_id("", 0));
}
} // namespace esphome::core::testing