1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2pub mod hash;
3
4mod bytes;
5mod constants;
6mod math;
7mod memory;
8
9pub use hash::AxHasher;
12pub use hash::api::{axhash, axhash_of, axhash_of_seeded, axhash_seeded};
13pub use hash::build::AxBuildHasher;
14
15#[non_exhaustive]
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub enum RuntimeBackend {
23 Scalar,
25 Aarch64AesNeon,
27 X86_64AesAvx2,
29}
30
31#[inline(always)]
50pub fn runtime_backend() -> RuntimeBackend {
51 match bytes::selected_backend() {
52 bytes::Backend::Scalar => RuntimeBackend::Scalar,
53 #[cfg(target_arch = "aarch64")]
54 bytes::Backend::Aarch64AesNeon => RuntimeBackend::Aarch64AesNeon,
55 #[cfg(target_arch = "x86_64")]
56 bytes::Backend::X86_64AesAvx2 => RuntimeBackend::X86_64AesAvx2,
57 }
58}
59
60#[inline(always)]
74pub fn runtime_has_aes() -> bool {
75 runtime_backend() != RuntimeBackend::Scalar
76}
77
78#[cfg(test)]
79mod tests {
80 use crate::RuntimeBackend;
81 use crate::hash::AxHasher;
82 use crate::hash::api::*;
83 use core::hash::{Hash, Hasher};
84
85 #[derive(Hash)]
86 struct DemoRecord {
87 id: u64,
88 shard: u32,
89 flags: u32,
90 }
91
92 #[test]
93 fn hash_is_deterministic_for_bytes() {
94 let data = b"axhash regression seed";
95 let a = axhash_seeded(data, 0x1234_5678_9abc_def0);
96 let b = axhash_seeded(data, 0x1234_5678_9abc_def0);
97 assert_eq!(a, b);
98 }
99
100 #[test]
101 fn hash_changes_when_seed_changes() {
102 let data = b"same payload different seed";
103 let a = axhash_seeded(data, 1);
104 let b = axhash_seeded(data, 2);
105 assert_ne!(a, b);
106 }
107
108 #[test]
109 fn hash_trait_path_is_deterministic() {
110 let record = DemoRecord {
111 id: 42,
112 shard: 7,
113 flags: 3,
114 };
115 let a = axhash_of_seeded(&record, 0xdead_beef);
116 let b = axhash_of_seeded(&record, 0xdead_beef);
117 assert_eq!(a, b);
118 }
119
120 #[test]
121 fn primitive_writes_produce_a_stable_finish() {
122 let mut hasher = AxHasher::new_with_seed(0x4444);
123 hasher.write_u64(0x0102_0304_0506_0708);
124 hasher.write_u32(0xaabb_ccdd);
125 hasher.write_u16(0xeeff);
126 hasher.write_u8(0x11);
127 let value = hasher.finish();
128 assert_ne!(value, 0);
129 }
130
131 #[test]
132 fn test_axhash_default_seed() {
133 let data = b"default seed test";
134 let a = axhash(data);
135 let b = axhash_seeded(data, 0);
136 assert_eq!(a, b);
137 }
138
139 #[test]
140 fn test_axhash_of_default_seed() {
141 let record = DemoRecord {
142 id: 1,
143 shard: 2,
144 flags: 3,
145 };
146 let a = axhash_of(&record);
147 let b = axhash_of_seeded(&record, 0);
148 assert_eq!(a, b);
149 }
150
151 #[test]
152 fn runtime_backend_smoke_test() {
153 match super::runtime_backend() {
154 RuntimeBackend::Scalar
155 | RuntimeBackend::Aarch64AesNeon
156 | RuntimeBackend::X86_64AesAvx2 => {}
157 }
158 }
159
160 #[test]
163 fn empty_input_is_deterministic() {
164 assert_eq!(axhash(b""), axhash(b""));
165 assert_eq!(axhash_seeded(b"", 0), axhash_seeded(b"", 0));
166 assert_eq!(
167 axhash_seeded(b"", 0xdead_beef),
168 axhash_seeded(b"", 0xdead_beef)
169 );
170 }
171
172 #[test]
173 fn empty_input_changes_with_seed() {
174 assert_ne!(axhash_seeded(b"", 0), axhash_seeded(b"", 1));
175 }
176
177 #[test]
178 fn write_empty_bytes_is_noop() {
179 let seed = 0x1234_5678;
181
182 let mut h1 = AxHasher::new_with_seed(seed);
183 h1.write_u64(0xabcd);
184
185 let mut h2 = AxHasher::new_with_seed(seed);
186 h2.write(b"");
187 h2.write_u64(0xabcd);
188 h2.write(b"");
189
190 assert_eq!(h1.finish(), h2.finish());
191 }
192
193 #[test]
196 fn boundary_lengths_are_deterministic() {
197 let data = vec![0xABu8; 300];
205 let boundaries = [
206 0usize, 1, 7, 8, 15, 16, 17, 24, 32, 33, 48, 64, 65, 96, 128, 129, 192, 256, 300,
207 ];
208 for &len in &boundaries {
209 let slice = &data[..len];
210 assert_eq!(
211 axhash(slice),
212 axhash(slice),
213 "non-deterministic at len={len}"
214 );
215 }
216 }
217
218 #[test]
219 fn adjacent_lengths_produce_different_hashes() {
220 let data = vec![0xCCu8; 300];
222 let boundaries = [1, 8, 16, 17, 32, 33, 64, 65, 128, 129];
223 for &len in &boundaries {
224 let h1 = axhash(&data[..len]);
225 let h2 = axhash(&data[..len + 1]);
226 assert_ne!(h1, h2, "collision at len={len} vs len={}", len + 1);
227 }
228 }
229
230 #[test]
231 fn finish_is_idempotent() {
232 let mut hasher = AxHasher::new_with_seed(0x9999);
235 hasher.write_u32(0x1234);
236 let a = hasher.finish();
237 let b = hasher.finish();
238 assert_eq!(a, b);
239 }
240
241 #[test]
242 fn default_equals_new() {
243 use core::hash::Hasher as _;
244 let mut h1 = AxHasher::default();
245 let mut h2 = AxHasher::new();
246 h1.write(b"hello");
247 h2.write(b"hello");
248 assert_eq!(h1.finish(), h2.finish());
249 }
250
251 #[test]
254 fn all_single_bytes_are_distinct() {
255 let hashes: std::collections::HashSet<u64> = (0u8..=255).map(|b| axhash(&[b])).collect();
258 assert_eq!(hashes.len(), 256, "collision among single-byte inputs");
259 }
260
261 #[test]
262 fn single_byte_differs_from_empty() {
263 let empty = axhash(b"");
264 for b in 0u8..=255 {
265 assert_ne!(axhash(&[b]), empty, "byte {b} collides with empty input");
266 }
267 }
268
269 #[test]
272 fn distinct_seeds_produce_independent_families() {
273 let input = b"seed independence check";
275 let hashes: std::collections::HashSet<u64> =
276 (0u64..256).map(|s| axhash_seeded(input, s)).collect();
277 assert_eq!(hashes.len(), 256, "seed collision detected");
278 }
279
280 #[test]
283 fn crate_root_reexports_match_hash_api() {
284 use crate::{axhash as root_axhash, axhash_seeded as root_seeded};
287 let data = b"reexport consistency";
288 assert_eq!(root_axhash(data), axhash(data));
289 assert_eq!(root_seeded(data, 0xABCD), axhash_seeded(data, 0xABCD));
290 }
291}