Skip to main content

mqdb_core/
checksum.rs

1// Copyright 2025-2026 LabOverWire. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crc32fast::Hasher;
5
6#[derive(Debug, thiserror::Error)]
7pub enum ChecksumError {
8    #[error("data too short for checksum")]
9    DataTooShort,
10    #[error("checksum mismatch")]
11    ChecksumMismatch,
12}
13
14#[must_use]
15pub fn compute_checksum(data: &[u8]) -> u32 {
16    let mut hasher = Hasher::new();
17    hasher.update(data);
18    hasher.finalize()
19}
20
21#[must_use]
22pub fn encode_with_checksum(data: &[u8]) -> Vec<u8> {
23    let checksum = compute_checksum(data);
24    let mut result = Vec::with_capacity(4 + data.len());
25    result.extend_from_slice(&checksum.to_be_bytes());
26    result.extend_from_slice(data);
27    result
28}
29
30/// # Errors
31/// Returns an error if data is too short or checksum doesn't match.
32pub fn decode_and_verify(data: &[u8]) -> Result<&[u8], ChecksumError> {
33    if data.len() < 4 {
34        return Err(ChecksumError::DataTooShort);
35    }
36
37    let stored_checksum = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
38    let payload = &data[4..];
39    let computed_checksum = compute_checksum(payload);
40
41    if stored_checksum == computed_checksum {
42        Ok(payload)
43    } else {
44        Err(ChecksumError::ChecksumMismatch)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn test_compute_checksum_consistency() {
54        let data = b"hello world";
55        let checksum1 = compute_checksum(data);
56        let checksum2 = compute_checksum(data);
57        assert_eq!(checksum1, checksum2);
58    }
59
60    #[test]
61    fn test_encode_with_checksum() {
62        let data = b"test data";
63        let encoded = encode_with_checksum(data);
64
65        assert_eq!(encoded.len(), 4 + data.len());
66        assert_eq!(&encoded[4..], data);
67    }
68
69    #[test]
70    fn test_decode_and_verify_valid() {
71        let data = b"test data";
72        let encoded = encode_with_checksum(data);
73        let decoded = decode_and_verify(&encoded).unwrap();
74        assert_eq!(decoded, data);
75    }
76
77    #[test]
78    fn test_decode_and_verify_corrupted() {
79        let data = b"test data";
80        let mut encoded = encode_with_checksum(data);
81
82        encoded[5] ^= 0xFF;
83
84        let result = decode_and_verify(&encoded);
85        assert!(result.is_err());
86    }
87
88    #[test]
89    fn test_decode_and_verify_too_short() {
90        let data = &[1, 2, 3];
91        let result = decode_and_verify(data);
92        assert!(result.is_err());
93    }
94
95    #[test]
96    fn test_bitflip_detection() {
97        let data = b"important data that must not be corrupted";
98        let encoded = encode_with_checksum(data);
99
100        for i in 4..encoded.len() {
101            let mut corrupted = encoded.clone();
102            corrupted[i] ^= 0x01;
103
104            let result = decode_and_verify(&corrupted);
105            assert!(result.is_err(), "Failed to detect bitflip at position {i}");
106        }
107    }
108}