1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::error::{DataError, DataErrorKind};
6
7use crate::fallback::{LocaleFallbackConfig, LocaleFallbackPriority, LocaleFallbackSupplement};
8use alloc::borrow::Cow;
9use core::fmt;
10use core::fmt::Write;
11use core::ops::Deref;
12use writeable::{LengthHint, Writeable};
13use zerovec::ule::*;
14
15#[doc(hidden)]
16#[macro_export]
17macro_rules! leading_tag {
18 () => {
19 "\nicu4x_key_tag"
20 };
21}
22
23#[doc(hidden)]
24#[macro_export]
25macro_rules! trailing_tag {
26 () => {
27 "\n"
28 };
29}
30
31#[doc(hidden)]
32#[macro_export]
33macro_rules! tagged {
34 ($without_tags:expr) => {
35 concat!(
36 $crate::leading_tag!(),
37 $without_tags,
38 $crate::trailing_tag!()
39 )
40 };
41}
42
43/// A compact hash of a [`DataKey`]. Useful for keys in maps.
44///
45/// The hash will be stable over time within major releases.
46#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, ULE)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[repr(transparent)]
49pub struct DataKeyHash([u8; 4]);
50
51impl DataKeyHash {
52 const fn compute_from_path(path: DataKeyPath) -> Self {
53 let hash: u32 = fxhash_32(
54 path.tagged.as_bytes(),
55 ignore_leading:leading_tag!().len(),
56 ignore_trailing:trailing_tag!().len(),
57 );
58 Self(hash.to_le_bytes())
59 }
60
61 /// Gets the hash value as a byte array.
62 pub const fn to_bytes(self) -> [u8; 4] {
63 self.0
64 }
65}
66
67/// Const function to compute the FxHash of a byte array.
68///
69/// FxHash is a speedy hash algorithm used within rustc. The algorithm is satisfactory for our
70/// use case since the strings being hashed originate from a trusted source (the ICU4X
71/// components), and the hashes are computed at compile time, so we can check for collisions.
72///
73/// We could have considered a SHA or other cryptographic hash function. However, we are using
74/// FxHash because:
75///
76/// 1. There is precedent for this algorithm in Rust
77/// 2. The algorithm is easy to implement as a const function
78/// 3. The amount of code is small enough that we can reasonably keep the algorithm in-tree
79/// 4. FxHash is designed to output 32-bit or 64-bit values, whereas SHA outputs more bits,
80/// such that truncation would be required in order to fit into a u32, partially reducing
81/// the benefit of a cryptographically secure algorithm
82// The indexing operations in this function have been reviewed in detail and won't panic.
83#[allow(clippy::indexing_slicing)]
84const fn fxhash_32(bytes: &[u8], ignore_leading: usize, ignore_trailing: usize) -> u32 {
85 // This code is adapted from https://github.com/rust-lang/rustc-hash,
86 // whose license text is reproduced below.
87 //
88 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
89 // file at the top-level directory of this distribution and at
90 // http://rust-lang.org/COPYRIGHT.
91 //
92 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
93 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
94 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
95 // option. This file may not be copied, modified, or distributed
96 // except according to those terms.
97
98 if ignore_leading + ignore_trailing >= bytes.len() {
99 return 0;
100 }
101
102 #[inline]
103 const fn hash_word_32(mut hash: u32, word: u32) -> u32 {
104 const ROTATE: u32 = 5;
105 const SEED32: u32 = 0x9e_37_79_b9;
106 hash = hash.rotate_left(ROTATE);
107 hash ^= word;
108 hash = hash.wrapping_mul(SEED32);
109 hash
110 }
111
112 let mut cursor = ignore_leading;
113 let end = bytes.len() - ignore_trailing;
114 let mut hash = 0;
115
116 while end - cursor >= 4 {
117 let word = u32::from_le_bytes([
118 bytes[cursor],
119 bytes[cursor + 1],
120 bytes[cursor + 2],
121 bytes[cursor + 3],
122 ]);
123 hash = hash_word_32(hash, word);
124 cursor += 4;
125 }
126
127 if end - cursor >= 2 {
128 let word = u16::from_le_bytes([bytes[cursor], bytes[cursor + 1]]);
129 hash = hash_word_32(hash, word as u32);
130 cursor += 2;
131 }
132
133 if end - cursor >= 1 {
134 hash = hash_word_32(hash, bytes[cursor] as u32);
135 }
136
137 hash
138}
139
140impl<'a> zerovec::maps::ZeroMapKV<'a> for DataKeyHash {
141 type Container = zerovec::ZeroVec<'a, DataKeyHash>;
142 type Slice = zerovec::ZeroSlice<DataKeyHash>;
143 type GetType = <DataKeyHash as AsULE>::ULE;
144 type OwnedType = DataKeyHash;
145}
146
147impl AsULE for DataKeyHash {
148 type ULE = Self;
149 #[inline]
150 fn to_unaligned(self) -> Self::ULE {
151 self
152 }
153 #[inline]
154 fn from_unaligned(unaligned: Self::ULE) -> Self {
155 unaligned
156 }
157}
158
159// Safe since the ULE type is `self`.
160unsafe impl EqULE for DataKeyHash {}
161
162/// The string path of a data key. For example, "foo@1"
163#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
164pub struct DataKeyPath {
165 // This string literal is wrapped in leading_tag!() and trailing_tag!() to make it detectable
166 // in a compiled binary.
167 tagged: &'static str,
168}
169
170impl DataKeyPath {
171 /// Gets the path as a static string slice.
172 #[inline]
173 pub const fn get(self) -> &'static str {
174 unsafe {
175 // Safe due to invariant that self.path is tagged correctly
176 core::str::from_utf8_unchecked(core::mem::transmute((
177 self.tagged.as_ptr().add(count:leading_tag!().len()),
178 self.tagged.len() - trailing_tag!().len() - leading_tag!().len(),
179 )))
180 }
181 }
182}
183
184impl Deref for DataKeyPath {
185 type Target = str;
186 #[inline]
187 fn deref(&self) -> &Self::Target {
188 self.get()
189 }
190}
191
192/// Metadata statically associated with a particular [`DataKey`].
193#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
194#[non_exhaustive]
195pub struct DataKeyMetadata {
196 /// What to prioritize when fallbacking on this [`DataKey`].
197 pub fallback_priority: LocaleFallbackPriority,
198 /// A Unicode extension keyword to consider when loading data for this [`DataKey`].
199 pub extension_key: Option<icu_locid::extensions::unicode::Key>,
200 /// Optional choice for additional fallbacking data required for loading this marker.
201 ///
202 /// For more information, see `LocaleFallbackConfig::fallback_supplement`.
203 pub fallback_supplement: Option<LocaleFallbackSupplement>,
204 /// Whether the key has a singleton value, as opposed to per-locale values. Singleton
205 /// keys behave differently, e.g. they never perform fallback, and can be optimized
206 /// in data providers.
207 pub singleton: bool,
208}
209
210impl DataKeyMetadata {
211 /// Const-friendly version of [`Default::default`].
212 pub const fn const_default() -> Self {
213 Self {
214 fallback_priority: LocaleFallbackPriority::const_default(),
215 extension_key: None,
216 fallback_supplement: None,
217 singleton: false,
218 }
219 }
220
221 #[doc(hidden)]
222 pub const fn construct_internal(
223 fallback_priority: LocaleFallbackPriority,
224 extension_key: Option<icu_locid::extensions::unicode::Key>,
225 fallback_supplement: Option<LocaleFallbackSupplement>,
226 singleton: bool,
227 ) -> Self {
228 Self {
229 fallback_priority,
230 extension_key,
231 fallback_supplement,
232 singleton,
233 }
234 }
235}
236
237impl Default for DataKeyMetadata {
238 #[inline]
239 fn default() -> Self {
240 Self::const_default()
241 }
242}
243
244/// Used for loading data from an ICU4X data provider.
245///
246/// A resource key is tightly coupled with the code that uses it to load data at runtime.
247/// Executables can be searched for `DataKey` instances to produce optimized data files.
248/// Therefore, users should not generally create DataKey instances; they should instead use
249/// the ones exported by a component.
250///
251/// `DataKey`s are created with the [`data_key!`](crate::data_key) macro:
252///
253/// ```
254/// # use icu_provider::DataKey;
255/// const K: DataKey = icu_provider::data_key!("foo/bar@1");
256/// ```
257///
258/// The human-readable path string ends with `@` followed by one or more digits (the version
259/// number). Paths do not contain characters other than ASCII letters and digits, `_`, `/`.
260///
261/// Invalid paths are compile-time errors (as [`data_key!`](crate::data_key) uses `const`).
262///
263/// ```compile_fail,E0080
264/// # use icu_provider::DataKey;
265/// const K: DataKey = icu_provider::data_key!("foo/../bar@1");
266/// ```
267#[derive(Copy, Clone)]
268pub struct DataKey {
269 path: DataKeyPath,
270 hash: DataKeyHash,
271 metadata: DataKeyMetadata,
272}
273
274impl PartialEq for DataKey {
275 #[inline]
276 fn eq(&self, other: &Self) -> bool {
277 self.hash == other.hash && self.path == other.path && self.metadata == other.metadata
278 }
279}
280
281impl Eq for DataKey {}
282
283impl Ord for DataKey {
284 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
285 self.path
286 .cmp(&other.path)
287 .then_with(|| self.metadata.cmp(&other.metadata))
288 }
289}
290
291impl PartialOrd for DataKey {
292 #[inline]
293 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
294 Some(self.cmp(other))
295 }
296}
297
298impl core::hash::Hash for DataKey {
299 #[inline]
300 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
301 self.hash.hash(state)
302 }
303}
304
305impl DataKey {
306 /// Gets a human-readable representation of a [`DataKey`].
307 ///
308 /// The human-readable path string ends with `@` followed by one or more digits (the version
309 /// number). Paths do not contain characters other than ASCII letters and digits, `_`, `/`.
310 ///
311 /// Useful for reading and writing data to a file system.
312 #[inline]
313 pub const fn path(self) -> DataKeyPath {
314 self.path
315 }
316
317 /// Gets a platform-independent hash of a [`DataKey`].
318 ///
319 /// The hash is 4 bytes and allows for fast key comparison.
320 ///
321 /// # Example
322 ///
323 /// ```
324 /// use icu_provider::DataKey;
325 /// use icu_provider::DataKeyHash;
326 ///
327 /// const KEY: DataKey = icu_provider::data_key!("foo@1");
328 /// const KEY_HASH: DataKeyHash = KEY.hashed();
329 ///
330 /// assert_eq!(KEY_HASH.to_bytes(), [0xe2, 0xb6, 0x17, 0x71]);
331 /// ```
332 #[inline]
333 pub const fn hashed(self) -> DataKeyHash {
334 self.hash
335 }
336
337 /// Gets the metadata associated with this [`DataKey`].
338 #[inline]
339 pub const fn metadata(self) -> DataKeyMetadata {
340 self.metadata
341 }
342
343 /// Returns the [`LocaleFallbackConfig`] for this [`DataKey`].
344 #[inline]
345 pub const fn fallback_config(self) -> LocaleFallbackConfig {
346 let mut config = LocaleFallbackConfig::const_default();
347 config.priority = self.metadata.fallback_priority;
348 config.extension_key = self.metadata.extension_key;
349 config.fallback_supplement = self.metadata.fallback_supplement;
350 config
351 }
352
353 /// Constructs a [`DataKey`] from a path and metadata.
354 ///
355 /// # Examples
356 ///
357 /// ```
358 /// use icu_provider::data_key;
359 /// use icu_provider::DataKey;
360 ///
361 /// const CONST_KEY: DataKey = data_key!("foo@1");
362 ///
363 /// let runtime_key =
364 /// DataKey::from_path_and_metadata(CONST_KEY.path(), CONST_KEY.metadata());
365 ///
366 /// assert_eq!(CONST_KEY, runtime_key);
367 /// ```
368 #[inline]
369 pub const fn from_path_and_metadata(path: DataKeyPath, metadata: DataKeyMetadata) -> Self {
370 Self {
371 path,
372 hash: DataKeyHash::compute_from_path(path),
373 metadata,
374 }
375 }
376
377 #[doc(hidden)]
378 // Error is a str of the expected character class and the index where it wasn't encountered
379 // The indexing operations in this function have been reviewed in detail and won't panic.
380 #[allow(clippy::indexing_slicing)]
381 pub const fn construct_internal(
382 path: &'static str,
383 metadata: DataKeyMetadata,
384 ) -> Result<Self, (&'static str, usize)> {
385 if path.len() < leading_tag!().len() + trailing_tag!().len() {
386 return Err(("tag", 0));
387 }
388 // Start and end of the untagged part
389 let start = leading_tag!().len();
390 let end = path.len() - trailing_tag!().len();
391
392 // Check tags
393 let mut i = 0;
394 while i < leading_tag!().len() {
395 if path.as_bytes()[i] != leading_tag!().as_bytes()[i] {
396 return Err(("tag", 0));
397 }
398 i += 1;
399 }
400 i = 0;
401 while i < trailing_tag!().len() {
402 if path.as_bytes()[end + i] != trailing_tag!().as_bytes()[i] {
403 return Err(("tag", end + 1));
404 }
405 i += 1;
406 }
407
408 match Self::validate_path_manual_slice(path, start, end) {
409 Ok(()) => (),
410 Err(e) => return Err(e),
411 };
412
413 let path = DataKeyPath { tagged: path };
414
415 Ok(Self {
416 path,
417 hash: DataKeyHash::compute_from_path(path),
418 metadata,
419 })
420 }
421
422 const fn validate_path_manual_slice(
423 path: &'static str,
424 start: usize,
425 end: usize,
426 ) -> Result<(), (&'static str, usize)> {
427 debug_assert!(start <= end);
428 debug_assert!(end <= path.len());
429 // Regex: [a-zA-Z0-9_][a-zA-Z0-9_/]*@[0-9]+
430 enum State {
431 Empty,
432 Body,
433 At,
434 Version,
435 }
436 use State::*;
437 let mut i = start;
438 let mut state = Empty;
439 loop {
440 let byte = if i < end {
441 #[allow(clippy::indexing_slicing)] // protected by debug assertion
442 Some(path.as_bytes()[i])
443 } else {
444 None
445 };
446 state = match (state, byte) {
447 (Empty | Body, Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')) => Body,
448 (Body, Some(b'/')) => Body,
449 (Body, Some(b'@')) => At,
450 (At | Version, Some(b'0'..=b'9')) => Version,
451 // One of these cases will be hit at the latest when i == end, so the loop converges.
452 (Version, None) => {
453 return Ok(());
454 }
455
456 (Empty, _) => return Err(("[a-zA-Z0-9_]", i)),
457 (Body, _) => return Err(("[a-zA-z0-9_/@]", i)),
458 (At, _) => return Err(("[0-9]", i)),
459 (Version, _) => return Err(("[0-9]", i)),
460 };
461 i += 1;
462 }
463 }
464
465 /// Returns [`Ok`] if this data key matches the argument, or the appropriate error.
466 ///
467 /// Convenience method for data providers that support a single [`DataKey`].
468 ///
469 /// # Examples
470 ///
471 /// ```
472 /// use icu_provider::prelude::*;
473 ///
474 /// const FOO_BAR: DataKey = icu_provider::data_key!("foo/bar@1");
475 /// const FOO_BAZ: DataKey = icu_provider::data_key!("foo/baz@1");
476 /// const BAR_BAZ: DataKey = icu_provider::data_key!("bar/baz@1");
477 ///
478 /// assert!(matches!(FOO_BAR.match_key(FOO_BAR), Ok(())));
479 /// assert!(matches!(
480 /// FOO_BAR.match_key(FOO_BAZ),
481 /// Err(DataError {
482 /// kind: DataErrorKind::MissingDataKey,
483 /// ..
484 /// })
485 /// ));
486 /// assert!(matches!(
487 /// FOO_BAR.match_key(BAR_BAZ),
488 /// Err(DataError {
489 /// kind: DataErrorKind::MissingDataKey,
490 /// ..
491 /// })
492 /// ));
493 ///
494 /// // The error context contains the argument:
495 /// assert_eq!(FOO_BAR.match_key(BAR_BAZ).unwrap_err().key, Some(BAR_BAZ));
496 /// ```
497 pub fn match_key(self, key: Self) -> Result<(), DataError> {
498 if self == key {
499 Ok(())
500 } else {
501 Err(DataErrorKind::MissingDataKey.with_key(key))
502 }
503 }
504}
505
506/// See [`DataKey`].
507#[macro_export]
508macro_rules! data_key {
509 ($path:expr) => {{
510 $crate::data_key!($path, $crate::DataKeyMetadata::const_default())
511 }};
512 ($path:expr, $metadata:expr) => {{
513 // Force the DataKey into a const context
514 const RESOURCE_KEY_MACRO_CONST: $crate::DataKey = {
515 match $crate::DataKey::construct_internal($crate::tagged!($path), $metadata) {
516 Ok(v) => v,
517 #[allow(clippy::panic)] // Const context
518 Err(_) => panic!(concat!("Invalid resource key: ", $path)),
519 // TODO Once formatting is const:
520 // Err((expected, index)) => panic!(
521 // "Invalid resource key {:?}: expected {:?}, found {:?} ",
522 // $path,
523 // expected,
524 // $crate::tagged!($path).get(index..))
525 // );
526 }
527 };
528 RESOURCE_KEY_MACRO_CONST
529 }};
530}
531
532impl fmt::Debug for DataKey {
533 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
534 f.write_str(data:"DataKey{")?;
535 fmt::Display::fmt(self, f)?;
536 f.write_char('}')?;
537 Ok(())
538 }
539}
540
541impl Writeable for DataKey {
542 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
543 self.path().write_to(sink)
544 }
545
546 fn writeable_length_hint(&self) -> LengthHint {
547 self.path().writeable_length_hint()
548 }
549
550 fn write_to_string(&self) -> Cow<str> {
551 Cow::Borrowed(self.path().get())
552 }
553}
554
555writeable::impl_display_with_writeable!(DataKey);
556
557#[test]
558fn test_path_syntax() {
559 // Valid keys:
560 DataKey::construct_internal(tagged!("hello/world@1"), Default::default()).unwrap();
561 DataKey::construct_internal(tagged!("hello/world/foo@1"), Default::default()).unwrap();
562 DataKey::construct_internal(tagged!("hello/world@999"), Default::default()).unwrap();
563 DataKey::construct_internal(tagged!("hello_world/foo@1"), Default::default()).unwrap();
564 DataKey::construct_internal(tagged!("hello_458/world@1"), Default::default()).unwrap();
565 DataKey::construct_internal(tagged!("hello_world@1"), Default::default()).unwrap();
566
567 // No version:
568 assert_eq!(
569 DataKey::construct_internal(tagged!("hello/world"), Default::default()),
570 Err((
571 "[a-zA-z0-9_/@]",
572 concat!(leading_tag!(), "hello/world").len()
573 ))
574 );
575
576 assert_eq!(
577 DataKey::construct_internal(tagged!("hello/world@"), Default::default()),
578 Err(("[0-9]", concat!(leading_tag!(), "hello/world@").len()))
579 );
580 assert_eq!(
581 DataKey::construct_internal(tagged!("hello/world@foo"), Default::default()),
582 Err(("[0-9]", concat!(leading_tag!(), "hello/world@").len()))
583 );
584 assert_eq!(
585 DataKey::construct_internal(tagged!("hello/world@1foo"), Default::default()),
586 Err(("[0-9]", concat!(leading_tag!(), "hello/world@1").len()))
587 );
588
589 // Meta no longer accepted:
590 assert_eq!(
591 DataKey::construct_internal(tagged!("foo@1[R]"), Default::default()),
592 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
593 );
594 assert_eq!(
595 DataKey::construct_internal(tagged!("foo@1[u-ca]"), Default::default()),
596 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
597 );
598 assert_eq!(
599 DataKey::construct_internal(tagged!("foo@1[R][u-ca]"), Default::default()),
600 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
601 );
602
603 // Invalid meta:
604 assert_eq!(
605 DataKey::construct_internal(tagged!("foo@1[U]"), Default::default()),
606 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
607 );
608 assert_eq!(
609 DataKey::construct_internal(tagged!("foo@1[uca]"), Default::default()),
610 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
611 );
612 assert_eq!(
613 DataKey::construct_internal(tagged!("foo@1[u-"), Default::default()),
614 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
615 );
616 assert_eq!(
617 DataKey::construct_internal(tagged!("foo@1[u-caa]"), Default::default()),
618 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
619 );
620 assert_eq!(
621 DataKey::construct_internal(tagged!("foo@1[R"), Default::default()),
622 Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
623 );
624
625 // Invalid characters:
626 assert_eq!(
627 DataKey::construct_internal(tagged!("你好/世界@1"), Default::default()),
628 Err(("[a-zA-Z0-9_]", leading_tag!().len()))
629 );
630
631 // Invalid tag:
632 assert_eq!(
633 DataKey::construct_internal(
634 concat!("hello/world@1", trailing_tag!()),
635 Default::default()
636 ),
637 Err(("tag", 0))
638 );
639 assert_eq!(
640 DataKey::construct_internal(concat!(leading_tag!(), "hello/world@1"), Default::default()),
641 Err(("tag", concat!(leading_tag!(), "hello/world@1").len()))
642 );
643 assert_eq!(
644 DataKey::construct_internal("hello/world@1", Default::default()),
645 Err(("tag", 0))
646 );
647}
648
649#[test]
650fn test_key_to_string() {
651 struct KeyTestCase {
652 pub key: DataKey,
653 pub expected: &'static str,
654 }
655
656 for cas: KeyTestCase in [
657 KeyTestCase {
658 key: data_key!("core/cardinal@1"),
659 expected: "core/cardinal@1",
660 },
661 KeyTestCase {
662 key: data_key!("core/maxlengthsubcatg@1"),
663 expected: "core/maxlengthsubcatg@1",
664 },
665 KeyTestCase {
666 key: data_key!("core/cardinal@65535"),
667 expected: "core/cardinal@65535",
668 },
669 ] {
670 writeable::assert_writeable_eq!(&cas.key, cas.expected);
671 assert_eq!(cas.expected, &*cas.key.path());
672 }
673}
674
675#[test]
676fn test_hash_word_32() {
677 assert_eq!(0, fxhash_32(b"", 0, 0));
678 assert_eq!(0, fxhash_32(b"a", 1, 0));
679 assert_eq!(0, fxhash_32(b"a", 0, 1));
680 assert_eq!(0, fxhash_32(b"a", 0, 10));
681 assert_eq!(0, fxhash_32(b"a", 10, 0));
682 assert_eq!(0, fxhash_32(b"a", 1, 1));
683 assert_eq!(0xF3051F19, fxhash_32(b"a", 0, 0));
684 assert_eq!(0x2F9DF119, fxhash_32(b"ab", 0, 0));
685 assert_eq!(0xCB1D9396, fxhash_32(b"abc", 0, 0));
686 assert_eq!(0x8628F119, fxhash_32(b"abcd", 0, 0));
687 assert_eq!(0xBEBDB56D, fxhash_32(b"abcde", 0, 0));
688 assert_eq!(0x1CE8476D, fxhash_32(b"abcdef", 0, 0));
689 assert_eq!(0xC0F176A4, fxhash_32(b"abcdefg", 0, 0));
690 assert_eq!(0x09AB476D, fxhash_32(b"abcdefgh", 0, 0));
691 assert_eq!(0xB72F5D88, fxhash_32(b"abcdefghi", 0, 0));
692}
693
694#[test]
695fn test_key_hash() {
696 struct KeyTestCase {
697 pub key: DataKey,
698 pub hash: DataKeyHash,
699 }
700
701 for cas: KeyTestCase in [
702 KeyTestCase {
703 key: data_key!("core/cardinal@1"),
704 hash: DataKeyHash([172, 207, 42, 236]),
705 },
706 KeyTestCase {
707 key: data_key!("core/maxlengthsubcatg@1"),
708 hash: DataKeyHash([193, 6, 79, 61]),
709 },
710 KeyTestCase {
711 key: data_key!("core/cardinal@65535"),
712 hash: DataKeyHash([176, 131, 182, 223]),
713 },
714 ] {
715 assert_eq!(cas.hash, cas.key.hashed(), "{}", cas.key);
716 }
717}
718