esphome_native_api/
hash.rs1const FNV1_OFFSET_BASIS: u32 = 2166136261;
7const FNV1_PRIME: u32 = 16777619;
8
9fn to_snake_case_char(c: char) -> char {
10 if c == ' ' {
11 '_'
12 } else if c >= 'A' && c <= 'Z' {
13 ((c as u8) + (b'a' - b'A')) as char
14 } else {
15 c
16 }
17}
18
19fn to_sanitized_char(c: char) -> char {
20 if c == '-'
22 || c == '_'
23 || (c >= '0' && c <= '9')
24 || (c >= 'a' && c <= 'z')
25 || (c >= 'A' && c <= 'Z')
26 {
27 c
28 } else {
29 '_'
30 }
31}
32
33pub fn hash_fnv1(name: &String) -> u32 {
46 let mut hash = FNV1_OFFSET_BASIS;
47 for c in name.chars() {
48 hash = hash.wrapping_mul(FNV1_PRIME);
49 let processed_char = to_sanitized_char(to_snake_case_char(c));
50 hash ^= processed_char as u8 as u32;
51 }
52 hash
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 macro_rules! fnv1_hash_tests {
60 ($($name:ident: $input:expr => $expected:expr;)*) => {
61 $(
62 #[test]
63 fn $name() {
64 let actual = hash_fnv1(&$input.to_string());
65 assert_eq!(
66 actual, $expected,
67 "Hash mismatch for '{}': expected {:#x}, got {:#x}",
68 $input, $expected, actual
69 );
70 }
71 )*
72 };
73 }
74
75 fnv1_hash_tests! {
76 test_hash_foo: "foo" => 0x408F5E13u32;
78 test_hash_foo_uppercase: "Foo" => 0x408F5E13u32; test_hash_foo_all_caps: "FOO" => 0x408F5E13u32; test_hash_foo_bar_space: "foo bar" => 0x3AE35AA1u32; test_hash_foo_bar_space_caps: "Foo Bar" => 0x3AE35AA1u32; test_hash_foo_bar_underscore: "foo_bar" => 0x3AE35AA1u32;
85 test_hash_foo_bar_exclamation: "foo!bar" => 0x3AE35AA1u32; test_hash_foo_bar_at: "foo@bar" => 0x3AE35AA1u32; test_hash_foo_bar_hyphen: "foo-bar" => 0x438B12E3u32;
90 test_hash_foo123: "foo123" => 0xF3B0067Du32;
92 test_hash_empty: "" => 0x811C9DC5u32; test_hash_single_char: "a" => 0x050C5D7Eu32;
96 test_hash_my_sensor_name: "My Sensor Name" => 0x2760962Au32; }
99}