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}