Skip to main content

fxfs/
checksum.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::errors::FxfsError;
6use anyhow::Error;
7use fprint::TypeFingerprint;
8use serde::{Deserialize, Serialize};
9use static_assertions::assert_cfg;
10use storage_ptr_slice::PtrByteSlice;
11use zerocopy::{FromBytes as _, IntoBytes as _};
12
13/// For the foreseeable future, Fxfs will use 64-bit checksums.
14pub type Checksum = u64;
15
16/// Generates a Fletcher64 checksum of |buf| seeded by |previous|.
17///
18/// All logfile blocks are covered by a fletcher64 checksum as the last 8 bytes in a block.
19///
20/// We also use this checksum for integrity validation of potentially out-of-order writes
21/// during Journal replay.
22pub fn fletcher64(buf: &[u8], previous: Checksum) -> Checksum {
23    fletcher64_ptr(buf.into(), previous)
24}
25
26/// Generates a Fletcher64 checksum of |buf| (a pointer slice) seeded by |previous|.
27/// This is completely safe from Rust aliasing UB on allocator-managed memory.
28pub fn fletcher64_ptr(buf: PtrByteSlice<'_>, previous: Checksum) -> Checksum {
29    assert!(buf.len() % 4 == 0);
30    let mut lo = previous as u32;
31    let mut hi = (previous >> 32) as u32;
32    for chunk in buf.iter_as::<[u8; 4]>() {
33        let val = u32::from_le_bytes(chunk.read());
34        lo = lo.wrapping_add(val);
35        hi = hi.wrapping_add(lo);
36    }
37    (hi as u64) << 32 | lo as u64
38}
39
40/// A vector of fletcher64 checksums, one per block.
41/// These are stored as a flat array of bytes for efficient deserialization.
42pub type Checksums = ChecksumsV38;
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TypeFingerprint)]
45#[cfg_attr(fuzz, derive(arbitrary::Arbitrary))]
46pub struct ChecksumsV38 {
47    #[serde(with = "crate::zerocopy_serialization")]
48    sums: Vec<u8>,
49}
50
51impl Checksums {
52    pub fn fletcher(checksums: Vec<Checksum>) -> Self {
53        assert_cfg!(target_endian = "little");
54        let checksums_as_u8: &[u8] = &*checksums.as_bytes();
55        Self { sums: checksums_as_u8.to_owned() }
56    }
57
58    pub fn len(&self) -> usize {
59        self.sums.len() / std::mem::size_of::<Checksum>()
60    }
61
62    pub fn maybe_as_ref(&self) -> Result<&[Checksum], Error> {
63        assert_cfg!(target_endian = "little");
64        <[Checksum]>::ref_from_bytes(&self.sums).map_err(|_| FxfsError::Inconsistent.into())
65    }
66
67    pub fn offset_by(&self, amount: usize) -> Self {
68        Checksums { sums: self.sums[amount * std::mem::size_of::<Checksum>()..].to_vec() }
69    }
70
71    pub fn shrunk(&self, len: usize) -> Self {
72        Checksums { sums: self.sums[..len * std::mem::size_of::<Checksum>()].to_vec() }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use crate::checksum::Checksums;
79    use crate::errors::FxfsError;
80
81    #[test]
82    fn checksum_encoding_idempotent() {
83        let mut checksums = vec![0xabu64 << 56, 0x11002200u64, u64::MAX, 0];
84        checksums.reserve_exact(5);
85
86        let encoded = Checksums::fletcher(checksums.clone());
87        let decoded = encoded.maybe_as_ref().unwrap();
88
89        assert_eq!(decoded, &checksums[..]);
90    }
91
92    #[test]
93    fn deserialize_invalid_checksum() {
94        let bad = Checksums { sums: vec![0, 1, 2, 3, 4, 5, 6] };
95        let res = bad.maybe_as_ref().expect_err("deserialization should fail");
96        assert!(FxfsError::Inconsistent.matches(&res));
97    }
98}