embedded_savegame/
chksum.rs

1//! Checksum implementation for savegame validation
2//!
3//! This module provides a checksum type based on the DJB2 hash algorithm. The checksum
4//! uses only 31 bits, with the most significant bit reserved as a validity marker.
5//! This allows quick detection of uninitialized or invalid slots by checking if the
6//! first byte has the high bit set.
7
8/// A 31-bit checksum with validity marker
9///
10/// The checksum is computed using the DJB2 hash algorithm and masked to 31 bits.
11/// The most significant bit (bit 31) must be zero for a valid checksum, allowing
12/// quick detection of erased/uninitialized flash memory (which reads as 0xFF).
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Chksum(u32);
15
16/// Mask to keep only the lower 31 bits for the checksum value
17pub const CHKSUM_MASK: u32 = u32::MAX >> 1;
18
19/// Mask for the high bit of the first byte (0x80)
20///
21/// Used to quickly detect invalid/erased slots where the first byte is 0xFF
22pub const BYTE_MASK: u8 = !(u8::MAX >> 1); // 0x80
23
24impl Chksum {
25    /// Size of the checksum in bytes (4 bytes for u32)
26    pub const SIZE: usize = u32::BITS as usize / 8;
27
28    /// Create a zero checksum
29    ///
30    /// This is used as the initial previous checksum for the first savegame.
31    pub const fn zero() -> Self {
32        Self(0)
33    }
34
35    /// Compute a checksum for the given data, chained with a previous checksum
36    ///
37    /// Uses DJB2 hash algorithm. The previous checksum is included in the hash
38    /// to create a chain of checksums linking savegames together.
39    ///
40    /// # Arguments
41    ///
42    /// * `prev` - The checksum of the previous savegame
43    /// * `data` - The data to hash
44    pub const fn hash(prev: Chksum, data: &[u8]) -> Self {
45        let hash = djb2::hash(&prev.to_bytes());
46        let hash = djb2::hash_with_initial(hash, data);
47        Self(hash & CHKSUM_MASK)
48    }
49
50    /// Check if this checksum has a valid format
51    ///
52    /// A valid checksum has its most significant bit set to zero. This allows
53    /// quick detection of uninitialized flash (0xFF) or corrupted data.
54    pub const fn is_valid(&self) -> bool {
55        let value = self.0 & !CHKSUM_MASK;
56        value == 0
57    }
58
59    /// Convert the checksum to big-endian bytes
60    pub const fn to_bytes(&self) -> [u8; Self::SIZE] {
61        self.0.to_be_bytes()
62    }
63
64    /// Parse a checksum from big-endian bytes
65    pub const fn from_bytes(bytes: [u8; Self::SIZE]) -> Self {
66        Self(u32::from_be_bytes(bytes))
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_chksum() {
76        let data = b"hello world";
77        let chksum = Chksum::hash(Chksum::zero(), data);
78        assert_eq!(chksum, Chksum(646036933));
79        assert!(chksum.is_valid());
80    }
81
82    #[test]
83    fn test_header_mask() {
84        let chksum = Chksum(0xFFFFFFFF);
85        assert!(!chksum.is_valid());
86
87        let chksum = Chksum(0x7FFFFFFF);
88        assert!(chksum.is_valid());
89    }
90}