mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
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:
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user