yzx_core/
checksum.rs

1/*
2 * Description: Calculation of the content checksum for a decoded frame.
3 *
4 * Copyright (C) 2025 d@nny mc² <dmc2@hypnicjerk.ai>
5 * SPDX-License-Identifier: AGPL-3.0-or-later
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published
9 * by the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20
21//! Calculation of the content checksum for a decoded frame.
22//!
23//! This is specified in [section 3.1.1] of IETF RFC 8878.
24//!
25//! [section 3.1.1]: https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1
26
27use core::hash;
28
29use twox_hash::XxHash64;
30
31/// An optional checksum value provided at the end of the frame.
32///
33/// [`Content_Checksum`], as per the RFC:
34/// > An optional 32-bit checksum, only present if `Content_Checksum_Flag` is set.
35/// > The content checksum is the result of the `XXH64()` hash function [XXHASH] digesting the
36/// > original (decoded) data as input, and a seed of zero.
37/// > The low 4 bytes of the checksum are stored in little-endian format.
38///
39/// [`Content_Checksum`]: https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1-3.8
40/// [XXHASH]: http://www.xxhash.org/
41#[repr(transparent)]
42#[derive(Debug, Clone)]
43pub struct ContentChecksum(XxHash64);
44
45impl ContentChecksum {
46  const SEED: u64 = 0;
47
48  #[inline(always)]
49  pub const fn new() -> Self { Self(XxHash64::with_seed(Self::SEED)) }
50
51  #[inline(always)]
52  pub const fn total_len(&self) -> u64 { self.0.total_len() }
53
54  #[inline]
55  pub fn digest(&mut self, data: &[u8]) {
56    use hash::Hasher;
57    self.0.write(data)
58  }
59
60  #[inline]
61  pub fn finish(self) -> [u8; 4] {
62    use hash::Hasher;
63    Self::transform_result(self.0.finish())
64  }
65
66  /* Take low 4 bytes, then convert to little-endian. */
67  #[inline(always)]
68  const fn transform_result(h: u64) -> [u8; 4] { (h as u32).to_le_bytes() }
69
70  #[inline]
71  pub fn oneshot(data: &[u8]) -> [u8; 4] {
72    Self::transform_result(XxHash64::oneshot(Self::SEED, data))
73  }
74}
75
76impl Default for ContentChecksum {
77  #[inline(always)]
78  fn default() -> Self { Self::new() }
79}